我正在实现一个几乎与set相同的对象,但需要一个额外的实例变量,所以我将内置的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')
但使用内置设置模块无效。
我能看到的唯一解决方案是覆盖返回复制的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')
)
答案 0 :(得分:16)
我最喜欢的方法来包装内置集合的方法:
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__',
])
基本上你在自己的答案中做了同样的事情,但是用较少的loc。如果你想用列表和dicts做同样的事情,也很容易放入元类。
答案 1 :(得分:6)
我认为建议的方法不是直接从内置set
继承子类,而是使用Abstract Base Class Set
中提供的collections。
使用ABC Set为您提供了一些免费的混合方法,因此您只需定义__contains__()
,__len__()
和__iter__()
即可获得最小的Set类。如果你想要一些更好的设置方法,如intersection()
和difference()
,你可能需要包装它们。
这是我的尝试(这个尝试类似于冻结,但您可以从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)
答案 2 :(得分:4)
遗憾的是,set不遵循规则,并且__new__
不会被调用来创建新的set
对象,即使它们保留了类型。这显然是Python中的一个错误(问题#1721812,不会在2.x序列中修复)。如果不调用创建X对象的type
对象,您永远不能获得X类型的对象!如果set.__or__
不打算调用__new__
,则正式有义务返回set
个对象而不是子类对象。
但实际上,注意上面 nosklo 的帖子,你的原始行为没有任何意义。 Set.__or__
运算符不应该重用任何一个源对象来构造它的结果,它应该是一个新的运算符,在这种情况下它的foo
应该是"default"
!
所以,实际上,任何这样做 的人都必须重载这些运算符,以便他们知道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'
答案 3 :(得分:2)
set1 | set2
是一项不会修改现有set
,但会返回新set
的操作。创建并返回新的set
。无法自动将set
中的一个或两个的arbritary属性复制到新创建的set
,而无法通过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
打印:
MySet(['a', 'c', 'b']) MySet(['c', 'e', 'd']) MySet(['a', 'c', 'b', 'e', 'd'])
bar baz bar|baz
答案 4 :(得分:2)
看起来在c code中设置绕过__init__
。但是,您将结束Fooset
的实例,它只是没有机会复制该字段。
除了重写返回新集合的方法之外,我不确定在这种情况下你可以做多少。 Set显然是以一定的速度构建的,所以在c中做了很多工作。
答案 5 :(得分:1)
我试图回答以下问题:“如何使“set”运算符的返回值属于我的 set 子类的类型。忽略给定类的详细信息以及是否不是这个例子一开始就被打破了。我是从我自己的问题来到这里的,如果我的阅读是正确的,这将是重复的。
这个答案与其他一些答案的不同之处如下:
库代码,可以放在项目或模块的任何地方:
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())
打印:
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})
有一种情况,结果不是最优的。这是,运算符与类的实例一起用作右手操作数,并将内置“set”的实例用作第一个。我不喜欢这个,但我相信这个问题在我见过的所有提议的解决方案中都很常见。
我也想过提供一个例子,其中使用了 collections.abc.Set。 虽然可以这样做:
from collections.abc import Set, Hashable
@Wrapfuncs(*returning_ops_funcs)
class MySet(set, Set):
pass
我不确定它是否带来了@bjmc 所考虑的好处,或者它“免费”为您提供的“某些方法”是什么。 此解决方案旨在使用基类来完成工作并返回子类的实例。使用成员对象来完成工作的解决方案可能会以类似的方式生成。
答案 6 :(得分:0)
假设其他答案是正确的,并且覆盖所有方法是唯一的方法,这是我尝试以一种适度优雅的方式做到这一点。如果添加了更多实例变量,则只需要更改一段代码。不幸的是,如果将新的二元运算符添加到set对象中,此代码将会中断,但我认为没有办法避免这种情况。欢迎评论!
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')
答案 7 :(得分:-2)
对我来说,这在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