51

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

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

4

5 回答 5

21

您找到的 PEP 尚未被接受,因此没有标准或可接受的方式来执行此操作(但是 - 您始终可以自己实现 PEP!)。但是,正如您所发现的,有几种不同的方法。

可能最轻量级的就是简单地使用 Python 装饰器。Python 装饰器库中有一组用于前置/后置条件的装饰器,使用起来非常简单。这是该页面的示例:

  >>> 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

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

于 2012-01-22T11:08:54.137 回答
14

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

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-按合同设计/)。他们还提到了语言支持的必要性,因为断言对于确保 Liskov-substitution-principle 没有帮助,尽管我上面的示例旨在证明并非如此。我还应该提到 C++ pimpl(私有实现)习语作为灵感来源,尽管它有完全不同的目的。

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

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

于 2013-08-09T20:49:16.070 回答
8

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

在reddit中有一些关于这个库的讨论。

于 2011-12-19T15:41:52.190 回答
8

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

因此我们开发了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

我们发现该库在生产(由于信息丰富)和开发过程中(因为它允许您及早发现错误)都非常有用。

于 2018-08-13T10:04:47.917 回答
6

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

随机测试某些属性是否在运行时保持允许轻松检查:

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

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

于 2012-01-24T13:38:41.647 回答