Python 如何实现一个有序的默认dict?

Python 如何实现一个有序的默认dict?,python,dictionary,Python,Dictionary,我想将collections中的OrderedDict()和defaultdict()合并到一个对象中,该对象应为有序的默认dict 这可能吗?以下内容(使用的是经过修改的版本)适用于我: from collections import OrderedDict, Callable class DefaultOrderedDict(OrderedDict): # Source: http://stackoverflow.com/a/6190500/562769 def __ini

我想将
collections
中的
OrderedDict()
defaultdict()
合并到一个对象中,该对象应为有序的默认
dict
这可能吗?

以下内容(使用的是经过修改的版本)适用于我:

from collections import OrderedDict, Callable

class DefaultOrderedDict(OrderedDict):
    # Source: http://stackoverflow.com/a/6190500/562769
    def __init__(self, default_factory=None, *a, **kw):
        if (default_factory is not None and
           not isinstance(default_factory, Callable)):
            raise TypeError('first argument must be callable')
        OrderedDict.__init__(self, *a, **kw)
        self.default_factory = default_factory

    def __getitem__(self, key):
        try:
            return OrderedDict.__getitem__(self, key)
        except KeyError:
            return self.__missing__(key)

    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError(key)
        self[key] = value = self.default_factory()
        return value

    def __reduce__(self):
        if self.default_factory is None:
            args = tuple()
        else:
            args = self.default_factory,
        return type(self), args, None, None, self.items()

    def copy(self):
        return self.__copy__()

    def __copy__(self):
        return type(self)(self.default_factory, self)

    def __deepcopy__(self, memo):
        import copy
        return type(self)(self.default_factory,
                          copy.deepcopy(self.items()))

    def __repr__(self):
        return 'OrderedDefaultDict(%s, %s)' % (self.default_factory,
                                               OrderedDict.__repr__(self))

如果您的用例像我的一样简单,并且您不一定要将
defaultorderedict
类实现的复杂性添加到代码中,那么可以考虑另一种解决方案

from collections import OrderedDict

keys = ['a', 'b', 'c']
items = [(key, None) for key in keys]
od = OrderedDict(items)
None
是我想要的默认值。)

请注意,如果您的需求之一是动态插入具有默认值的新键,则此解决方案将不起作用。简单性的权衡

2017年3月13日更新-我了解到这个用例有一个方便的功能。与上面相同,但您可以省略行
items=…
,只需:

od = OrderedDict.fromkeys(keys)
输出:

OrderedDict([('a', None), ('b', None), ('c', None)])
如果密钥是单个字符,则只需传递一个字符串:

OrderedDict.fromkeys('abc')
这与上面的两个示例具有相同的输出


您还可以将默认值作为第二个参数传递给
OrderedDict.fromkeys(…)

以下是另一种可能性,它是受Python 2.7.X和3.4.X的启发,在Python 2.7.X和3.4.X上测试的:

from collections import OrderedDict, defaultdict

class OrderedDefaultDict(OrderedDict, defaultdict):
    def __init__(self, default_factory=None, *args, **kwargs):
        #in python3 you can omit the args to super
        super(OrderedDefaultDict, self).__init__(*args, **kwargs)
        self.default_factory = default_factory
如果您查看该类的MRO(又名,
帮助(OrderedDefaultDict)
),您将看到:

class OrderedDefaultDict(collections.OrderedDict, collections.defaultdict)
 |  Method resolution order:
 |      OrderedDefaultDict
 |      collections.OrderedDict
 |      collections.defaultdict
 |      __builtin__.dict
 |      __builtin__.object

这意味着当初始化
OrderedDefaultDict
的实例时,它将遵从
OrderedDict
的init,但这一实例在调用
内置dict
之前将调用
默认dict
的方法,这正是我们想要的。

更简单的@zeekay回答是:

from collections import OrderedDict

class OrderedDefaultListDict(OrderedDict): #name according to default
    def __missing__(self, key):
        self[key] = value = [] #change to whatever default you want
        return value

如果您想要一个不需要类的简单解决方案,您可以使用或。如果您只从几个地方获取/设置,比如在循环中,您可以轻松地设置默认值

totals = collections.OrderedDict()

for i, x in some_generator():
    totals[i] = totals.get(i, 0) + x
使用
setdefault
的列表更容易:

agglomerate = collections.OrderedDict()

for i, x in some_generator():
    agglomerate.setdefault(i, []).append(x)

但是如果您多次使用它,可能最好像其他答案一样设置一个类。

我测试了默认dict,发现它也被排序了! 也许这只是巧合,但无论如何,您可以使用排序函数:

sorted(s.items())

我认为它更简单

一个基于@NickBread的简单而优雅的解决方案。 有一个稍微不同的API来设置工厂,但是好的默认值总是好的

class OrderedDefaultDict(OrderedDict):
    factory = list

    def __missing__(self, key):
        self[key] = value = self.factory()
        return value

另一个简单的方法是使用dictionary
get
方法

>>> from collections import OrderedDict
>>> d = OrderedDict()
>>> d['key'] = d.get('key', 0) + 1
>>> d['key'] = d.get('key', 0) + 1
>>> d
OrderedDict([('key', 2)])
>>> 

受此线索上其他答案的启发,您可以使用

from collections import OrderedDict

class OrderedDefaultDict(OrderedDict):
    def __missing__(self, key):
        value = OrderedDefaultDict()
        self[key] = value
        return value


我想知道在missing方法中初始化同一类的另一个对象是否有任何缺点

删除了我的答案,这个答案在思考过程中是类似的,但是动态设计的(因此需要实现各种其他功能)。@Neil G:您可能应该使用内置的
callable()
函数来测试
默认工厂
。使用
isinstance(默认工厂,可调用)
实际上要求它不仅具有可调用性--请参阅--这就是这里所需要的一切。@martineau:你说得对。我相信
callable
在Python3.1中被删除,然后在Python3.2中被恢复,并且我在进行此编辑时还没有升级。请随意更改。@Neil G:实际上,
callable()
首先在Python3.0中删除,然后在Python3.2中恢复。无论如何,如果你愿意的话,你可以考虑改变它(我喜欢我自己的答案。)我通常不想直接插手修改别人的答案,而是更喜欢像我在这里所做的那样只发表评论。@zeekay:我想你可能需要将
self.items()
改为
iter(self.items())
内部
\uuuu
。否则,
PicklingError
异常会被引发,抱怨
\uuuuuu reduce\uuuuu
的第五个参数必须是迭代器。因为OrderedDict和defaultdict都是用C实现的,所以会出现一个TypeError,“多个基都有实例布局冲突”。这是因为C类在如何布局内部数据结构方面有不同且不兼容的想法。上面被接受的答案在Python3中运行良好,只做了一些微小的更改(super()。\uuuuuGetItem\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。我正在使用Python3.5。有趣的是,这在Python3.4.3中正常工作。是否有任何方法可以查看C代码中的类型错误来自何处?从Python3.6开始,这将是不必要的,因为所有的
dict
,因此所有的
defaultdict
,都将被排序。我同意它不在3.5上工作;)虽然CPython 3.6中的
dicts
保留顺序,但这是一个不可依赖的实现细节,请参阅。如果您需要,请使用
OrderedDict
。它现在是官方Guido批准的。谢谢!
od=OrderedDict((k,None)表示iterable中的k)
这假设您的键是在某些iterable中预定义的,因此下游对象需要知道添加新键需要初始值。更准确地说,您不能为以下内容假设初始值:
>>>od=OrderedDefaultDict(int)>>od['foo']+=100 OrderedDefaultDict([('foo',100)])
这种情况可以通过如下解决方案正确处理。@AvyFan这是正确的。对于我的用例,它只是初始数据,因此将来插入以前未定义的键是不相关的。我将添加一个注释,以明确假设。您甚至可以覆盖
\uu init\uuu
以捕获“默认工厂”即使您已经接受了一个解决方案,您也可能希望查看我为此编写的稍微简单的
OrderedDefaultdict
类。@drs请参阅下面我的答案,它正好说明了这一点:我知道从Python 3.7开始,从常规
d继承的任何内容都会保持插入顺序信息和通信技术