Подклассы встроенных типов в Python 2 и Python 3 - PullRequest
22 голосов
/ 02 ноября 2011

При создании подклассов встроенных типов я заметил довольно важное различие между Python 2 и Python 3 в типе возврата методов встроенных типов.Следующий код иллюстрирует это для наборов:

class MySet(set):

    pass

s1 = MySet([1, 2, 3, 4, 5])

s2 = MySet([1, 2, 3, 6, 7])

print(type(s1.union(s2)))

print(type(s1.intersection(s2)))

print(type(s1.difference(s2)))

В Python 2 все возвращаемые значения имеют тип MySet.С Python 3 возвращаемые типы set.Я не смог найти ни документации о том, каким должен быть результат, ни документации об изменениях в Python 3.

В любом случае, меня действительно волнует следующее: есть ли в Python 3 простой способполучить поведение, увиденное в Python 2, без переопределения каждого метода встроенных типов?

Ответы [ 3 ]

11 голосов
/ 02 ноября 2011

Это не общее изменение для встроенных типов при переходе с Python 2.x на 3.x - например, list и int ведут себя одинаково в 2.x и 3.Икс.Только тип набора был изменен, чтобы привести его в соответствие с другими типами, как обсуждалось в этой проблеме отслеживания ошибок .

Боюсь, нет действительно хорошего способа заставить его вести себяпо старому.Вот некоторый код, который мне удалось придумать:

class MySet(set):
    def copy(self):
        return MySet(self)
    def _make_binary_op(in_place_method):
        def bin_op(self, other):
            new = self.copy()
            in_place_method(new, other)
            return new
        return bin_op
    __rand__ = __and__ = _make_binary_op(set.__iand__)
    intersection = _make_binary_op(set.intersection_update)
    __ror__ = __or__ = _make_binary_op(set.__ior__)
    union = _make_binary_op(set.update)
    __sub__ = _make_binary_op(set.__isub__)
    difference = _make_binary_op(set.difference_update)
    __rxor__ = xor__ = _make_binary_op(set.__ixor__)
    symmetric_difference = _make_binary_op(set.symmetric_difference_update)
    del _make_binary_op
    def __rsub__(self, other):
        new = MySet(other)
        new -= self
        return new

Это просто перезапишет все методы версиями, которые возвращают ваш собственный тип.(Существует множество методов!)

Возможно, для вашего приложения вы можете избежать перезаписи copy() и придерживаться методов на месте.

0 голосов
/ 03 ноября 2011

В качестве ответа на ответ Свена, вот универсальное упаковочное решение, которое заботится обо всех нестандартных методах. Идея состоит в том, чтобы перехватить первый поиск, вызванный вызовом метода, и установить метод-обертку, который выполняет преобразование типа. При последующих поисках обертка возвращается напрямую.

Предостережения:

1) Это более волшебная хитрость, чем мне нравится в моем коде.

2) Мне все еще нужно было обернуть специальные методы (__and__ и т. Д.) Вручную, потому что их поиск обходит __getattribute__

import types

class MySet(set):

    def __getattribute__(self, name):
        attr = super(MySet, self).__getattribute__(name)
        if isinstance(attr, types.BuiltinMethodType):
            def wrapper(self, *args, **kwargs):
                result = attr(self, *args, **kwargs)
                if isinstance(result, set):
                    return MySet(result)
                else:
                    return result
            setattr(MySet, name, wrapper)
            return wrapper
        return attr
0 голосов
/ 03 ноября 2011

Возможно, метакласс, который сделает всю эту обертку, будет проще:

class Perpetuate(type):
    def __new__(metacls, cls_name, cls_bases, cls_dict):
        if len(cls_bases) > 1:
            raise TypeError("multiple bases not allowed")
        result_class = type.__new__(metacls, cls_name, cls_bases, cls_dict)
        base_class = cls_bases[0]
        known_attr = set()
        for attr in cls_dict.keys():
            known_attr.add(attr)
        for attr in base_class.__dict__.keys():
            if attr in ('__new__'):
                continue
            code = getattr(base_class, attr)
            if callable(code) and attr not in known_attr:
                setattr(result_class, attr, metacls._wrap(base_class, code))
            elif attr not in known_attr:
                setattr(result_class, attr, code)
        return result_class
    @staticmethod
    def _wrap(base, code):
        def wrapper(*args, **kwargs):
            if args:
                cls = args[0]
            result = code(*args, **kwargs)
            if type(result) == base:
                return cls.__class__(result)
            elif isinstance(result, (tuple, list, set)):
                new_result = []
                for partial in result:
                    if type(partial) == base:
                        new_result.append(cls.__class__(partial))
                    else:
                        new_result.append(partial)
                result = result.__class__(new_result)
            elif isinstance(result, dict):
                for key in result:
                    value = result[key]
                    if type(value) == base:
                        result[key] = cls.__class__(value)
            return result
        wrapper.__name__ = code.__name__
        wrapper.__doc__ = code.__doc__
        return wrapper

class MySet(set, metaclass=Perpetuate):
    pass

s1 = MySet([1, 2, 3, 4, 5])

s2 = MySet([1, 2, 3, 6, 7])

print(s1.union(s2))
print(type(s1.union(s2)))

print(s1.intersection(s2))
print(type(s1.intersection(s2)))

print(s1.difference(s2))
print(type(s1.difference(s2)))
...