对 Python set 类进行子类化(添加新的实例变量)的正确(或最佳)方法是什么?

发布于 2024-07-17 20:35:59 字数 873 浏览 4 评论 0原文

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

使用旧的 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')

但是使用内置的 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')

:)

I'm implementing an object that is almost identical to a set, but requires an extra instance variable, so I am subclassing the built-in set object. What is the best way to make sure that the value of this variable is copied when one of my objects is copied?

Using the old sets module, the following code worked perfectly:

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')

but this does not work using the built-in set module.

The only solution that I can see is to override every single method that returns a copied set object... in which case I might as well not bother subclassing the set object. Surely there is a standard way to do this?

(To clarify, the following code does not work (the assertion fails):

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')

)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

不再让梦枯萎 2024-07-24 20:35:59

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

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__',
])

本质上与您在自己的答案中所做的事情相同,但位置较少。 如果您也想对列表和字典执行相同的操作,那么放入元类也很容易。

My favorite way to wrap methods of a built-in collection:

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__',
])

Essentially the same thing you're doing in your own answer, but with fewer loc. It's also easy to put in a metaclass if you want to do the same thing with lists and dicts as well.

青芜 2024-07-24 20:35:59

我认为推荐的方法不是直接从内置 set 子类化,而是使用 抽象基类 Set 位于 collections.abc

使用 ABC Set 为您提供了一些免费的混合方法,因此您可以通过仅定义 __contains__()__len__()来获得最小的 Set 类>__iter__()。 如果您想要一些更好的设置方法,例如 intersection()difference(),您可能必须包装它们。

这是我的尝试(这个恰好是类似 freezeset 的,但您可以从 MutableSet 继承来获取可变版本):

from collections.abc 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=None):
        selfobj = super(CustomSet, cls).__new__(CustomSet)
        selfobj._set = frozenset() if iterable is None else 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)

I think that the recommended way to do this is not to subclass directly from the built-in set, but rather to make use of the Abstract Base Class Set available in collections.abc.

Using the ABC Set gives you some methods for free as a mix-in so you can have a minimal Set class by defining only __contains__(), __len__() and __iter__(). If you want some of the nicer set methods like intersection() and difference(), you probably do have to wrap them.

Here's my attempt (this one happens to be a frozenset-like, but you can inherit from MutableSet to get a mutable version):

from collections.abc 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=None):
        selfobj = super(CustomSet, cls).__new__(CustomSet)
        selfobj._set = frozenset() if iterable is None else 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)
风吹雨成花 2024-07-24 20:35:59

遗憾的是,set 不遵循规则,并且不会调用 __new__ 来创建新的 set 对象,即使它们保留了类型。 这显然是 Python 中的一个错误(问题 #1721812,在 2.x 中不会修复)顺序)。 如果不调用创建 X 对象的 type 对象,则永远无法获取类型 X 的对象! 如果set.__or__不打算调用__new__,它就必须返回set对象而不是子类对象。

但实际上,注意到上面 nosklo 的帖子,你原来的行为没有任何意义。 Set.__or__ 运算符不应该重用任何一个源对象来构造其结果,它应该创建一个新的对象,在这种情况下,它的 foo 应该是 <代码>“默认”!

因此,实际上,任何执行此操作的人都应该必须重载这些运算符,以便他们知道使用了 foo 的哪个副本。 如果它不依赖于组合的 Foosets,您可以将其设置为默认类,在这种情况下它将受到尊重,因为新对象认为它是子类类型。

我的意思是,如果你这样做的话,你的例子就会起作用:

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

f = Fooset([1,2,5])
assert (f|f).foo == 'default'

Sadly, set does not follow the rules and __new__ is not called to make new set objects, even though they keep the type. This is clearly a bug in Python (issue #1721812, which will not be fixed in the 2.x sequence). You should never be able to get an object of type X without calling the type object that creates X objects! If set.__or__ is not going to call __new__ it is formally obligated to return set objects instead of subclass objects.

But actually, noting the post by nosklo above, your original behavior does not make any sense. The Set.__or__ operator should not be reusing either of the source objects to construct its result, it should be whipping up a new one, in which case its foo should be "default"!

So, practically, anyone doing this should have to overload those operators so that they would know which copy of foo gets used. If it is not dependent on the Foosets being combined, you can make it a class default, in which case it will get honored, because the new object thinks it is of the subclass type.

What I mean is, your example would work, sort of, if you did this:

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

f = Fooset([1,2,5])
assert (f|f).foo == 'default'
怀里藏娇 2024-07-24 20:35:59

<代码>设置1 | set2 是一个不会修改现有 set 的操作,而是返回一个新的 set。 新的被创建并返回。 没有办法让它自动将任意属性从一个或两个 set 复制到新创建的 set,而无需自定义 |通过定义__or__方法自己操作

class MySet(set):
    def __init__(self, *args, **kwds):
        super(MySet, self).__init__(*args, **kwds)
        self.foo = 'nothing'
    def __or__(self, other):
        result = super(MySet, self).__or__(other)
        result.foo = self.foo + "|" + other.foo
        return result

r = MySet('abc')
r.foo = 'bar'
s = MySet('cde')
s.foo = 'baz'

t = r | s

print r, s, t
print r.foo, s.foo, t.foo

印刷:

MySet(['a', 'c', 'b']) MySet(['c', 'e', 'd']) MySet(['a', 'c', 'b', 'e', 'd'])
bar baz bar|baz

set1 | set2 is an operation that won't modify either existing set, but return a new set instead. The new set is created and returned. There is no way to make it automatically copy arbritary attributes from one or both of the sets to the newly created set, without customizing the | operator yourself by defining the __or__ method.

class MySet(set):
    def __init__(self, *args, **kwds):
        super(MySet, self).__init__(*args, **kwds)
        self.foo = 'nothing'
    def __or__(self, other):
        result = super(MySet, self).__or__(other)
        result.foo = self.foo + "|" + other.foo
        return result

r = MySet('abc')
r.foo = 'bar'
s = MySet('cde')
s.foo = 'baz'

t = r | s

print r, s, t
print r.foo, s.foo, t.foo

Prints:

MySet(['a', 'c', 'b']) MySet(['c', 'e', 'd']) MySet(['a', 'c', 'b', 'e', 'd'])
bar baz bar|baz
我的鱼塘能养鲲 2024-07-24 20:35:59

看起来 set 绕过了 c 代码。 然而,您将结束 Fooset 的实例,它只是没有机会复制该字段。

除了覆盖返回新集合的方法之外,我不确定在这种情况下您可以做太多事情。 Set 显然是为了一定的速度而构建的,c 中的很多工作也是如此。

It looks like set bypasses __init__ in the c code. However you will end an instance of Fooset, it just won't have had a chance to copy the field.

Apart from overriding the methods that return new sets I'm not sure you can do too much in this case. Set is clearly built for a certain amount of speed, so does a lot of work in c.

梦言归人 2024-07-24 20:35:59

我试图回答阅读它的问题:“如何使“集合”运算符的返回值成为我的集合子类的类型。忽略给定类的详细信息以及示例是否我从我自己的问题来到这里,如果我的阅读正确的话,

这个答案与其他一些答案不同,如下所示:

  • 给定的类(子类)只能通过添加装饰器来更改。
  • 因此,它足够通用,无需关心给定类的细节 (hasattr(s, 'foo'))
  • 每个类(当它被装饰时)支付一次额外成本,而不是每个实例
  • 给定示例的唯一问题,特定于“集合”的是方法列表,可以轻松定义它,
  • 假设基类不是抽象的,可以复制构造自身(否则需要实现 __init__ 方法,该方法从 的实例复制。基类)

库代码,可以放在项目或模块中的任何位置:

class Wrapfuncs:
  def __init__(self, *funcs):
    self._funcs = funcs

  def __call__(self, cls):
    def _wrap_method(method_name):
      def method(*args, **kwargs):
          result = getattr(cls.__base__, method_name)(*args, **kwargs)
          return cls(result)
      return method

    for func in self._funcs:
      setattr(cls, func, _wrap_method(func))
    return cls

要将其与集合一起使用,我们需要返回一个新实例的方法列表:

returning_ops_funcs = ['difference', 'symmetric_difference', '__rsub__', '__or__', '__ior__', '__rxor__', '__iand__', '__ror__', '__xor__', '__sub__', 'intersection', 'union', '__ixor__', '__and__', '__isub__', 'copy']

并且我们可以使用它与我们的类:

@Wrapfuncs(*returning_ops_funcs)
class MySet(set):
  pass

我不会详细说明该类的特殊之处。

我已经用以下几行测试了代码:

s1 = MySet([1, 2, 3])
s2 = MySet([2, 3, 4])
s3 = MySet([3, 4, 5])

print(s1&s2)
print(s1.intersection(s2))
print(s1 and s2)
print(s1|s2)
print(s1.union(s2))
print(s1|s2|s3)
print(s1.union(s2, s3))
print(s1 or s2)
print(s1-s2)
print(s1.difference(s2))
print(s1^s2)
print(s1.symmetric_difference(s2))

print(s1 & set(s2))
print(set(s1) & s2)

print(s1.copy())

其中 print:

MySet({2, 3})
MySet({2, 3})
MySet({2, 3, 4})
MySet({1, 2, 3, 4})
MySet({1, 2, 3, 4})
MySet({1, 2, 3, 4, 5})
MySet({1, 2, 3, 4, 5})
MySet({1, 2, 3})
MySet({1})
MySet({1})
MySet({1, 4})
MySet({1, 4})
MySet({2, 3})
{2, 3}
MySet({1, 2, 3})

有一种情况,结果不是最佳的。 在这种情况下,运算符与类的实例一起使用作为右侧操作数,并且将内置“集合”的实例作为第一个操作数。 我不喜欢这个,但我相信这个问题对于我见过的所有提议的解决方案都是常见的。

我还考虑过提供一个使用 collections.abc.Set 的示例。
虽然可以这样做:

from collections.abc import Set, Hashable
@Wrapfuncs(*returning_ops_funcs)
class MySet(set, Set):
  pass

我不确定它是否具有 @bjmc 所想到的好处,或者它“免费”为您提供的“一些方法”是什么。
该解决方案的目标是使用基类来完成工作并返回子类的实例。 使用成员对象来完成工作的解决方案可能会以类似的方式生成。

I am trying to answer the questions reading it as: "How can I make the return values of the operators of "set" to be of the type of my subclass of set. Ignoring the details of the given class and whether or not the example is broken to begin with. I came here from my own question which would be a duplicate, if my reading is correct.

This answer differs from some of the other answers as follows:

  • The given class (subclass) gets changed only by adding a decorator
  • therefore is general enough to not care about details of the given class (hasattr(s, 'foo'))
  • The additional cost is paid once per class (when it's decorated), not for every instance.
  • The only matter of the given example, that's specific to the "set" is the list of methods, which can be defined easily.
  • Assumes, that the base class is NOT abstract and can be copy constructed itself (otherwise an __init__method needs to be implemented, that copies from an instance of the base class)

The library code, which can be put anywhere in the project or a module:

class Wrapfuncs:
  def __init__(self, *funcs):
    self._funcs = funcs

  def __call__(self, cls):
    def _wrap_method(method_name):
      def method(*args, **kwargs):
          result = getattr(cls.__base__, method_name)(*args, **kwargs)
          return cls(result)
      return method

    for func in self._funcs:
      setattr(cls, func, _wrap_method(func))
    return cls

To use it with a set, we need the list of methods, that return a new instance:

returning_ops_funcs = ['difference', 'symmetric_difference', '__rsub__', '__or__', '__ior__', '__rxor__', '__iand__', '__ror__', '__xor__', '__sub__', 'intersection', 'union', '__ixor__', '__and__', '__isub__', 'copy']

and we can use it with our class:

@Wrapfuncs(*returning_ops_funcs)
class MySet(set):
  pass

I am sparing the details of what could be special about this class.

I have tested the code with the following lines:

s1 = MySet([1, 2, 3])
s2 = MySet([2, 3, 4])
s3 = MySet([3, 4, 5])

print(s1&s2)
print(s1.intersection(s2))
print(s1 and s2)
print(s1|s2)
print(s1.union(s2))
print(s1|s2|s3)
print(s1.union(s2, s3))
print(s1 or s2)
print(s1-s2)
print(s1.difference(s2))
print(s1^s2)
print(s1.symmetric_difference(s2))

print(s1 & set(s2))
print(set(s1) & s2)

print(s1.copy())

which print:

MySet({2, 3})
MySet({2, 3})
MySet({2, 3, 4})
MySet({1, 2, 3, 4})
MySet({1, 2, 3, 4})
MySet({1, 2, 3, 4, 5})
MySet({1, 2, 3, 4, 5})
MySet({1, 2, 3})
MySet({1})
MySet({1})
MySet({1, 4})
MySet({1, 4})
MySet({2, 3})
{2, 3}
MySet({1, 2, 3})

There is one case, in which the result is not optimal. This is, where the operator is used with an instance of the class as right hand operand and an instance of the builtin 'set' as first. I don't like this, but I believe this problem is common to all proposed solutions I have seen.

I have also thought of providing an example, where the collections.abc.Set is used.
While it could be done like this:

from collections.abc import Set, Hashable
@Wrapfuncs(*returning_ops_funcs)
class MySet(set, Set):
  pass

I am not sure, whether it comes with the benefits, that @bjmc had in mind, or what the "some methods" are, that it gives you "for free".
This solution is targeted at using a base class to do the work and return instances of the subclass. A solution, that uses a member object to do the work could probably be generated in a similar way.

烟织青萝梦 2024-07-24 20:35:59

使用装饰器概括 Matthew Marshall 响应而不添加运行时检查,这会影响性能:

>>>from functools import wraps
>>>
>>> def builtinSubclass( *methods ):
...    def decorator( cls ):
...       for m in methods:
...          if cls.__dict__.get(m):
...             continue
...          def closure():
...             wrapped = getattr(set, m)
...             @wraps( wrapped )
...             def wrapper(self, *args, **kwargs):
...                print(wrapped, m)
...                return cls( wrapped( self, *args, **kwargs ) )
...             return wrapper
...          print(f"replacing {m}")
...          setattr( cls, m, closure() )
...       return cls
...    return decorator
...
>>>
>>> setSubclass = builtinSubclass('__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__')
>>>
>>> @setSubclass
... class S(set):
...    pass
...
replacing __and__
replacing __or__
replacing intersection
>>> type(S([1]) | S([2]))
intersection
<class '__main__.S'>

干净的代码:

from functools import wraps

def builtinSubclass( *methods ):
   def decorator( cls ):
      for m in methods:
         if cls.__dict__.get(m):
            continue
         def makeWrapper():
            wrapped = getattr(set, m)
            @wraps( wrapped )
            def wrapper(self, *args, **kwargs):
               return cls(wrapped(self, *args, **kwargs))
            return wrapper
         setattr( cls, m, makeWrapper() )
      return cls
   return decorator

setSubclass = builtinSubclass('__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__')

Generalizing Matthew Marshall response using a decorator without adding runtime checking, which would impact performance:

>>>from functools import wraps
>>>
>>> def builtinSubclass( *methods ):
...    def decorator( cls ):
...       for m in methods:
...          if cls.__dict__.get(m):
...             continue
...          def closure():
...             wrapped = getattr(set, m)
...             @wraps( wrapped )
...             def wrapper(self, *args, **kwargs):
...                print(wrapped, m)
...                return cls( wrapped( self, *args, **kwargs ) )
...             return wrapper
...          print(f"replacing {m}")
...          setattr( cls, m, closure() )
...       return cls
...    return decorator
...
>>>
>>> setSubclass = builtinSubclass('__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__')
>>>
>>> @setSubclass
... class S(set):
...    pass
...
replacing __and__
replacing __or__
replacing intersection
>>> type(S([1]) | S([2]))
intersection
<class '__main__.S'>

clean code:

from functools import wraps

def builtinSubclass( *methods ):
   def decorator( cls ):
      for m in methods:
         if cls.__dict__.get(m):
            continue
         def makeWrapper():
            wrapped = getattr(set, m)
            @wraps( wrapped )
            def wrapper(self, *args, **kwargs):
               return cls(wrapped(self, *args, **kwargs))
            return wrapper
         setattr( cls, m, makeWrapper() )
      return cls
   return decorator

setSubclass = builtinSubclass('__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__')
吃不饱 2024-07-24 20:35:59

假设其他答案是正确的,并且重写所有方法是执行此操作的唯一方法,这是我尝试采用一种适度优雅的方法来执行此操作。 如果添加更多的实例变量,只需要改变一段代码。 不幸的是,如果将新的二元运算符添加到集合对象中,则此代码将中断,但我认为没有办法避免这种情况。 欢迎评论!

def foocopy(f):
    def cf(self, new):
        r = f(self, new)
        r.foo = self.foo
        return r
    return cf

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

    def copy(self):
        x = set.copy(self)
        x.foo = self.foo
        return x

    @foocopy
    def __and__(self, x):
        return set.__and__(self, x)

    @foocopy
    def __or__(self, x):
        return set.__or__(self, x)

    @foocopy
    def __rand__(self, x):
        return set.__rand__(self, x)

    @foocopy
    def __ror__(self, x):
        return set.__ror__(self, x)

    @foocopy
    def __rsub__(self, x):
        return set.__rsub__(self, x)

    @foocopy
    def __rxor__(self, x):
        return set.__rxor__(self, x)

    @foocopy
    def __sub__(self, x):
        return set.__sub__(self, x)

    @foocopy
    def __xor__(self, x):
        return set.__xor__(self, x)

    @foocopy
    def difference(self, x):
        return set.difference(self, x)

    @foocopy
    def intersection(self, x):
        return set.intersection(self, x)

    @foocopy
    def symmetric_difference(self, x):
        return set.symmetric_difference(self, x)

    @foocopy
    def union(self, x):
        return set.union(self, x)


f = Fooset([1,2,4])
f.foo = 'bar'
assert( (f | f).foo == 'bar')

Assuming the other answers are correct, and overriding all the methods is the only way to do this, here's my attempt at a moderately elegant way of doing this. If more instance variables are added, only one piece of code needs to change. Unfortunately if a new binary operator is added to the set object, this code will break, but I don't think there's a way to avoid that. Comments welcome!

def foocopy(f):
    def cf(self, new):
        r = f(self, new)
        r.foo = self.foo
        return r
    return cf

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

    def copy(self):
        x = set.copy(self)
        x.foo = self.foo
        return x

    @foocopy
    def __and__(self, x):
        return set.__and__(self, x)

    @foocopy
    def __or__(self, x):
        return set.__or__(self, x)

    @foocopy
    def __rand__(self, x):
        return set.__rand__(self, x)

    @foocopy
    def __ror__(self, x):
        return set.__ror__(self, x)

    @foocopy
    def __rsub__(self, x):
        return set.__rsub__(self, x)

    @foocopy
    def __rxor__(self, x):
        return set.__rxor__(self, x)

    @foocopy
    def __sub__(self, x):
        return set.__sub__(self, x)

    @foocopy
    def __xor__(self, x):
        return set.__xor__(self, x)

    @foocopy
    def difference(self, x):
        return set.difference(self, x)

    @foocopy
    def intersection(self, x):
        return set.intersection(self, x)

    @foocopy
    def symmetric_difference(self, x):
        return set.symmetric_difference(self, x)

    @foocopy
    def union(self, x):
        return set.union(self, x)


f = Fooset([1,2,4])
f.foo = 'bar'
assert( (f | f).foo == 'bar')
姜生凉生 2024-07-24 20:35:59

对我来说,在 Win32 上使用 Python 2.5.2 可以完美地工作。 使用您的类定义和以下测试:

f = Fooset([1,2,4])
s = sets.Set((5,6,7))
print f, f.foo
f.foo = 'bar'
print f, f.foo
g = f | s
print g, g.foo
assert( (f | f).foo == 'bar')

我得到这个输出,这是我所期望的:

Fooset([1, 2, 4]) default
Fooset([1, 2, 4]) bar
Fooset([1, 2, 4, 5, 6, 7]) bar

For me this works perfectly using Python 2.5.2 on Win32. Using you class definition and the following test:

f = Fooset([1,2,4])
s = sets.Set((5,6,7))
print f, f.foo
f.foo = 'bar'
print f, f.foo
g = f | s
print g, g.foo
assert( (f | f).foo == 'bar')

I get this output, which is what I expect:

Fooset([1, 2, 4]) default
Fooset([1, 2, 4]) bar
Fooset([1, 2, 4, 5, 6, 7]) bar
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文