是否可以确定包含 None 的嵌套字典中的哪个 level/key,导致 'NoneType' 对象不可订阅?
Is it possible to determine which level/key of a nested dict that contained None, causing 'NoneType' object is not subscriptable?
我的框架的用户(他们可能精通也可能不精通 Python)编写导航字典的代码(最初来自某些 [=35= 的 json 响应]).
有时他们会犯错误,有时 API returns 数据会缺失一些值,他们会得到可怕的 'NoneType' object is not subscriptable
如何明确错误发生在什么级别? (返回什么键 None)
def user_code(some_dict):
# I can't modify this code, it is written by the user
something = some_dict["a"]["b"]["c"]
# I don't control the contents of this.
data_from_api = '{"a": {"b": None}}'
# framework code, I control this
try:
user_code(json.loads(data_from_api))
except TypeError as e:
# I'd like to print an error message containing "a","b" here
如有必要,我可以overload/alter dict 实现,但我不想进行源代码检查。
这个问题可能已经有了答案(或者不可能),但是在所有基本问题中很难找到 为什么我得到 'NoneType' object is not subscriptable ? 个问题。如果这是重复的,我很抱歉。
编辑:@2e0byo 的回答对我原来的问题是最正确的,但我确实发现 autoviv 为我的“真实”潜在问题提供了一个很好的解决方案(允许用户轻松浏览有时没有所有预期的数据),所以我选择了这种方法。它唯一真正的缺点是如果有人依赖 some_dict["a"]["b"]["c"]
来抛出异常。我的解决方案是这样的:
def user_code(some_dict):
# this doesnt crash anymore, and instead sets something to None
something = some_dict["a"]["b"]["c"]
# I don't control the contents of this.
data_from_api = '{"a": {"b": None}}'
# framework code, I control this
user_code(autoviv.loads(data_from_api))
这是解决此问题的一种方法:使您的代码 return 成为一个自定义 Result()
对象来包装每个对象。 (这种方法可以推广到 .left()
和 .right()
的 monad 方法,但我没有去那里,因为我不经常看到这种模式(在我公认的小经验中!)。
示例代码
首先是自定义 Result()
对象:
class Result:
def __init__(self, val):
self._val = val
def __getitem__(self, k):
try:
return self._val[k]
except KeyError:
raise Exception("No such key")
except TypeError:
raise Exception(
"Result is None. This probably indicates an error in your code."
)
def __getattr__(self, a):
try:
return self._val.a
except AttributeError:
if self._val is None:
raise Exception(
"Result is None. This probably indicates an error in your code."
)
else:
raise Exception(
f"No such attribute for value of type {type(self._val)}, valid attributes are {dir(self._val)}"
)
@property
def val(self):
return self._val
当然,这里还有很大的改进空间(例如__repr__()
,您可能想修改错误消息)。
在行动:
def to_result(thing):
if isinstance(thing, dict):
return Result({k: to_result(v) for k, v in thing.items()})
else:
return Result(thing)
d = {"a": {"b": None}}
r_dd = to_result(d)
r_dd["a"] # Returns a Result object
r_dd["a"]["b"] # Returns a Result object
r_dd["a"]["c"] # Raises a helpful error
r_dd["a"]["b"]["c"] # Raises a helpful error
r_dd["a"]["b"].val # None
r_dd["a"]["b"].nosuchattr # Raises a helpful error
推理
如果我要提供一个自定义对象,我希望我的用户知道它是一个自定义对象。所以我们有一个包装器 class,我们告诉用户范式是 'get at the object, and then use .val to get the result'。处理错误的 .val 是 他们的 代码的问题(所以如果 .val
是 None,他们必须处理那个)。但是处理数据结构中的问题是我们的问题,所以我们给他们一个定制的 class,其中包含有用的消息,而不是其他任何东西。
获取嵌套错误的级别
按照目前的实现,很容易在错误消息中得到上面的一个(用于 dict 查找)。如果你想获得更多,你需要在 Result 的层次结构中保留一个引用——这可能更好地用 Result 编写,而不仅仅是一个包装器。
我不确定这是否是您正在寻找的解决方案,但这可能是朝着正确方向迈出的一步。
我的框架的用户(他们可能精通也可能不精通 Python)编写导航字典的代码(最初来自某些 [=35= 的 json 响应]).
有时他们会犯错误,有时 API returns 数据会缺失一些值,他们会得到可怕的 'NoneType' object is not subscriptable
如何明确错误发生在什么级别? (返回什么键 None)
def user_code(some_dict):
# I can't modify this code, it is written by the user
something = some_dict["a"]["b"]["c"]
# I don't control the contents of this.
data_from_api = '{"a": {"b": None}}'
# framework code, I control this
try:
user_code(json.loads(data_from_api))
except TypeError as e:
# I'd like to print an error message containing "a","b" here
如有必要,我可以overload/alter dict 实现,但我不想进行源代码检查。
这个问题可能已经有了答案(或者不可能),但是在所有基本问题中很难找到 为什么我得到 'NoneType' object is not subscriptable ? 个问题。如果这是重复的,我很抱歉。
编辑:@2e0byo 的回答对我原来的问题是最正确的,但我确实发现 autoviv 为我的“真实”潜在问题提供了一个很好的解决方案(允许用户轻松浏览有时没有所有预期的数据),所以我选择了这种方法。它唯一真正的缺点是如果有人依赖 some_dict["a"]["b"]["c"]
来抛出异常。我的解决方案是这样的:
def user_code(some_dict):
# this doesnt crash anymore, and instead sets something to None
something = some_dict["a"]["b"]["c"]
# I don't control the contents of this.
data_from_api = '{"a": {"b": None}}'
# framework code, I control this
user_code(autoviv.loads(data_from_api))
这是解决此问题的一种方法:使您的代码 return 成为一个自定义 Result()
对象来包装每个对象。 (这种方法可以推广到 .left()
和 .right()
的 monad 方法,但我没有去那里,因为我不经常看到这种模式(在我公认的小经验中!)。
示例代码
首先是自定义 Result()
对象:
class Result:
def __init__(self, val):
self._val = val
def __getitem__(self, k):
try:
return self._val[k]
except KeyError:
raise Exception("No such key")
except TypeError:
raise Exception(
"Result is None. This probably indicates an error in your code."
)
def __getattr__(self, a):
try:
return self._val.a
except AttributeError:
if self._val is None:
raise Exception(
"Result is None. This probably indicates an error in your code."
)
else:
raise Exception(
f"No such attribute for value of type {type(self._val)}, valid attributes are {dir(self._val)}"
)
@property
def val(self):
return self._val
当然,这里还有很大的改进空间(例如__repr__()
,您可能想修改错误消息)。
在行动:
def to_result(thing):
if isinstance(thing, dict):
return Result({k: to_result(v) for k, v in thing.items()})
else:
return Result(thing)
d = {"a": {"b": None}}
r_dd = to_result(d)
r_dd["a"] # Returns a Result object
r_dd["a"]["b"] # Returns a Result object
r_dd["a"]["c"] # Raises a helpful error
r_dd["a"]["b"]["c"] # Raises a helpful error
r_dd["a"]["b"].val # None
r_dd["a"]["b"].nosuchattr # Raises a helpful error
推理
如果我要提供一个自定义对象,我希望我的用户知道它是一个自定义对象。所以我们有一个包装器 class,我们告诉用户范式是 'get at the object, and then use .val to get the result'。处理错误的 .val 是 他们的 代码的问题(所以如果 .val
是 None,他们必须处理那个)。但是处理数据结构中的问题是我们的问题,所以我们给他们一个定制的 class,其中包含有用的消息,而不是其他任何东西。
获取嵌套错误的级别
按照目前的实现,很容易在错误消息中得到上面的一个(用于 dict 查找)。如果你想获得更多,你需要在 Result 的层次结构中保留一个引用——这可能更好地用 Result 编写,而不仅仅是一个包装器。
我不确定这是否是您正在寻找的解决方案,但这可能是朝着正确方向迈出的一步。