在Python 2和Python 3中对内置类型进行子类化

时间:2011-11-02 15:48:01

标签: python python-3.x subclass built-in-types

在对内置类型进行子类化时,我注意到内置类型方法的返回类型中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 个答案:

答案 0 :(得分: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()并坚持使用就地方法。

答案 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