Is there a clever way to pass the key to defaultdict's default_factory?
类具有一个采用一个参数的构造函数:
1 2 3 4
| class C(object):
def __init__(self, v):
self.v = v
... |
在代码的某个地方,对于dict中的值来说,知道它们的键是很有用的。我想使用默认dict,并将密钥传递给新生儿默认值:
1
| d = defaultdict(lambda : C(here_i_wish_the_key_to_be)) |
有什么建议吗?
- 你有什么问题?
- EDOCX1?0?
- @杰利宾:你会怎么做?
- @杰利宾:问题是默认情况下,工厂不接受任何参数;这是我试图绕过的。
- @保罗:你运行了你已经拥有的代码吗?它工作得很好。或者您想将不同的密钥传递给defaultdict的每个不同实例?
- @SilentGhost,Paul:正确,默认工厂不接受任何参数。我的快照解决方案无法工作。
- @SilentGhost:代码被破坏。我要做的是将dict的键(在新条目的情况下,其中uuMissing_uu()正在调用默认的_Factory)传递给新值的构造函数
- 我真的,真的希望这是默认实现的一部分…
- @Jochenritzel给出了一个很好的,Python式的回答。你应该接受它。
它很难被认为是聪明的——但是子类化是你的朋友:
1 2 3 4 5 6 7 8 9 10
| 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) |
- 这正是我想要避免的丑陋…即使使用一个简单的dict并检查键的存在也要干净得多。
- @保罗:但这是你的答案。丑陋?加油!
- 我想我只需要把这段代码放到我的个性化通用工具模块中,这样我就可以随时使用它。别那么难看…
- +我直截了当地回答了手术室的问题,在我看来并不难看。这也是一个很好的答案,因为许多人似乎没有意识到defaultdict的__missing__()方法可以被重写(因为它可以在2.5版以后的内置dict类的任何子类中被重写)。
- 不,这个答案是一个很好的建议,很有用,但显然不是OP要求的。这个问题的关键是要避免这一点。
- +1丢失钥匙的整个目的是为丢失的钥匙定制行为。@silentghost提到的dict.setdefault()方法也可以工作(在正方,setdefault()很短并且已经存在;在负方,它存在效率问题,没有人真正喜欢"setdefault"这个名称)。
- dict.setdefault(...)是一种更清洁的方法来解决这个问题。
- 我不同意丑陋,如果你不打算再利用它,那是丑陋的。但对于多种用途,每个实例都是一行。
- 有人有这个吗?看起来不难看,但也不适合应用程序代码。
不,没有。
不能将defaultdict实现配置为将缺少的key传递到开箱即用的default_factory。您唯一的选择是实现您自己的defaultdict子类,如上面@jochenritzel建议的那样。
但这并不"聪明",也不像标准的库解决方案那样干净(如果它存在的话)。因此,对你简明扼要的"是/否"问题的答案显然是"否"。
可惜的是,标准库缺少了一个如此频繁需要的工具。
我认为你根本不需要在这里。为什么不直接用dict.setdefault方法?
1 2 3
| >>> d = {}
>>> d.setdefault('p', C('p')).v
'p' |
当然,这将产生许多C的实例。如果这是一个问题,我认为更简单的方法可以做到:
1 2
| >>> d = {}
>>> if 'e' not in d: d['e'] = C('e') |
它比defaultdict或其他任何替代品都要快。
关于in试验与使用Try试验的速度的ETA,除非条款:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| >>> 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 |
- 在D被多次访问的情况下,这是非常浪费的,而且很少会丢失一个键:C(键)将因此为GC收集创建大量不需要的对象。另外,在我的例子中还有一个额外的痛苦,因为创建新的C对象很慢。
- @保罗:没错。我会建议更简单的方法,看我的编辑。
- 我不确定它是否比默认的dict更快,但这是我通常做的(见我对thc4k答案的评论)。我希望有一种简单的方法来破解默认工厂不带参数的事实,使代码稍微优雅一些。
- @保罗:当然更快!这是一个单一的in声明!它也是干净易读的。defaultdict背后有着不同的意图。
- 它是一个"if k in d"与(a hidden)"try:d[k]except keyerror";cpython的实现速度非常快,但也有例外,因此应该处于相同的速度级别。
- @保罗:你知道这些是不同的代码,对吧?此外,in总是比try-except子句更快。
- 异常和测试一样快。这是btaftp与lbyl共存的原因之一。(尽管事实证明它是特定于实现的:由于.NET设计的原因,Ironpython非常慢,但有例外)。
- @保罗:看我的编辑
- @SilentGhost:我不明白-这是如何解决OP的问题的?我以为欧普想要读《江户记》一书(7),如果《江户记》一书(9),他会把《江户记》一书(8)还给他。但你的解决方案要求他提前去预定d[key]?他怎么知道他需要哪一个key?
- 令人惊叹的!没有难看的代码,只使用标准的dict:D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D。
- 因为setdefault是丑陋的地狱,来自collection的defaultdict应该支持接收密钥的工厂函数。Python设计师浪费了多少机会!