【问题标题】:Why does collections.MutableSet not bestow an update method?为什么 collections.MutableSet 不赋予更新方法?
【发布时间】:2014-09-18 14:48:08
【问题描述】:

当实现一个像集合一样工作的类时,可以从collections.MutableSet 继承,如果你实现了新类所需的方法,它将赋予新类多个 mixin 方法。 (换句话说,集合的某些方法可以用其他方法来实现。为了让您免于无聊,collections.MutableSet 和朋友只包含这些实现。)

The docs 说抽象方法是:

__contains____iter____len__adddiscard

混合方法是

继承Set方法和clearpopremove__ior____iand____ixor____isub__

(而且,要明确的是,update 不是“继承的Set 方法的一部分,Set 的混合方法是:

__le____lt____eq____ne____gt____ge____and____or____sub__isdisjoint和@p98 >

不过,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,source code 没有提到任何原因。

【问题讨论】:

  • 这是一个问题还是错误报告?
  • @AaronHall:哈!这是个好问题。它可能只是。我总是认为我的代码是问题所在,也许有人知道我不知道的事情。再看一遍,我不确定。 (如果有原因,错误报告可能会找出原因。)
  • 归档issue 22089
  • 加一个,但经过进一步思考,我认为您不会获得太大的吸引力。请参阅下面的答案。

标签: python collections set mixins


【解决方案1】:

更新:

Raymond Hettinger 本人回复the bug report you raised,如下所述,Set Abstract Base Class 使用操作符,而不是命名方法。


原始回复:

Raymond Hettinger 基于MutableSet 抽象基类编写了a recipe for an OrderedSet,(参见底部的代码块。)但他没有使用更新方法。相反,他使用更新方法调用的|= 运算符。我不知道您的错误报告是否会引起注意,因为它可能会破坏仅期望当前实现的预先存在的代码。

但是,您可以编写一个抽象基类,它确实期望方法包含更多您坚持要实现的方法:

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)

【讨论】:

  • 我在想update 将在ABC 上实现,实现(基本上)def update(self, iterable): for i in iterable: self.add(i);我不认为添加这样的定义会破坏任何东西(你没有它,现在你有,或者你用你自己的,可能更有效的版本覆盖它)。
  • @Thanatos 当然,让我补充一下我的答案。
【解决方案2】:

MutableSet 的 API 由 Guido van Rossum 设计。他的提议在PEP 3119's section on for Sets 中阐明。他没有详细说明,而是指出:

"这个类还定义了具体的算子来计算并集, 交点,对称和非对称差,分别 __or__、__and__、__xor__ 和 __sub__"

...

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

这里没有错误或疏忽;相反,对于您是否喜欢 Guido 的设计存在意见。

Python 之禅对此有话要说:

  • 应该有一种——最好只有一种——明显的方法。
  • 虽然这种方式一开始可能并不明显,除非您是荷兰人。

也就是说,抽象基类被设计为易于扩展。使用update = Set.__ior__ 将您自己的update() 方法添加到您的具体类是微不足道的。

【讨论】:

  • 嗯,通过 __operator__ 函数的实现来实现命名方法通常是不安全的,因为运算符方法可能(并且通常应该)返回 NotImplemented 而不是而不是在传递无效参数时引发异常。在这种情况下似乎没问题,因为MutableSet.__ior__ 似乎在传递任何不可迭代的东西时引发TypeError,但这(理论上)不是__ior__ 的最佳行为,因为它阻止了__ror__ 方法的定义在一些奇怪的不可迭代但可设置的类型中提供自己的实现。
  • 我是雷蒙德! “我们不配!我们不配!”
  • @Blckknght 像 __ior__ 这样的就地操作独立于 __or__ 和 __ror__。由于显而易见的原因,就地操作不会被逆转或需要使用 NotImplemented
  • 嗯,我在评论之前运行的测试表明如果__ior__返回NotImplemented,那么如果另一个对象的__ror__存在则将被调用(并将结果分配回到左侧变量的名称)。虽然没有原地反转版本(没有__iror__)。我不确定我是否了解需要 NotImplemented 的位置以及不需要的位置。我一直在尝试__op__ 内置对象的方法并在我期望NotImplemented 时遇到异常,即使使用运算符按预期反转。也许我应该问一个关于这个的问题......
猜你喜欢
  • 2017-01-20
  • 1970-01-01
  • 1970-01-01
  • 2013-05-22
  • 2020-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-10
相关资源
最近更新 更多