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