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


93

一个类具有一个带有一个参数的构造函数:

class C(object):
    def __init__(self, v):
        self.v = v
        ...

在代码中的某处,对于字典中的值了解其键很有用。
我想使用defaultdict并将密钥传递给新生儿默认值:

d = defaultdict(lambda : C(here_i_wish_the_key_to_be))

有什么建议?

Answers:


127

它几乎不算聪明 -但子类化是您的朋友:

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)

16
那正是我要避免的丑陋……即使使用简单的dict并检查密钥是否存在也更加干净。
本杰明·尼特劳

1
@Paul:但这是你的答案。丑陋?来吧!
tzot

4
我想我只是将这段代码放入我的个性化通用工具模块中,这样我就可以随时使用它。那样不太丑……
weronika 2011年

24
+1直接解决了OP的问题,对我来说看起来并不“丑陋”。也是一个很好的答案,因为许多人似乎都没有意识到可以重写defaultdict__missing__()方法(因为dict自2.5版以来,该方法可以在内置类的任何子类中使用)。
martineau 2012年

7
+1 __missing__的全部目的是自定义缺少键的行为。@silentghost提到的dict.setdefault()方法也可以工作(从正面看,setdefault()很短并且已经存在;从正面看,它存在效率问题,没有人真正喜欢“ setdefault”这个名字) 。
Raymond Hettinger

26

不,那里没有。

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

但这并不像标准库解决方案(如果存在)那么“聪明”或几乎没有它那么干净。因此,对于您的简洁(是/否)问题的答案显然是“否”。

标准库缺少这样一个经常需要的工具,这太糟糕了。


是的,让工厂采用密钥(一元函数而不是空值)是一个更好的设计选择。当我们想返回一个常量时,很容易丢弃一个参数。
YvesgereY

6

我认为您根本不需要defaultdict这里。为什么不只是使用dict.setdefault方法?

>>> d = {}
>>> d.setdefault('p', C('p')).v
'p'

那当然会创建许多实例C。万一这是一个问题,我认为更简单的方法可以做到:

>>> d = {}
>>> if 'e' not in d: d['e'] = C('e')

defaultdict我所知,它将比或任何其他替代方案更快。

预计到达速度in测试与使用try-except子句的:

>>> 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

7
在多次访问d且仅很少丢失密钥的情况下,这是非常浪费的:C(key)将因此创建大量不需要的对象供GC收集。另外,在我的情况下,还存在其他麻烦,因为创建新的C对象很慢。
本杰明·尼特劳

@Paul:是的。我建议使用更简单的方法,请参见我的编辑。
SilentGhost 2010年

我不确定它比defaultdict更快,但这是我通常要做的(请参阅我对THC4k答案的评论)。我希望有一种简单的方法可以绕过default_factory不带参数的事实,以使代码保持更优雅。
本杰明·尼特劳

5
@SilentGhost:我不明白-这如何解决OP的问题?我以为OP希望读取if的任何尝试d[key]返回。但是您的解决方案要求他实际去预先设置好吗?他怎么知道他需要什么?d[key] = C(key)key not in dd[key]key
2012年

2
因为setdefault就像地狱一样丑陋,并且集合中的defaultdict应该支持接收密钥的工厂函数。Python设计师真是浪费了机会!
jgomo3

0

这是一个自动添加值的字典的工作示例。在/ usr / include中查找重复文件的演示任务。请注意,定制字典PathDict仅需要四行:

class FullPaths:

    def __init__(self,filename):
        self.filename = filename
        self.paths = set()

    def record_path(self,path):
        self.paths.add(path)

class PathDict(dict):

    def __missing__(self, key):
        ret = self[key] = FullPaths(key)
        return ret

if __name__ == "__main__":
    pathdict = PathDict()
    for root, _, files in os.walk('/usr/include'):
        for f in files:
            path = os.path.join(root,f)
            pathdict[f].record_path(path)
    for fullpath in pathdict.values():
        if len(fullpath.paths) > 1:
            print("{} located in {}".format(fullpath.filename,','.join(fullpath.paths)))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.