是否有一种聪明的方法将密钥传递给defaultdict的default_factory?

Ben*_*hoo 81 python

一个类有一个构造函数,它接受一个参数:

class C(object):
    def __init__(self, v):
        self.v = v
        ...
Run Code Online (Sandbox Code Playgroud)

在代码的某处,dict中的值可以用来知道它们的键.
我想使用defaultdict,并将密钥传递给新生默认值:

d = defaultdict(lambda : C(here_i_wish_the_key_to_be))
Run Code Online (Sandbox Code Playgroud)

有什么建议?

Joc*_*zel 114

它几乎没有资格聪明 - 但子类化是你的朋友:

class keydefaultdict(defaultdict):
    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError( key )
        else:
            ret = self[key] = self.default_factory(key)
            return ret

d = keydefaultdict(C)
d[x] # returns C(x)
Run Code Online (Sandbox Code Playgroud)

  • +1直接解决OP的问题,对我来说看起来并不"丑陋".也是一个很好的答案,因为许多人似乎没有意识到`defaultdict`的`__missing __()`方法可以被覆盖(因为它可以在内置的`dict`类的任何子类中从2.5版开始). (21认同)
  • 这正是我试图避免的丑陋......即使使用简单的字典并检查密钥存在也更加清晰. (14认同)
  • +1\_\__ missing\_\_ _的全部目的是自定义丢失键的行为.@silentghost提到的dict.setdefault()方法也可以工作(在正面,setdefault()很短并且已经存在;在负面,它会遇到效率问题而没有人真正喜欢名称"setdefault") . (6认同)
  • @Paul:但这就是你的答案。丑陋?快点! (4认同)
  • 我想我只是将这些代码放在我的个性化通用实用程序模块中,这样我就可以随时使用它.这样不太难看...... (4认同)

Stu*_*erg 24

不,那里没有.

defaultdict实现不能被配置为传递失踪keydefault_factory外的开箱.您唯一的选择是实现您自己的defaultdict子类,如上面@JochenRitzel所建议的那样.

但这并不像标准库解决方案那样"聪明"或几乎一样干净(如果它存在的话).因此,你的简洁,是/否问题的答案显然是"不".

标准库缺少这种经常需要的工具太糟糕了.

  • 是的,让工厂获取密钥(一元函数而不是空函数)将是一个更好的设计选择。当我们想要返回一个常量时,很容易丢弃一个参数。 (3认同)

Pau*_*sta 8

我只是想用一个让类型检查员满意的版本来扩展Jochen Ritzel 的答案:

from typing import Callable, TypeVar

K = TypeVar("K")
V = TypeVar("V")

class keydefaultdict(dict[K, V]):
    def __init__(self, default_factory: Callable[[K], V]):
        super().__init__()
        self.default_factory = default_factory

    def __missing__(self, key: K) -> V:
        if self.default_factory is None:
            raise KeyError(key)
        else:
            ret = self[key] = self.default_factory(key)
            return ret
Run Code Online (Sandbox Code Playgroud)


Sil*_*ost 6

我觉得你根本不需要defaultdict这里.为什么不只是使用dict.setdefault方法?

>>> d = {}
>>> d.setdefault('p', C('p')).v
'p'
Run Code Online (Sandbox Code Playgroud)

那当然会创造许多实例C.如果这是一个问题,我认为更简单的方法将做:

>>> d = {}
>>> if 'e' not in d: d['e'] = C('e')
Run Code Online (Sandbox Code Playgroud)

defaultdict我所见,它会比任何其他选择更快.

关于in测试速度与使用try-except子句的ETA:

>>> def g():
    d = {}
    if 'a' in d:
        return d['a']


>>> timeit.timeit(g)
0.19638929363557622
>>> def f():
    d = {}
    try:
        return d['a']
    except KeyError:
        return


>>> timeit.timeit(f)
0.6167065411074759
>>> def k():
    d = {'a': 2}
    if 'a' in d:
        return d['a']


>>> timeit.timeit(k)
0.30074866358404506
>>> def p():
    d = {'a': 2}
    try:
        return d['a']
    except KeyError:
        return


>>> timeit.timeit(p)
0.28588609450770264
Run Code Online (Sandbox Code Playgroud)

  • 在多次访问d并且很少丢失密钥的情况下,这非常浪费:C(密钥)将因此产生大量不需要的对象以供GC收集.此外,在我的情况下还有一个额外的痛苦,因为创建新的C对象很慢. (7认同)
  • @SilentGhost:我不明白 - 这是如何解决OP的问题的?我认为OP希望任何尝试读取`d [key]`返回`d [key] = C(key)`如果`key不在d`中.但是你的解决方案要求他实际上提前预先设置`d [key]`?他怎么会知道他需要哪个"钥匙"? (5认同)
  • 因为 setdefault 非常丑陋,并且集合中的 defaultdict 应该支持接收密钥的工厂函数。浪费了 Python 设计者的机会! (4认同)