【问题标题】:Creating an Ordered Counter创建有序计数器
【发布时间】:2016-05-28 12:38:04
【问题描述】:

我一直在阅读super() 的工作原理。我遇到了this recipe,它演示了如何创建有序计数器:

from collections import Counter, OrderedDict

class OrderedCounter(Counter, OrderedDict):
     'Counter that remembers the order elements are first seen'
     def __repr__(self):
         return '%s(%r)' % (self.__class__.__name__,
                            OrderedDict(self))
     def __reduce__(self):
         return self.__class__, (OrderedDict(self),)

例如:

oc = OrderedCounter('adddddbracadabra')

print(oc)

OrderedCounter(OrderedDict([('a', 5), ('d', 6), ('b', 2), ('r', 2), ('c', 1)]))

有人能解释一下这是如何神奇地工作的吗?

这也出现在Python documentation。

【问题讨论】:

  • 你不明白的地方能再具体一点吗?
  • 我看到该类继承自 Counter 和 OrderedDict,但我看不出这些类是如何组合生成 OrderedCounter...我试图了解它的工作原理 - 因为例如,Counter 有什么特别之处吗?这是否有助于澄清我的问题?
  • 基本上,作为一般的经验法则,避免多重继承(你的同事不会这样杀死你)......(粗略的 mixin 有点不同)
  • 您在上面发布的示例缺少您链接到的文档中存在的 init 方法,我不确定,但我希望这是让它发挥作用很重要。您可以看到您覆盖了两个方法。任何未覆盖的方法都将从 Count 继承,如果不存在,则从 OrderedDict 继承。如果任何方法在它们的超类之一上,它就会变得有点棘手。正如@JoranBeasley 所说的“避免多重继承” - 复杂的继承树是你想要避免它的原因(它会导致意想不到的结果)。
  • 是的,这一切都与 dict 方法 Counter 实际覆盖/自定义有关。我不知道究竟是哪一个,但我怀疑Counter 没有改变__setitem__ 和__iter__,所以你的新课程从OrderedDict 得到这些,这足以给你有序的行为。跨度>

标签: python python-3.x dictionary counter ordereddictionary


【解决方案1】:

我认为当输入单词时我们需要在类中表示repr和reduce这些方法。

没有repr 和reduce:

from collections import Counter, OrderedDict
class OrderedCounter(Counter, OrderedDict):
    pass

oc = OrderedCounter(['apple', 'banana', 'cherry', 'mango', 'apple', 'pie', 'mango'])
print(oc)

输出:

OrderedCounter({'apple': 2, 'mango': 2, 'banana': 1, 'cherry': 1, 'pie': 1})

上例中的顺序没有保留。

使用repr 和reduce:

from collections import Counter, OrderedDict
class OrderedCounter(Counter, OrderedDict):
    'Counter that remembers the order elements are first encountered'
    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, OrderedDict(self))

    def __reduce__(self):
        return self.__class__, (OrderedDict(self),)
oc = OrderedCounter(['apple', 'banana', 'cherry', 'mango', 'apple', 'pie', 'mango'])
print(oc)

输出:

OrderedCounter(OrderedDict([('apple', 2), ('banana', 1), ('cherry', 1), ('mango', 2), ('pie', 1)]))

【讨论】:

    【解决方案2】:

    OrderedCounter 在OrderedDict documentation 中作为示例给出,无需重写任何方法即可工作:

    class OrderedCounter(Counter, OrderedDict):
        pass
    

    当一个类方法被调用时,Python 必须找到正确的方法来执行。它有一个定义的顺序来搜索类层次结构,称为“方法解析顺序”或 mro。 mro存储在属性__mro__中:

    OrderedCounter.__mro__
    
    (<class '__main__.OrderedCounter'>, <class 'collections.Counter'>, <class 'collections.OrderedDict'>, <class 'dict'>, <class 'object'>)
    

    当 OrderedDict 的实例调用__setitem__() 时,它会按顺序搜索类:OrderedCounter、Counter、OrderedDict(找到它的位置)。所以像oc['a'] = 0 这样的语句最终会调用OrderedDict.__setitem__()。

    相比之下,__getitem__ 不会被 mro 中的任何子类覆盖,因此 count = oc['a'] 由 dict.__getitem__() 处理。

    oc = OrderedCounter()    
    oc['a'] = 1             # this call uses OrderedDict.__setitem__
    count = oc['a']         # this call uses dict.__getitem__
    

    像oc.update('foobar'). 这样的语句会出现一个更有趣的调用序列。首先,Counter.update() 被调用。 Counter.update() 的代码使用 self[elem],它变成了对 OrderedDict.__setitem__() 的调用。 that 的代码调用dict.__setitem__()。

    如果基类颠倒了,它就不再起作用了。因为 mro 不同,调用了错误的方法。

    class OrderedCounter(OrderedDict, Counter):   # <<<== doesn't work
        pass
    

    有关 mro 的更多信息可以在 Python 2.3 documentation 中找到。

    【讨论】:

      猜你喜欢
      • 2016-01-27
      • 2014-12-24
      • 1970-01-01
      • 2019-07-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多