什么是子类化Python集合类的正确(或最佳)方法,添加新的实例变量?

rog*_*rog 13 python subclass instance-variables set

我正在实现一个几乎与集合相同的对象,但需要一个额外的实例变量,所以我是内置集合对象的子类.在复制其中一个对象时,确保复制此变量的值的最佳方法是什么?

使用旧的set模块,以下代码完美地运行:

import sets
class Fooset(sets.Set):
    def __init__(self, s = []):
        sets.Set.__init__(self, s)
        if isinstance(s, Fooset):
            self.foo = s.foo
        else:
            self.foo = 'default'
f = Fooset([1,2,4])
f.foo = 'bar'
assert( (f | f).foo == 'bar')
Run Code Online (Sandbox Code Playgroud)

但是使用内置的设置模块不起作用.

我能看到的唯一解决方案是覆盖返回复制的set对象的每个方法...在这种情况下,我可能不会打扰子类化set对象.当然有一种标准的方法可以做到这一点?

(为了澄清,下面的代码并不能正常工作(断言失败):

class Fooset(set):
    def __init__(self, s = []):
        set.__init__(self, s)
        if isinstance(s, Fooset):
            self.foo = s.foo
        else:
            self.foo = 'default'

f = Fooset([1,2,4])
f.foo = 'bar'
assert( (f | f).foo == 'bar')
Run Code Online (Sandbox Code Playgroud)

)

Mat*_*all 17

我最喜欢的包装内置集合方法的方法:

class Fooset(set):
    def __init__(self, s=(), foo=None):
        super(Fooset,self).__init__(s)
        if foo is None and hasattr(s, 'foo'):
            foo = s.foo
        self.foo = foo



    @classmethod
    def _wrap_methods(cls, names):
        def wrap_method_closure(name):
            def inner(self, *args):
                result = getattr(super(cls, self), name)(*args)
                if isinstance(result, set) and not hasattr(result, 'foo'):
                    result = cls(result, foo=self.foo)
                return result
            inner.fn_name = name
            setattr(cls, name, inner)
        for name in names:
            wrap_method_closure(name)

Fooset._wrap_methods(['__ror__', 'difference_update', '__isub__', 
    'symmetric_difference', '__rsub__', '__and__', '__rand__', 'intersection',
    'difference', '__iand__', 'union', '__ixor__', 
    'symmetric_difference_update', '__or__', 'copy', '__rxor__',
    'intersection_update', '__xor__', '__ior__', '__sub__',
])
Run Code Online (Sandbox Code Playgroud)

基本上你在自己的答案中做的事情是一样的,但是用较少的loc.如果你想用列表和dicts做同样的事情,也很容易放入元类.


bjm*_*jmc 7

我认为,推荐的方法不是直接从内置方法继承子类set,而是利用collections中可用的Abstract Base ClassSet

使用ABC套装免费送您一些方法作为一个mix-in,所以你可以只定义一个最小集合类__contains__()__len__()__iter__()。如果您想要一些更好的set方法,例如intersection()difference(),则可能必须包装它们。

这是我的尝试(该尝试恰好是一个Frozenset样的,但您可以从中继承MutableSet以获取可变版本):

from collections import Set, Hashable

class CustomSet(Set, Hashable):
    """An example of a custom frozenset-like object using
    Abstract Base Classes.
    """
    ___hash__ = Set._hash

    wrapped_methods = ('difference',
                       'intersection',
                       'symetric_difference',
                       'union',
                       'copy')

    def __repr__(self):
        return "CustomSet({0})".format(list(self._set))

    def __new__(cls, iterable):
        selfobj = super(CustomSet, cls).__new__(CustomSet)
        selfobj._set = frozenset(iterable)
        for method_name in cls.wrapped_methods:
            setattr(selfobj, method_name, cls._wrap_method(method_name, selfobj))
        return selfobj

    @classmethod
    def _wrap_method(cls, method_name, obj):
        def method(*args, **kwargs):
            result = getattr(obj._set, method_name)(*args, **kwargs)
            return CustomSet(result)
        return method

    def __getattr__(self, attr):
        """Make sure that we get things like issuperset() that aren't provided
        by the mix-in, but don't need to return a new set."""
        return getattr(self._set, attr)

    def __contains__(self, item):
        return item in self._set

    def __len__(self):
        return len(self._set)

    def __iter__(self):
        return iter(self._set)
Run Code Online (Sandbox Code Playgroud)