你如何避免Python中的动态类型错误(NoneType没有属性x)?

11 投票
10 回答
2858 浏览
提问于 2025-04-15 20:46

我不太确定我是否喜欢Python的动态特性。因为这常常导致我忘记检查数据类型,结果在调用某个属性时,出现“NoneType(或者其他类型)没有属性x”的错误。虽然很多这样的错误其实没什么大不了,但如果处理不当,可能会导致整个应用程序或进程崩溃。

随着时间的推移,我变得更擅长预测这些错误可能出现的地方,并添加明确的类型检查。但毕竟我也是人,有时候还是会漏掉一个,然后最终让某个用户发现了。

所以我很想知道你们是怎么避免这些问题的。你们会使用类型检查的装饰器吗?或者使用特殊的对象包装器?

请分享一下你们的经验……

10 个回答

3

你也可以使用装饰器来强制规定属性的类型

>>> @accepts(int, int, int)
... @returns(float)
... def average(x, y, z):
...     return (x + y + z) / 2
...
>>> average(5.5, 10, 15.0)
TypeWarning:  'average' method accepts (int, int, int), but was given
(float, int, float)
15.25
>>> average(5, 10, 15)
TypeWarning:  'average' method returns (float), but result is (int)
15

我其实不是很喜欢它们,但我能理解它们的用处。

4

如果你为自己写的每一段代码都写好单元测试,那么在测试代码的时候,你应该能很快发现错误。

8

忘记检查类型

这听起来有点奇怪。其实你很少需要去“检查”一个类型。你只需要运行单元测试,如果你提供了错误类型的对象,程序就会出错。在我看来,你几乎不需要做太多的“检查”。

试图调用一个属性却得到 NoneType(或其他类型)没有属性 x 的错误。

意外的 None 就是一个普通的错误。大约80%的情况下,我是因为漏掉了 return。单元测试总能揭示这些问题。

剩下的错误中,80%的情况是因为“提前退出”,返回了 None,因为有人写了不完整的 return 语句。这种 if foo: return 的结构通过单元测试很容易被发现。在某些情况下,它们应该是 if foo: return somethingMeaningful,而在其他情况下,应该是 if foo: raise Exception("Foo")

其余的错误是因为误读了API。一般来说,修改器函数是不返回任何东西的。有时候我会忘记这一点。单元测试能很快发现这些问题,因为基本上,什么都不对劲。

这就很好地覆盖了“意外的 None”情况。很容易进行单元测试。大多数错误涉及一些非常简单的测试,针对一些显而易见的错误类型:错误的返回;未能引发异常。

其他“没有属性 X”的错误则是一些比较离谱的错误,使用了完全错误的类型。这通常是因为赋值语句写错了,或者函数(或方法)调用错误。它们在单元测试中总是会详细失败,修复起来几乎不需要什么努力。

很多错误其实是无害的,但如果处理不当,可能会导致整个应用程序/进程等崩溃。

嗯……无害?如果这是个错误,我希望它能尽快导致我的整个应用崩溃,这样我就能找到它。一个不崩溃的错误是最糟糕的情况。“无害”这个词我可不想用在一个没有导致我的应用崩溃的错误上。

撰写回答