当实现一个像集合一样工作的类时,可以从
collections.MutableSet
继承,如果您实现了新类所需的方法,这将为新类提供多个 mixin 方法。 (换句话说,集合中的某些方法可以用其他方法来实现。为了让您免于这种无聊,collections.MutableSet
和朋友只包含这些实现。)
文档说抽象方法是:
、__contains__
、__iter__
、__len__
、add
discard
mixin 方法是
继承的
方法和Set
、clear
、pop
、remove
、__ior__
、__iand__
和__ixor__
__isub__
(并且,需要明确的是
update
不是“继承的 Set
方法的一部分,Set
的 mixin 方法是:
、__le__
、__lt__
、__eq__
、__ne__
、__gt__
、__ge__
、__and__
、__or__
、__sub__
和__xor__
isdisjoint
》
然而,
Set
指的是不可变集合,自然不会有update
。)
为什么
update
不在这些方法中? 我觉得令人惊讶 - 甚至不直观 - set
包含此方法,但 collections.Set
不包含。例如,它会导致以下情况:
In [12]: my_set
Out[12]: <ms.MySet at 0x7f947819a5d0>
In [13]: s
Out[13]: set()
In [14]: isinstance(my_set, collections.MutableSet)
Out[14]: True
In [15]: isinstance(s, collections.MutableSet)
Out[15]: True
In [16]: s.update
Out[16]: <function update>
In [17]: my_set.update
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-17-9ed968a9eb18> in <module>()
----> 1 my_set.update
AttributeError: 'MySet' object has no attribute 'update'
也许奇怪的是,
MutableMapping
确实赋予了update
方法,而MutableSet
则没有。 AFAICT,源代码没有提到任何原因。
MutableSet 的 API 是由 Guido van Rossum 设计的。他的提案在PEP 3119 的 for Sets 部分中有阐述。他没有详细说明,具体指出:
“此类还定义了计算并集的具体运算符, 分别为交集、对称差和非对称差 __or__、__and__、__xor__ 和 __sub__"...
“这也 支持就地变异操作 |=、&=、^=、-=。这些都是 具体方法,其右操作数可以是任意 Iterable, 除了 &= 之外,其右操作数必须是 Container。这个 ABC 确实 不提供内置具体集上存在的命名方法 执行(几乎)相同操作的类型。”这里没有错误或疏忽;相反,关于你是否喜欢 Guido 的设计存在一个意见问题。
Python 的
Zen 有话要说:
update = Set.__ior__
将您自己的 update()
方法添加到具体类中非常简单。
您提出的错误报告,如下所述,Set Abstract Base Class 使用运算符,而不是命名方法。
MutableSet
抽象基类编写了 OrderedSet
的配方(参见底部的代码块)。但他不使用 update 方法。相反,他使用 update 方法调用的
|=
运算符。我不知道您的错误报告是否会受到关注,因为它可能会破坏仅期望当前实现的现有代码。但是,您可以编写一个抽象基类,该基类确实需要您希望包含的方法:
import abc
import collections
class MyMutableSet(collections.MutableSet):
@abc.abstractmethod
def update(self, other):
raise NotImplementedError
MyMutableSet.register(set)
然后进行以下工作:
>>> isinstance(set('abc'), MyMutableSet)
True
如果我们尝试对新的抽象基类进行子类化(请参阅下面的食谱)而不是MutableSet
:
>>> s = OrderedSet()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class OrderedSet with abstract methods update
所以我们看到,如果我们要求用户子类化我们定制的抽象基类,我们可以通过这种方式要求 update
方法。这确实强调了这样一个事实:如果您想进行测试,您应该小心地只期望您正在使用的抽象基类实现的方法,并且不要假设您拥有内置方法中的每个方法(在这种情况下,
set
)。
set
被注册为 MutableSet 的子类,而不是相反。在 ABC 中实施更新
__ior__
:
def update(self, other):
self |= other
执行此操作不应破坏预先存在的代码。但如果您打算这样做,您也可以实现所有其他方法。
import collections
# class OrderedSet(collections.MutableSet):
class OrderedSet(MyMutableSet):
def __init__(self, iterable=None):
self.end = end = []
end += [None, end, end] # sentinel node for doubly linked list
self.map = {} # key --> [key, prev, next]
if iterable is not None:
self |= iterable
def __len__(self):
return len(self.map)
def __contains__(self, key):
return key in self.map
def add(self, key):
if key not in self.map:
end = self.end
curr = end[1]
curr[2] = end[1] = self.map[key] = [key, curr, end]
def discard(self, key):
if key in self.map:
key, prev, next = self.map.pop(key)
prev[2] = next
next[1] = prev
def __iter__(self):
end = self.end
curr = end[2]
while curr is not end:
yield curr[0]
curr = curr[2]
def __reversed__(self):
end = self.end
curr = end[1]
while curr is not end:
yield curr[0]
curr = curr[1]
def pop(self, last=True):
if not self:
raise KeyError('set is empty')
key = self.end[1][0] if last else self.end[2][0]
self.discard(key)
return key
def __repr__(self):
if not self:
return '%s()' % (self.__class__.__name__,)
return '%s(%r)' % (self.__class__.__name__, list(self))
def __eq__(self, other):
if isinstance(other, OrderedSet):
return len(self) == len(other) and list(self) == list(other)
return set(self) == set(other)
if __name__ == '__main__':
s = OrderedSet('abracadaba')
t = OrderedSet('simsalabim')
print(s | t)
print(s & t)
print(s - t)