在Python中使用契约式设计

时间:2011-12-19 15:23:32

标签: python design-by-contract

我希望在工作中开始在大量基于Python的项目上使用DBC,并且想知道其他人使用它的经历。到目前为止,我的研究结果如下:

我的问题是:您是否将DBC与Python一起用于成熟的生产代码?它的效果如何/值得努力?你会推荐哪些工具?

5 个答案:

答案 0 :(得分:17)

您找到的PEP尚未被接受,因此没有标准或可接受的方式(但是 - 您可以自己实施PEP!)。但是,正如您所发现的,有一些不同的方法。

可能最轻量级的只是简单地使用Python装饰器。 Python Decorator Library中有一组前置/后置条件的装饰器非常简单易用。这是该页面的一个例子:

  >>> def in_ge20(inval):
  ...    assert inval >= 20, 'Input value < 20'
  ...
  >>> def out_lt30(retval, inval):
  ...    assert retval < 30, 'Return value >= 30'
  ...
  >>> @precondition(in_ge20)
  ... @postcondition(out_lt30)
  ... def inc(value):
  ...   return value + 1
  ...
  >>> inc(5)
  Traceback (most recent call last):
    ...
  AssertionError: Input value < 20

现在,您提到了类不变量。这些有点困难,但我想要的方法是定义一个可调用来检查不变量,然后像每个方法调用结束时的后置条件装饰器检查那样不变。作为第一个剪辑你可能只是按原样使用后置条件装饰器。

答案 1 :(得分:11)

根据我的经验,即使没有语言支持,按合同设计也是值得的。对于未被覆盖断言的方法,以及docstrings对于前置条件和后置条件都足够了。对于被重写的方法,我们将方法拆分为两个:检查前置和后置条件的公共方法,以及提供实现的受保护方法,并且可以由子类重写。这是后者的一个例子:

class Math:
    def square_root(self, number)
        """
        Calculate the square-root of C{number}

        @precondition: C{number >= 0}

        @postcondition: C{abs(result * result - number) < 0.01}
        """
        assert number >= 0
        result = self._square_root(number)
        assert abs(result * result - number) < 0.01
        return result

    def _square_root(self, number):
        """
        Abstract method for implementing L{square_root()}
        """
        raise NotImplementedError()

我得到了平方根,作为软件工程广播(http://www.se-radio.net/2007/03/episode-51-design-by-contract/)上按合同设计的一集的合同设计的一般例子。他们还提到了语言支持的必要性,因为断言对确保Liskov替换原则没有帮助,尽管我上面的例子旨在证明其他原因。我还应该提到C ++ pimpl(私有实现)成语作为灵感来源,尽管它有着完全不同的目的。

在我的工作中,我最近将这种合同检查重构为更大的类层次结构(合同已经记录,但没有系统地测试)。现有的单元测试显示合同多次被违反。我只能得出结论,这应该是很久以前完成的,并且一旦按照合同设计,单位测试覆盖率就会更高。我希望那些尝试这种技术组合的人能够做出相同的观察。

更好的工具支持可能会在未来为我们提供更多的动力,我对此表示欢迎。

答案 2 :(得分:7)

我没有在python中使用合同设计,所以我无法回答你的所有问题。但是,我花了一些时间查看contracts库,其最新版本已于近期发布,看起来非常不错。

reddit中有关于此库的讨论。

答案 3 :(得分:5)

虽然不完全是按合同设计,但某些测试框架支持属性测试方法在概念上非常接近。

对运行时是否保留某些属性的随机测试可以轻松检查:

  • 不变量
  • 输入和输出值的域
  • 其他前后条件

对于Python,有一些QuickCheck风格的测试框架:

答案 4 :(得分:4)

我们想在生产代码中使用前置/后置条件/不变式,但发现所有当前的按合同设计库都缺乏信息性和适当的继承性。

因此,我们开发了icontract。通过重新遍历该函数的反编译代码并评估所有涉及的值,将自动生成错误消息:

import icontract

>>> class B:
...     def __init__(self) -> None:
...         self.x = 7
...
...     def y(self) -> int:
...         return 2
...
...     def __repr__(self) -> str:
...         return "instance of B"
...
>>> class A:
...     def __init__(self)->None:
...         self.b = B()
...
...     def __repr__(self) -> str:
...         return "instance of A"
...
>>> SOME_GLOBAL_VAR = 13
>>> @icontract.pre(lambda a: a.b.x + a.b.y() > SOME_GLOBAL_VAR)
... def some_func(a: A) -> None:
...     pass
...
>>> an_a = A()
>>> some_func(an_a)
Traceback (most recent call last):
  ...
icontract.ViolationError: 
Precondition violated: (a.b.x + a.b.y()) > SOME_GLOBAL_VAR:
SOME_GLOBAL_VAR was 13
a was instance of A
a.b was instance of B
a.b.x was 7
a.b.y() was 2

我们发现该库在生产中(由于提供了有用的消息)和开发期间(因为它可以让您尽早发现错误)都非常有用。