处理可选的 python 字典字段
Dealing with optional python dictionary fields
我正在处理加载到 python 词典中的 JSON 数据。其中很多都有可选字段,然后可能包含字典之类的东西。
dictionary1 =
{"required": {"value1": "one", "value2": "two"},
"optional": {"value1": "one"}}
dictionary2 =
{"required": {"value1": "one", "value2": "two"}}
如果我这样做,
dictionary1.get("required").get("value1")
这显然有效,因为“必填”字段始终存在。
但是,当我在 dictionary2 上使用同一行(获取可选字段)时,这将产生一个 AttributeError
dictionary2.get("optional").get("value1")
AttributeError: 'NoneType' object has no attribute 'get'
这是有道理的,因为第一个 .get() 将 return None,而第二个 .get() 不能在 None 对象上调用 get()。
如果可选字段丢失,我可以通过提供默认值来解决这个问题,但是数据变得越复杂,这就越烦人,所以我称之为“天真的修复”:
dictionary2.get("optional", {}).get("value1", " ")
因此第一个 .get() 将 return 一个空字典 {},可以在其上调用第二个 .get(),并且由于它显然不包含任何内容,因此它将 return根据第二个默认值定义的空字符串。
这将不再产生错误,但我想知道是否有更好的解决方案 - 特别是对于更复杂的情况(value1 包含一个数组或另一个字典等......)
我也可以用 try - except AttributeError 来解决这个问题,但这也不是我的首选方法。
try:
value1 = dictionary2.get("optional").get("value1")
except AttributeError:
value1 = " "
我也不喜欢检查可选字段是否存在,这会产生像
这样的垃圾代码行
optional = dictionary2.get("optional")
if optional:
value1 = optional.get("value1")
else:
value1 = " "
这看起来很不pythonic...
我在想,也许我只是链接 .get()s 的方法一开始就是错误的?
编辑:感谢 Ben Grossmann 的回答,我想出了涵盖我的用例的这个单行代码。
value1 = dictionary2["optional"]["value1"] if "optional" in dictionary2 else " "
作为三元运算符的成员检查将绕过导致错误的语句的评估并使用默认值“”,而不必为各个检查提供默认值
首先,您将 " "
称为空字符串。这是不正确的; ""
是空字符串。
其次,如果您要检查成员资格,我认为首先没有理由使用 get
方法。我会选择如下内容。
if "optional" in dictionary2:
value1 = dictionary2["optional"].get("value1")
else:
value1 = ""
要考虑的另一种选择(因为您经常使用 get
方法)是切换到 defaultdict
class。例如,
from collections import defaultdict
dictionary2 = {"required": {"value1": "one", "value2": "two"}}
ddic2 = defaultdict(dict,dictionary2)
value1 = ddic2["optional"].get("value1")
Pythonic 的方式是使用 try/except
块 -
dictionary2 = {"required": {"value1": "one", "value2": "two"}}
try:
value1 = dictionary2["optional"]["value1"]
except (KeyError, AttributeError) as e:
value1 = ""
KeyError
用于捕获丢失的键,AttributeError
用于捕获具有 list
/str
而不是 dict
对象的情况。
如果您不喜欢代码中的大量 try/except
,您可以考虑使用辅助函数 -
def get_val(data, keys):
try:
for k in keys:
data = data[k]
return data
except (KeyError, AttributeError) as e:
return ""
dictionary2 = {"required": {"value1": "one", "value2": "two"}}
print(get_val(dictionary2, ("required", "value2")))
print(get_val(dictionary2, ("optional", "value1")))
输出-
two
在您的代码中:
try:
value1 = dictionary2.get("optional").get("value1")
except AttributeError:
value1 = " "
您可以使用方括号和 except KeyError
:
try:
value1 = dictionary2["optional"]["value1"]
except KeyError:
value1 = " "
如果这对调用者来说太冗长,请添加一个助手:
def get_or_default(d, *keys, default=None):
try:
for k in keys:
d = d[k]
except (KeyError, IndexError):
return default
return d
if __name__ == "__main__":
d = {"a": {"b": {"c": [41, 42]}}}
print(get_or_default(d, "a", "b", "c", 1)) # => 42
print(get_or_default(d, "a", "b", "d", default=43)) # => 43
你也可以继承 dict 并使用元组括号索引,比如 NumPy 和 Pandas:
class DeepDict(dict):
def __init__(self, d, default=None):
self.d = d
self.default = default
def __getitem__(self, keys):
d = self.d
try:
for k in keys:
d = d[k]
except (KeyError, IndexError):
return self.default
return d
def __setitem__(self, keys, x):
d = self.d
for k in keys[:-1]:
d = d[k]
d[keys[-1]] = x
if __name__ == "__main__":
dd = DeepDict({"a": {"b": {"c": [42, 43]}}}, default="foo")
print(dd["a", "b", "c", 1]) # => 43
print(dd["a", "b", "c", 11]) # => "foo"
dd["a", "b", "c", 1] = "banana"
print(dd["a", "b", "c", 1]) # => "banana"
但是如果其他开发人员感到困惑,那么这可能会产生工程成本,并且您想要充实 How to "perfectly" override a dict? 中描述的其他预期方法(将其视为 proof-of-concept草图)。最好不要太聪明
您可以为此使用 toolz.dicttoolz.get_in():
from toolz.dicttoolz import get_in
dictionary1 = {"required": {"value1": "one", "value2": "two"}, "optional": {"value1": "one"}}
dictionary2 = {"required": {"value1": "one", "value2": "two"}}
get_in(("optional", "value1"), dictionary1)
# 'one'
get_in(("optional", "value1"), dictionary2)
# None
我正在处理加载到 python 词典中的 JSON 数据。其中很多都有可选字段,然后可能包含字典之类的东西。
dictionary1 =
{"required": {"value1": "one", "value2": "two"},
"optional": {"value1": "one"}}
dictionary2 =
{"required": {"value1": "one", "value2": "two"}}
如果我这样做,
dictionary1.get("required").get("value1")
这显然有效,因为“必填”字段始终存在。
但是,当我在 dictionary2 上使用同一行(获取可选字段)时,这将产生一个 AttributeError
dictionary2.get("optional").get("value1")
AttributeError: 'NoneType' object has no attribute 'get'
这是有道理的,因为第一个 .get() 将 return None,而第二个 .get() 不能在 None 对象上调用 get()。
如果可选字段丢失,我可以通过提供默认值来解决这个问题,但是数据变得越复杂,这就越烦人,所以我称之为“天真的修复”:
dictionary2.get("optional", {}).get("value1", " ")
因此第一个 .get() 将 return 一个空字典 {},可以在其上调用第二个 .get(),并且由于它显然不包含任何内容,因此它将 return根据第二个默认值定义的空字符串。 这将不再产生错误,但我想知道是否有更好的解决方案 - 特别是对于更复杂的情况(value1 包含一个数组或另一个字典等......)
我也可以用 try - except AttributeError 来解决这个问题,但这也不是我的首选方法。
try:
value1 = dictionary2.get("optional").get("value1")
except AttributeError:
value1 = " "
我也不喜欢检查可选字段是否存在,这会产生像
这样的垃圾代码行optional = dictionary2.get("optional")
if optional:
value1 = optional.get("value1")
else:
value1 = " "
这看起来很不pythonic...
我在想,也许我只是链接 .get()s 的方法一开始就是错误的?
编辑:感谢 Ben Grossmann 的回答,我想出了涵盖我的用例的这个单行代码。
value1 = dictionary2["optional"]["value1"] if "optional" in dictionary2 else " "
作为三元运算符的成员检查将绕过导致错误的语句的评估并使用默认值“”,而不必为各个检查提供默认值
首先,您将 " "
称为空字符串。这是不正确的; ""
是空字符串。
其次,如果您要检查成员资格,我认为首先没有理由使用 get
方法。我会选择如下内容。
if "optional" in dictionary2:
value1 = dictionary2["optional"].get("value1")
else:
value1 = ""
要考虑的另一种选择(因为您经常使用 get
方法)是切换到 defaultdict
class。例如,
from collections import defaultdict
dictionary2 = {"required": {"value1": "one", "value2": "two"}}
ddic2 = defaultdict(dict,dictionary2)
value1 = ddic2["optional"].get("value1")
Pythonic 的方式是使用 try/except
块 -
dictionary2 = {"required": {"value1": "one", "value2": "two"}}
try:
value1 = dictionary2["optional"]["value1"]
except (KeyError, AttributeError) as e:
value1 = ""
KeyError
用于捕获丢失的键,AttributeError
用于捕获具有 list
/str
而不是 dict
对象的情况。
如果您不喜欢代码中的大量 try/except
,您可以考虑使用辅助函数 -
def get_val(data, keys):
try:
for k in keys:
data = data[k]
return data
except (KeyError, AttributeError) as e:
return ""
dictionary2 = {"required": {"value1": "one", "value2": "two"}}
print(get_val(dictionary2, ("required", "value2")))
print(get_val(dictionary2, ("optional", "value1")))
输出-
two
在您的代码中:
try:
value1 = dictionary2.get("optional").get("value1")
except AttributeError:
value1 = " "
您可以使用方括号和 except KeyError
:
try:
value1 = dictionary2["optional"]["value1"]
except KeyError:
value1 = " "
如果这对调用者来说太冗长,请添加一个助手:
def get_or_default(d, *keys, default=None):
try:
for k in keys:
d = d[k]
except (KeyError, IndexError):
return default
return d
if __name__ == "__main__":
d = {"a": {"b": {"c": [41, 42]}}}
print(get_or_default(d, "a", "b", "c", 1)) # => 42
print(get_or_default(d, "a", "b", "d", default=43)) # => 43
你也可以继承 dict 并使用元组括号索引,比如 NumPy 和 Pandas:
class DeepDict(dict):
def __init__(self, d, default=None):
self.d = d
self.default = default
def __getitem__(self, keys):
d = self.d
try:
for k in keys:
d = d[k]
except (KeyError, IndexError):
return self.default
return d
def __setitem__(self, keys, x):
d = self.d
for k in keys[:-1]:
d = d[k]
d[keys[-1]] = x
if __name__ == "__main__":
dd = DeepDict({"a": {"b": {"c": [42, 43]}}}, default="foo")
print(dd["a", "b", "c", 1]) # => 43
print(dd["a", "b", "c", 11]) # => "foo"
dd["a", "b", "c", 1] = "banana"
print(dd["a", "b", "c", 1]) # => "banana"
但是如果其他开发人员感到困惑,那么这可能会产生工程成本,并且您想要充实 How to "perfectly" override a dict? 中描述的其他预期方法(将其视为 proof-of-concept草图)。最好不要太聪明
您可以为此使用 toolz.dicttoolz.get_in():
from toolz.dicttoolz import get_in
dictionary1 = {"required": {"value1": "one", "value2": "two"}, "optional": {"value1": "one"}}
dictionary2 = {"required": {"value1": "one", "value2": "two"}}
get_in(("optional", "value1"), dictionary1)
# 'one'
get_in(("optional", "value1"), dictionary2)
# None