我可以在Python中执行有序的默认dict吗?

jlc*_*lin 174 python dictionary

我想结合OrderedDict(),并defaultdict()collections一个对象,这应是一个有序的,默认的字典.这可能吗?

zee*_*kay 87

以下(使用此配方的修改版本)适合我:

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))
Run Code Online (Sandbox Code Playgroud)

  • @zeekay:我想你可能需要在`__reduce__`中将`self.items()`改成`iter(self.items())`.否则,引发`PicklingError`异常抱怨`__reduce__`的第五个参数必须是迭代器. (4认同)
  • @Neil G:您可能应该使用内置的`callable()`函数来测试`default_factory`.使用`isinstance(default_factory,Callable)`实际上要求它不仅仅具有可训练性 - 请参阅[docs](http://docs.python.org/library/collections.html?highlight=callable#collections.Callable) - 这就是所需要的. (3认同)
  • 删除了我的答案,这在思考过程中是类似的,但是在运行中设计(因此需要实现各种其他功能). (2认同)

avy*_*ain 39

这是另一种可能性,灵感来自Raymond Hettinger的super()Considered Super,在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
Run Code Online (Sandbox Code Playgroud)

如果你查看班级的MRO(aka,help(OrderedDefaultDict)),你会看到:

class OrderedDefaultDict(collections.OrderedDict, collections.defaultdict)
 |  Method resolution order:
 |      OrderedDefaultDict
 |      collections.OrderedDict
 |      collections.defaultdict
 |      __builtin__.dict
 |      __builtin__.object
Run Code Online (Sandbox Code Playgroud)

这意味着当一个实例OrderedDefaultDict被初始化时,它会延迟到OrderedDict's init,但是这个实际上会defaultdict在调用之前调用__builtin__.dict它的方法,这正是我们想要的.

  • 尽管它的优雅和简洁,但这个答案在Python3中不起作用.由于OrderedDict和defaultdict都是在C中实现的,因此会出现TypeError,"多个基础具有实例布局冲突".那是因为C类对如何布局内部数据结构有不同的,不相容的想法.上面接受的答案在Python3中运行良好,只有一些微小的变化(super().__ getitem __(...而不是OrderedDict .__ getitem _(...).我正在使用Python3.5. (18认同)
  • 虽然CPython 3.6中的`dicts`保留了顺序,但这是一个不能依赖的实现细节,请参阅http://stackoverflow.com/a/39980548/91243.如果你想要的话,使用`OrderedDict`. (14认同)
  • 从Python 3.6开始,这将是不必要的,因为所有`dicts`,因此所有`defaultdicts`,都将被订购.我很好,它不适用于3.5;) (9认同)
  • 它现在正式Guido批准了它. (8认同)
  • 有趣的是,这在Python中正常工作3.4.3有没有办法在C代码中查看TypeError的来源? (3认同)
  • 如此美丽.遗憾的是它在Python 3中不起作用. (3认同)

Tay*_*ton 20

这是另一种解决方案,可以考虑您的用例是否像我的一样简单,并且您不一定要在代码中添加DefaultOrderedDict类实现的复杂性.

from collections import OrderedDict

keys = ['a', 'b', 'c']
items = [(key, None) for key in keys]
od = OrderedDict(items)
Run Code Online (Sandbox Code Playgroud)

(None是我想要的默认值.)

请注意,如果您的一个要求是使用默认值动态插入新密钥,则此解决方案将不起作用.简单的权衡.

更新3/13/17 - 我了解了这个用例的便利功能.与上面相同,但你可以省略该行items = ...,只是:

od = OrderedDict.fromkeys(keys)
Run Code Online (Sandbox Code Playgroud)

输出:

OrderedDict([('a', None), ('b', None), ('c', None)])
Run Code Online (Sandbox Code Playgroud)

如果您的密钥是单个字符,您只需传递一个字符串:

OrderedDict.fromkeys('abc')
Run Code Online (Sandbox Code Playgroud)

它具有与上述两个示例相同的输出.

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

  • 谢谢!`od = OrderedDict((k,None)for k for iterable)` (2认同)

Art*_*yer 15

如果你想要一个不需要课程的简单解决方案,你可以使用或.如果你只是从几个地方获得/设置,比如循环,你可以很容易地设置默认.OrderedDict.setdefault(key, default=None)OrderedDict.get(key, default=None)

totals = collections.OrderedDict()

for i, x in some_generator():
    totals[i] = totals.get(i, 0) + x
Run Code Online (Sandbox Code Playgroud)

列表更容易setdefault:

agglomerate = collections.OrderedDict()

for i, x in some_generator():
    agglomerate.setdefault(i, []).append(x)
Run Code Online (Sandbox Code Playgroud)

但是如果你多次使用它,最好设置一个类,就像在其他答案中一样.

  • 这确实是最干净的答案! (2认同)

Nec*_*ard 7

一个更简单的@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
Run Code Online (Sandbox Code Playgroud)


F P*_*ira 6

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

class OrderedDefaultDict(OrderedDict):
    factory = list

    def __missing__(self, key):
        self[key] = value = self.factory()
        return value
Run Code Online (Sandbox Code Playgroud)


小智 5

另一个简单的方法是使用字典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)])
>>> 
Run Code Online (Sandbox Code Playgroud)