使用Python进行鸭子输入,数据验证和断言编程


10

关于鸭子打字

通过惯用性地不测试方法和函数体中的自变量类型,依靠文档,清晰的代码和测试来确保正确使用,可以帮助进行鸭子键入。

关于论证验证(EAFP:比宽恕更容易获得宽恕)。来自这里的改编示例:

...被认为是更pythonic的:

def my_method(self, key):
    try:
        value = self.a_dict[member]
    except TypeError:
        # do something else

这意味着使用您的代码的其他任何人都不必使用真正的字典或子类-他们可以使用实现映射接口的任何对象。

不幸的是,实际上并不是那么简单。如果上述示例中的member可能是整数怎么办?整数是不可变的-因此将它们用作字典键是完全合理的。但是,它们也用于索引序列类型对象。如果member恰好是整数,则示例二可以让列表,字符串以及字典通过。

关于断言编程:

断言是检查程序内部状态是否符合程序员期望的一种系统方法,目的是捕获错误。特别是,它们非常适合捕获编写代码时做出的错误假设,或捕获其他程序员滥用接口的情况。此外,通过使程序员的假设显而易见,它们可以在一定程度上充当内联文档。(“显式优于隐式。”)

提到的概念有时会发生冲突,因此在选择是否完全不进行任何数据验证,进行强力验证或使用断言时,我会依靠以下因素:

  1. 强力验证。通过强力验证,我的意思是引发自定义Exception(ApiError例如)。如果我的函数/方法是公共API的一部分,则最好验证参数以显示有关意外类型的良好错误消息。通过检查类型,我并不是指仅使用isinstance,而是通过的对象是否支持所需的接口(鸭子输入)。当我记录API并指定期望的类型并且用户可能想以意外方式使用我的函数时,在检查这些假设时,我会感到更加安全。我通常使用isinstance,如果以后要支持其他类型或鸭子,则可以更改验证逻辑。

  2. 断言编程。如果我的代码是新的,我会使用很多断言。您对此有何建议?以后是否从代码中删除断言?

  3. 如果我的函数/方法不是API的一部分,而是将其某些参数传递给我未编写,研究或测试的其他代码,则我会根据被调用的接口执行很多断言。我的逻辑背后-最好在我的代码中失败,然后在堆栈跟踪中加深10个级别,出现难以理解的错误,这迫使进行大量调试,然后无论如何都将断言添加到我的代码中。

断言,关于何时使用或不使用类型/值验证的评论和建议?很抱歉,问题不是最好的表述。

例如,考虑以下函数,其中Customer是SQLAlchemy声明性模型:

def add_customer(self, customer):
    """Save new customer into the database.
    @param customer: Customer instance, whose id is None
    @return: merged into global session customer
    """
    # no validation here at all
    # let's hope SQLAlchemy session will break if `customer` is not a model instance
    customer = self.session.add(customer)
    self.session.commit()
    return customer

因此,有几种处理验证的方法:

def add_customer(self, customer):
    # this is an API method, so let's validate the input
    if not isinstance(customer, Customer):
        raise ApiError('Invalid type')
    if customer.id is not None:
        raise ApiError('id should be None')

    customer = self.session.add(customer)
    self.session.commit()
    return customer

要么

def add_customer(self, customer):
    # this is an internal method, but i want to be sure
    # that it's a customer model instance
    assert isinstance(customer, Customer), 'Achtung!'
    assert customer.id is None

    customer = self.session.add(customer)
    self.session.commit()
    return customer

您何时以及为何在鸭子输入,类型检查和数据验证的上下文中使用它们?


1
除非出于性能原因,否则您不应该像单元测试一样删除断言
Bryan Chen

Answers:


4

让我给出一些指导原则。

原则1。如http://docs.python.org/2/reference/simple_stmts.html中概述的那样,可以使用命令行选项来消除assert的性能开销,同时仍然可以在那里进行调试。如果性能存在问题,请执行此操作。离开断言。(但是不要在断言中做任何重要的事情!)

原则2。如果您断言某些东西,并且将出现致命错误,请使用断言。做其他事情绝对没有任何价值。如果以后有人要更改它,他们可以更改您的代码或避免该方法调用。

原则3。不要仅仅因为您认为这是一件愚蠢的事情就禁止这样做。那么,如果您的方法允许字符串通过怎么办?如果可行,则可行。

原则4。禁止显示可能有错误的迹象。例如,考虑通过选项字典。如果该词典包含不是有效选项的内容,则表明有人不理解您的API或输入错误。炸毁它更容易引起错字,而不是阻止某人做出合理的事情。

根据前两个原则,您的第二个版本可能会被丢弃。您更喜欢其他两个中的哪一个取决于品味。您认为哪个更有可能?有人将非客户传递给客户,add_customer并且事情会中断(在这种情况下,首选版本3),或者有人会在某个时候想要用某种能够响应所有正确方法的代理对象替换您的客户(在这种情况下,首选版本1)。

我个人已经看过两种故障模式。我倾向于使用版本1,这是出于我懒惰且输入少的一般原则。(而且这种失败通常倾向于以明显的方式迟早出现。当我想使用代理对象时,我真的很讨厌那些束缚我双手的人。)但是有些程序员我很尊重谁会走另一条路。


我更喜欢v.3,尤其是在设计界面时-编写新的类和方法。我也认为v.3对于API方法很有用-因为我的代码对其他人来说是新的。我认为断言方法是一个很好的折衷方案,因为当以优化模式运行时,它会在生产中被删除。>吹牛更容易引起错字,而不是阻止某人做合理的事。<因此,您不介意进行此类验证吗?
warvariuc

让我们这样说。我发现继承与我喜欢如何发展设计的映射很差。我更喜欢构图。所以我回避断言这必须是该类的。但是我并不反对那些断言可以为我省些什么的断言。
btilly 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.