2011-11-02 71 views
22

在继承内置类型时,我注意到了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中看到的行为,而不重新定义内置类型的每一种方法?

+0

Python的2只s1'的'类型是相关的'S2不是类型'。 – agf

+2

它与'False + False'为'0',而不是'False'(顺便说一句,'bool'是'int'的子类)的方式类似。 –

回答

11

这对于从Python 2.x迁移到3.x时不是内置类型的一般变化 - 例如,listint在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()并坚持就地的方法。

+0

对,Python 2在这里并不一致。如果你在Python 2中创建了一个'class MySet(set):pass',那么'print type(MySet()。copy())'给出了'',但是如果你创建了一个'class MyDict(dict):pass',然后'print type(MyDict()。copy())'给出''。 – Cito

+0

有一种方法可以在单个操作中处理至少非特殊的方法。我会回答我自己的问题来说明(我不能将代码放入注释中)。但是,我想要的还有更多的开销,所有特殊的方法都要一一处理。 – khinsen

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

一些注释:1.这将无法封装一个名为'e()'的方法,但它会包装'__getattribute __()',从而阻止将基本类型的对象存储在属性中。 2.这会造成严重的性能下降,尤其是对于检索属性。如果你在一个属性中存储一个列表,它将在每次访问时迭代。有更多的性能问题,可能太多指出。 –

+0

@SvenMarnach:为什么它不能包装'e()'? –

+0

因为名字'e',('__new __')'中的attr条件成立。诚然,这是一个便宜的,但在这个代码中有更多的晦涩的错误。 –

0

作为后续斯文的答案,这里是一个通用的包装解决方案,它负责所有非特殊的方法。这个想法是捕获来自方法调用的第一个查找,并安装一个进行类型转换的包装器方法。在随后的查找中,包装直接返回。

注意事项:

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