Подтвердить что ты не робот

Подклассификация встроенных типов в Python 2 и Python 3

При подклассификации встроенных типов я заметил довольно важное различие между 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, без переопределения каждого метода встроенных типов?

4b9b3361

Ответ 1

Это не общее изменение для встроенных типов при переходе с 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() и придерживаться методов на месте.

Ответ 2

Возможно, метаклас, чтобы сделать все, что вам удалось, облегчит вам:

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)))

Ответ 3

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

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

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