为什么collections.MutableSet没有提供更新方法?

问题描述 投票:0回答:2

当实现一个像集合一样工作的类时,可以从

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,源代码没有提到任何原因。

python collections set mixins
2个回答
10
投票

MutableSet 的 API 是由 Guido van Rossum 设计的。他的提案在PEP 3119 的 for Sets 部分中有阐述。他没有详细说明,具体指出:

“此类还定义了计算并集的具体运算符, 分别为交集、对称差和非对称差 __or__、__and__、__xor__ 和 __sub__"

...

“这也 支持就地变异操作 |=、&=、^=、-=。这些都是 具体方法,其右操作数可以是任意 Iterable, 除了 &= 之外,其右操作数必须是 Container。这个 ABC 确实 不提供内置具体集上存在的命名方法 执行(几乎)相同操作的类型。”

这里没有错误或疏忽;相反,关于你是否喜欢 Guido 的设计存在一个意见问题。

Python 的

Zen 有话要说:

    应该有一种——最好只有一种——明显的方法来做到这一点。
  • 虽然这种方式一开始可能并不明显,除非你是荷兰人。
也就是说,抽象基类被设计为易于扩展。使用

update = Set.__ior__ 将您自己的 update()

 方法添加到具体类中非常简单。 


3
投票
更新:

Raymond Hettinger 本人回应了

您提出的错误报告,如下所述,Set Abstract Base Class 使用运算符,而不是命名方法。


原始回复:

Raymond Hettinger 基于

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)
    
© www.soinside.com 2019 - 2024. All rights reserved.