![](/img/trans.png)
[英]What's the best way to check if class instance variable is set in Python?
[英]What is the correct (or best) way to subclass the Python set class, adding a new instance variable?
我正在實現一個與集合幾乎相同的對象,但需要一個額外的實例變量,所以我將內置的集合對象子類化。 確保在復制我的對象之一時復制此變量的值的最佳方法是什么?
使用舊的 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 對象的方法......在這種情況下,我最好不要打擾 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')
)
我最喜歡的包裝內置集合方法的方法:
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 較少。 如果您也想對列表和字典做同樣的事情,那么放入元類也很容易。
我認為推薦的方法不是直接從內置set
子類化,而是利用collections.abc 中可用的Abstract Base Class Set
。
使用 ABC Set 為您提供了一些免費的混合方法,因此您可以通過僅定義__contains__()
、 __len__()
和__iter__()
來獲得最小的 Set 類。 如果你想要一些更好的 set 方法,比如intersection()
和difference()
,你可能必須包裝它們。
這是我的嘗試(這恰好是一個frozenset-like,但您可以從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)
可悲的是, set 不遵循規則並且__new__
不會被調用來創建新的set
對象,即使它們保留了類型。 這顯然是 Python 中的一個錯誤(問題#1721812 ,不會在 2.x 序列中修復)。 如果不調用創建 X 對象的type
對象,就永遠不能獲得 X type
對象! 如果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'
set1 | set2
set1 | set2
是一個不會修改任何現有set
,而是返回一個新的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
看起來 set 繞過了c 代碼中的__init__
。 但是,您將結束Fooset
一個實例,它只是沒有機會復制該字段。
除了覆蓋返回新集合的方法之外,我不確定在這種情況下您可以做太多事情。 Set 顯然是為一定的速度而構建的,因此在 c 中做了很多工作。
我試圖回答閱讀它的問題:“如何使“集合”運算符的返回值成為集合子類的類型。忽略給定類的詳細信息以及示例是否一開始就壞了。如果我的閱讀正確,我是從我自己的問題來到這里的,這將是重復的。
這個答案與其他一些答案不同,如下所示:
庫代碼,可以放在項目或模塊的任何位置:
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 所考慮的好處,或者它“免費”為您提供的“某些方法”是什么。 此解決方案旨在使用基類來完成工作並返回子類的實例。 使用成員對象來完成工作的解決方案可能會以類似的方式生成。
假設其他答案是正確的,並且覆蓋所有方法是做到這一點的唯一方法,這是我嘗試以一種適度優雅的方式來做到這一點。 如果添加更多的實例變量,只需更改一段代碼。 不幸的是,如果將新的二元運算符添加到 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')
對我來說,在 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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.