为什么collections.MutableSet没有提供update方法?
当你想要创建一个像集合(set)一样工作的类时,可以从 collections.MutableSet
这个类继承。这样,你的新类就会获得一些混合方法,只要你实现它们所需要的方法。(换句话说,集合的一些方法可以通过其他方法来实现。为了避免这种繁琐,collections.MutableSet
及其相关类已经包含了这些实现。)
文档中提到的抽象方法有:
__contains__
、__iter__
、__len__
、add
、discard
而混合方法包括:
继承自
Set
的方法,还有clear
、pop
、remove
、__ior__
、__iand__
、__ixor__
和__isub__
(为了明确说明,update
并不属于“继承自 Set
的方法”,Set
的混合方法有:
__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
没有。根据我所了解的,源代码 并没有提到任何原因。
2 个回答
更新:
Raymond Hettinger本人回应了你提出的错误报告,如下所述,集合抽象基类使用的是运算符,而不是命名的方法。
原始回应:
Raymond Hettinger写了一个关于有序集合的示例,这个示例是基于MutableSet
抽象基类的(请看底部的代码块)。但他没有使用更新方法。相反,他使用了|=
运算符,这个运算符是更新方法调用的。我不知道你的错误报告是否会得到重视,因为这可能会破坏一些只期望当前实现的已有代码。
不过,你可以写一个抽象基类,来期待你想要包含的方法:
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
的子类,而不是反过来。
在抽象基类中实现更新
如果你想在抽象基类中实现更新,因为它期望__ior__
:
def update(self, other):
self |= other
这样做不应该破坏已有的代码。不过,如果你要这样做,最好也实现其他所有的方法。
Raymond的示例,经过我们的调整:
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)
MutableSet的接口是由Guido van Rossum设计的。他在PEP 3119的集合部分中详细说明了他的提议。简单来说,他指出:
“这个类还定义了具体的操作符,用于计算并集、交集、对称差和非对称差,分别是__or__、__and__、__xor__和__sub__。”
...
“这也支持就地修改操作 |=、&=、^=、-=。这些是具体的方法,右侧的操作数可以是任意可迭代对象,除了&=,它的右侧操作数必须是一个容器。这个抽象基类没有提供内置具体集合类型中存在的命名方法,这些方法执行(几乎)相同的操作。”
这里并不是有bug或疏漏,而是关于你是否喜欢Guido的设计的个人看法。
Python之禅对此有一些看法:
- 应该有一个——最好只有一个——明显的方法来做到这一点。
- 虽然这个方法一开始可能不明显,除非你是荷兰人。
话虽如此,抽象基类的设计是为了便于扩展。你可以很简单地在你的具体类中添加自己的update()方法,方法是update = Set.__ior__
。