ipa*_*ola 42 python design-by-contract
我希望在工作中开始在大量基于Python的项目上使用DBC,并且想知道其他人对它有什么经验.到目前为止,我的研究结果如下:
我的问题是:您是否将DBC与Python一起用于成熟的生产代码?它的效果如何/值得努力?你会推荐哪些工具?
sni*_*im2 17
您找到的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
Run Code Online (Sandbox Code Playgroud)
现在,你提到了类不变量.这些有点困难,但我想要的方法是定义一个可调用来检查不变量,然后像每个方法调用结束时的后置条件装饰器检查那样不变.作为第一个剪辑你可能只是按原样使用后置条件装饰器.
Rob*_*ahl 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()
Run Code Online (Sandbox Code Playgroud)
我得到了平方根作为合同设计的一般例子,来自软件工程无线电设计合同的一集(http://www.se-radio.net/2007/03/episode-51-按合同设计/).他们还提到了语言支持的必要性,因为断言对确保Liskov替换原则没有帮助,尽管我上面的例子旨在证明其他原因.我还应该提到C++ pimpl(私有实现)习语作为灵感来源,尽管它有着完全不同的目的.
在我的工作中,我最近将这种合同检查重构为更大的类层次结构(合同已经记录,但没有系统地测试).现有的单元测试显示合同多次被违反.我只能得出结论,这应该是很久以前完成的,并且一旦按照合同设计,单位测试覆盖率就会更高.我希望那些尝试这种技术组合的人能够做出相同的观察.
更好的工具支持可能会在未来为我们提供更多的动力,我对此表示欢迎.
我们想在生产代码中使用前置/后置条件/不变式,但发现当前所有的按合同设计的库都缺乏信息性消息和适当的继承。
因此,我们开发了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
Run Code Online (Sandbox Code Playgroud)
我们发现该库在生产中(由于提供了有用的消息)和开发过程中都非常有用(因为它使您可以尽早发现错误)。