在对内置类型进行子类化时,我注意到内置类型方法的返回类型中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中的行为,而无需重新定义内置类型的每一种方法?
答案 0 :(得分:11)
从Python 2.x移动到3.x时,这不是对内置类型的一般更改 - list
和int
,例如,在2中具有相同的行为。 x和3.x.只更改了集合类型以使其与其他类型保持一致,如this bug tracker issue中所述。
我担心没有什么好方法可以让它表现得很旧。以下是我能够提出的一些代码:
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()
并坚持使用就地方法。
答案 1 :(得分:0)
也许一个元素类可以让你更轻松地完成这一切:
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)))
答案 2 :(得分:0)
作为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