有没有一种聪明的方法可以将密钥传递到 default_Factory?

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

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

在代码中的某个地方,可以让 dict 中的值知道它们的键。
我想使用一个默认值,并将密钥传递给新生的默认值:

d = defaultdict(lambda : C(here_i_wish_the_key_to_be))

有什么建议吗?

22044 次浏览

它几乎不符合 聪明-但子类化是你的朋友:

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)

我认为这里根本不需要 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 除外条款的比较,预计到达时间 :

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

不,没有。

The defaultdict implementation can not be configured to pass missing key to the default_factory out-of-the-box. Your only option is to implement your own defaultdict subclass, as suggested by @JochenRitzel, above.

但是这并不像标准库解决方案那样“聪明”或者干净(如果存在的话)

遗憾的是,标准库缺少这样一个经常需要的工具。

下面是一个自动添加值的字典的工作示例。在/usr/include 中查找重复文件的演示任务。注意自定义字典 PathDit只需要四行:

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

Another way that you can potentially achieve the desired functionality is by using decorators

def initializer(cls: type):
def argument_wrapper(
*args: Tuple[Any], **kwargs: Dict[str, Any]
) -> Callable[[], 'X']:
def wrapper():
return cls(*args, **kwargs)


return wrapper


return argument_wrapper




@initializer
class X:
def __init__(self, *, some_key: int, foo: int = 10, bar: int = 20) -> None:
self._some_key = some_key
self._foo = foo
self._bar = bar


@property
def key(self) -> int:
return self._some_key


@property
def foo(self) -> int:
return self._foo


@property
def bar(self) -> int:
return self._bar


def __str__(self) -> str:
return f'[Key: {self.key}, Foo: {self.foo}, Bar: {self.bar}]'

然后你可以创建一个 defaultdict,如下所示:

>>> d = defaultdict(X(some_key=10, foo=15, bar=20))
>>> d['baz']
[Key: 10, Foo: 15, Bar: 20]
>>> d['qux']
[Key: 10, Foo: 15, Bar: 20]

default_factory将创建具有指定的 争论。

Of course, this would only be useful if you know that the class will be used in a default_factory. Otherwise, in-order to instantiate an individual class you would need to do something like:

x = X(some_key=10, foo=15)()

但是,如果你想避免这种情况,并且引入一定程度的复杂性,你也可以在 argument_wrapper中添加一个像 factory这样的关键字参数,它允许通用行为:

def initializer(cls: type):
def argument_wrapper(
*args: Tuple[Any], factory: bool = False, **kwargs: Dict[str, Any]
) -> Callable[[], 'X']:
def wrapper():
return cls(*args, **kwargs)


if factory:
return wrapper
return cls(*args, **kwargs)


return argument_wrapper

你可以这样使用这个类:

>>> X(some_key=10, foo=15)
[Key: 10, Foo: 15, Bar: 20]
>>> d = defaultdict(X(some_key=15, foo=15, bar=25, factory=True))
>>> d['baz']
[Key: 15, Foo: 15, Bar: 25]

我只是想在 Jochen Ritzel's answer的基础上扩展一个版本,让打字检查员感到高兴:

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