J. *_*man 7 python dictionary autovivification
TL; DR
在为子键分配值时,如何在Python dict中使超级密钥自动生成,而不检查子键时是否自动生成它们?
背景: 通常在Python中,在嵌套字典中设置值需要在分配给子键之前手动确保存在更高级别的键.那是,
my_dict[1][2] = 3
Run Code Online (Sandbox Code Playgroud)
如果没有先做类似的事情,将不会按预期可靠地工作
if 1 not in my_dict:
my_dict[1] = {}
Run Code Online (Sandbox Code Playgroud)
现在,可以通过创建my_dict覆盖类的实例来设置一种自动生成,__missing__例如在/sf/answers/1388080011/中所示.
问题:但是,如果您在这样的嵌套字典中检查是否存在子键,那么该解决方案会自动对高级别密钥进行自动生成.这导致以下不幸:
>>> vd = Vividict()
>>> 1 in vd
False
>>> 2 in vd[1]
False
>>> 1 in vd
True
Run Code Online (Sandbox Code Playgroud)
我怎样才能避免这种误导性的结果呢?顺便说一句,在Perl中,我可以通过这样做获得所需的行为
no autovivification qw/exists/;
Run Code Online (Sandbox Code Playgroud)
基本上我想在可能的情况下在Python中复制该行为.
这不是一个容易解决的问题,因为在您的示例中:
my_dict[1][2] = 3
Run Code Online (Sandbox Code Playgroud)
my_dict[1]结果调用__getitem__字典。此时无法知道正在进行分配。[]只有序列中的最后一个是__setitem__调用,除非存在,否则它不会成功mydict[1],因为否则,您要分配给哪个对象?
所以不要使用自动生存。您可以使用setdefault()常规dict.
my_dict.setdefault(1, {})[2] = 3
Run Code Online (Sandbox Code Playgroud)
现在这不太漂亮,特别是当您嵌套得更深时,因此您可以编写一个辅助方法:
class MyDict(dict):
def nest(self, keys, value):
for key in keys[:-1]:
self = self.setdefault(key, {})
self[keys[-1]] = value
my_dict = MyDict()
my_dict.nest((1, 2), 3) # my_dict[1][2] = 3
Run Code Online (Sandbox Code Playgroud)
但更好的方法是将其包装成一个新的__setitem__,一次获取所有索引,而不是需要__getitem__引发自动激活的中间调用。这样,我们从一开始就知道我们正在执行一项任务,并且可以在不依赖自动生存的情况下继续进行。
class MyDict(dict):
def __setitem__(self, keys, value):
if not isinstance(keys, tuple):
return dict.__setitem__(self, keys, value)
for key in keys[:-1]:
self = self.setdefault(key, {})
dict.__setitem__(self, keys[-1], value)
my_dict = MyDict()
my_dict[1, 2] = 3
Run Code Online (Sandbox Code Playgroud)
为了保持一致性,您还可以提供__getitem__接受元组中的键,如下所示:
def __getitem__(self, keys):
if not isinstance(keys, tuple):
return dict.__getitem__(self, keys)
for key in keys:
self = dict.__getitem__(self, key)
return self
Run Code Online (Sandbox Code Playgroud)
我能想到的唯一缺点是我们不能轻松地使用元组作为字典键:我们必须将其写为,例如my_dict[(1, 2),]。