处理可选的 python 字典字段

c89*_*f64 5 python json dictionary attributeerror

我正在处理加载到 Python 字典中的 JSON 数据。其中很多都有可选字段,其中可能包含字典之类的东西。

dictionary1 = 
{"required": {"value1": "one", "value2": "two"},
"optional": {"value1": "one"}}

dictionary2 = 
{"required": {"value1": "one", "value2": "two"}}
Run Code Online (Sandbox Code Playgroud)

如果我这样做,

dictionary1.get("required").get("value1")
Run Code Online (Sandbox Code Playgroud)

显然,这是有效的,因为场"required"总是存在的。

但是,当我使用同一行dictionary2(以获取可选字段)时,这将产生一个AttributeError

dictionary2.get("optional").get("value1")
AttributeError: 'NoneType' object has no attribute 'get'
Run Code Online (Sandbox Code Playgroud)

这是有道理的,因为第一个.get()将返回None,而第二个.get()不能调用.get()None 对象。

我可以通过提供默认值来解决这个问题,以防可选字段丢失,但是数据变得越复杂,这就会很烦人,所以我称之为“天真的修复”:

dictionary2.get("optional", {}).get("value1", " ")
Run Code Online (Sandbox Code Playgroud)

因此,第一个.get()将返回一个空字典{},可以在其上调用第二个字典.get(),并且由于它显然不包含任何内容,因此它将返回空字符串,如第二个默认值所定义的那样。

这将不再产生错误,但我想知道是否有更好的解决方案 - 特别是对于更复杂的情况(value1包含数组或另一个字典等......)

我也可以用 try - except 来解决这个问题AttributeError,但这也不是我喜欢的方式。

try:
    value1 = dictionary2.get("optional").get("value1")
except AttributeError:
    value1 = " "
Run Code Online (Sandbox Code Playgroud)

我也不喜欢检查可选字段是否存在,这会产生垃圾代码行,例如

optional = dictionary2.get("optional")
if optional:
    value1 = optional.get("value1")
else:
    value1 = " "
Run Code Online (Sandbox Code Playgroud)

这看起来非常非Pythonic......

我在想也许我链接.get()s 的方法一开始就是错误的?

ggo*_*len 5

在您的代码中:

try:
    value1 = dictionary2.get("optional").get("value1")
except AttributeError:
    value1 = " "
Run Code Online (Sandbox Code Playgroud)

您可以使用括号和except KeyError

try:
    value1 = dictionary2["optional"]["value1"]
except KeyError:
    value1 = " "
Run Code Online (Sandbox Code Playgroud)

如果这对于调用者来说太冗长,请添加一个助手:

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
Run Code Online (Sandbox Code Playgroud)

您还可以对 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"
Run Code Online (Sandbox Code Playgroud)

但是,如果其他开发人员感到困惑,并且您想要充实其他预期方法,如如何“完美”覆盖字典?中所述,则可能会产生工程成本。(将此视为概念验证草图)。最好不要太聪明。


Ben*_*ann 1

首先,您指的" "是空字符串。这是不正确的;""是空字符串。

get其次,如果您要检查会员资格,我首先看不出使用该方法的理由。我会选择类似下面的东西。

if "optional" in dictionary2:
    value1 = dictionary2["optional"].get("value1")
else:
    value1 = ""
Run Code Online (Sandbox Code Playgroud)

另一种可供考虑的替代方案(因为您get经常使用该方法)是切换到该类defaultdict。例如,

from collections import defaultdict

dictionary2 = {"required": {"value1": "one", "value2": "two"}}
ddic2 = defaultdict(dict,dictionary2)
value1 = ddic2["optional"].get("value1")
Run Code Online (Sandbox Code Playgroud)