在Python中避免动态类型错误的策略是什么(NoneType没有属性x)?

在Python中避免动态类型错误的策略是什么(NoneType没有属性x)?,python,Python,我不确定我是否喜欢Python的动态特性。它经常导致我忘记检查类型,尝试调用属性,而获取非类型(或任何其他)时没有属性x错误。其中很多都是无害的,但如果处理不当,它们可能会导致整个应用程序/流程等崩溃 随着时间的推移,我可以更好地预测这些内容会出现在哪里,并添加显式类型检查,但因为我是唯一的人类,我偶尔会错过一个,然后一些最终用户会发现它 所以我对你避免这些的策略很感兴趣。你使用类型检查装饰器吗?也许是特殊的物体包装 请分享…如果您为所有代码编写了良好的单元测试,那么在测试代码时应该可以很快发现

我不确定我是否喜欢Python的动态特性。它经常导致我忘记检查类型,尝试调用属性,而获取非类型(或任何其他)时没有属性x错误。其中很多都是无害的,但如果处理不当,它们可能会导致整个应用程序/流程等崩溃

随着时间的推移,我可以更好地预测这些内容会出现在哪里,并添加显式类型检查,但因为我是唯一的人类,我偶尔会错过一个,然后一些最终用户会发现它

所以我对你避免这些的策略很感兴趣。你使用类型检查装饰器吗?也许是特殊的物体包装


请分享…

如果您为所有代码编写了良好的单元测试,那么在测试代码时应该可以很快发现错误。

我倾向于使用

if x is None:
    raise ValueError('x cannot be None')
但这只适用于实际的
None

一种更通用的方法是在尝试使用必要的属性之前测试它们。例如:

def write_data(f):
    # Here we expect f is a file-like object.  But what if it's not?
    if not hasattr(f, 'write'):
        raise ValueError('write_data requires a file-like object')
    # Now we can do stuff with f that assumes it is a file-like object

这段代码的要点是,不是得到一条错误消息,比如“NoneType没有属性write”,而是得到“write\u数据需要一个类似文件的对象”。实际的bug不在
write_data()
中,也不是
NoneType
的问题。实际的错误在调用
write\u data()
的代码中。关键是尽可能直接地传达这些信息。

我没有做过很多Python编程,但我根本没有用静态类型语言进行编程,所以我不倾向于从变量类型的角度考虑问题。这也许可以解释为什么我没有经常遇到这个问题。(虽然我所做的少量Python编程也可以解释这一点。)


我非常喜欢Python3对字符串的修改处理(即所有字符串都是unicode,其他所有内容都只是一个字节流),因为在Python2中,在处理不寻常的真实字符串值之前,您可能不会注意到
TypeError
s。

可以用来简化代码的东西是使用(我是在其中介绍的)

大致上,空对象的目标是提供一个“智能的” 替换Python中经常使用的基本数据类型None或 其他语言中的Null(或Null指针)。这些是用于许多目的的 目的包括某个团体的一名成员 无论出于何种原因,在其他方面相似的元素都是特殊的。最 这通常会导致使用条件语句来区分 普通元素和原语空值

这个对象只是吃了属性错误的缺失,您可以避免检查它们的存在

只不过是

class Null(object):

    def __init__(self, *args, **kwargs):
        "Ignore parameters."
        return None

    def __call__(self, *args, **kwargs):
        "Ignore method calls."
        return self

    def __getattr__(self, mname):
        "Ignore attribute requests."
        return self

    def __setattr__(self, name, value):
        "Ignore attribute setting."
        return self

    def __delattr__(self, name):
        "Ignore deleting attributes."
        return self

    def __repr__(self):
        "Return a string representation."
        return "<Null>"

    def __str__(self):
        "Convert to a string and return it."
        return "Null"
有了这个,您只需执行以下操作:

obj.attr()

忘了它吧。请注意,大量使用
Null
对象可能会隐藏代码中的错误。

TDD的一个优点是,您最终编写的代码更易于编写测试

先编写代码,然后再编写测试,可能会产生表面上工作原理相同的代码,但编写100%覆盖率测试要困难得多

每种情况都可能不同

如果您在很多地方使用某个特定参数,那么让一个装饰器检查该参数是否为None(或其他一些意外值)可能是有意义的

也许使用-如果因为将初始值设置为None而导致代码崩溃,则可以将初始值设置为对象的空版本

但是,越来越多的包装器可能会对性能造成相当大的影响,因此最好从一开始就编写代码,避免出现死角

忘记检查字体

这没有多大意义。你很少需要“检查”一个类型。您只需运行单元测试,如果您提供了错误类型的对象,那么事情就会失败。根据我的经验,你永远不需要“检查”太多

尝试调用属性并 获取非类型(或任何其他) 没有属性x错误

意外的
None
是一个普通的老错误。80%的时候,我忽略了返回的
。单元测试总是揭示这些问题

在剩下的那些错误中,80%的情况下,它们都是普通的老错误,因为“提前退出”会返回
None
,因为有人写了一个不完整的
return
语句。这些
if foo:return
结构很容易通过单元测试检测到。在某些情况下,如果foo:returnsomething有意义的话,它们应该是
,而在其他情况下,如果foo:raiseexception(“foo”)
,它们应该是

其余的都是误读API的愚蠢错误。通常,mutator函数不返回任何内容。有时我会忘记。单元测试很快就会发现这些问题,因为基本上没有什么是正确的

这涵盖了“意外的
None
”案例。易于进行单元测试。大多数错误都涉及为一些非常明显的错误类型编写测试的琐碎工作:错误返回;未能引发异常

其他“hasnoattributex”错误实际上是使用了完全错误类型的错误。这要么是非常错误的赋值语句,要么是非常错误的函数(或方法)调用。在单元测试期间,它们总是会失败,只需很少的努力就可以修复

其中很多都是无害的,但如果处理不当,它们可能会导致整个应用程序/流程等崩溃


嗯。。。无害的?如果它是一个bug,我祈祷它尽快关闭我的整个应用程序,以便我能找到它。一个没有使我的应用程序崩溃的bug是可以想象到的最可怕的情况。“无害”并不是我用来形容无法使我的应用程序崩溃的bug的词。

一个可以帮助你保持各部分良好配合的工具是界面
zope.interface
是Python世界中使用接口最著名的包
obj.attr()
>>> @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
@decorators.params(0, int, 2, str) # first parameter must be integer / third a string
@decorators.returnsOrNone(int, long) # must return an int/long value or None
def doSomething(integerParam, noMatterWhatParam, stringParam):
    ...