是否有一个巧妙的方法将密钥传递给defaultdict的default_factory?
- 2025-03-04 08:24:00
- admin 原创
- 75
问题描述:
一个类有一个接受一个参数的构造函数:
class C(object):
def __init__(self, v):
self.v = v
...
在代码的某个地方,让字典中的值知道它们的键是很有用的。
我想使用一个 defaultdict,并将键传递给新生的默认值:
d = defaultdict(lambda : C(here_i_wish_the_key_to_be))
有什么建议吗?
解决方案 1:
这很难算得上聪明——但是子类化是你的朋友:
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)
解决方案 2:
没有。
defaultdict
无法配置实现以将缺失传递给key
现成default_factory
的。您唯一的选择是实现自己的defaultdict
子类,如上文 @JochenRitzel 所建议的那样。
但这并不“聪明”,也不像标准库解决方案那样简洁(如果存在的话)。因此,对于您简洁的是非问题,答案显然是“否”。
标准库缺少这样一个经常需要的工具,真是太糟糕了。
解决方案 3:
我只是想用一个让类型检查器满意的版本来扩展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
解决方案 4:
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 子句的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
解决方案 5:
以下是自动添加值的字典的工作示例。演示任务是在 /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)))
解决方案 6:
实现所需功能的另一种方法是使用装饰器
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
创建的新实例。X
当然,这只有当您知道该类将在 中使用时才有用default_factory
。否则,为了实例化单个类,您需要执行以下操作:
x = X(some_key=10, foo=15)()
这有点丑陋...但是如果你想避免这种情况,并引入一定程度的复杂性,你也可以添加一个关键字参数,类似于factory
允许argument_wrapper
通用行为的参数:
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]
扫码咨询,免费领取项目管理大礼包!