在Python中使用契约式设计
我希望在大量基于Python的项目中开始使用DBC,我想知道其他人在这方面有什么经验。到目前为止,我的研究结果如下:在Python中使用契约式设计,python,design-by-contract,Python,Design By Contract,我希望在大量基于Python的项目中开始使用DBC,我想知道其他人在这方面有什么经验。到目前为止,我的研究结果如下: -PEP 316,它应该为Python标准化合同设计,但已被推迟。本PEP建议使用docstring -Python合同。这似乎是一个使用docstring的完整但未维护的框架 -PyDBC,它使用元类实现契约。也有几年没有保养 我的问题是:您是否将DBC与Python一起用于成熟的生产代码?它的效果如何/值得付出努力吗?你会推荐哪些工具 我没有在python中使用契约式设计
- -PEP 316,它应该为Python标准化合同设计,但已被推迟。本PEP建议使用docstring
- -Python合同。这似乎是一个使用docstring的完整但未维护的框架
- -PyDBC,它使用元类实现契约。也有几年没有保养
我的问题是:您是否将DBC与Python一起用于成熟的生产代码?它的效果如何/值得付出努力吗?你会推荐哪些工具 我没有在python中使用契约式设计,所以我无法回答您所有的问题。然而,我花了一些时间研究了这个库,它的最新版本最近已经发布,看起来非常不错
中有一些关于此库的讨论。您找到的PEP尚未被接受,因此没有一种标准或公认的方法来实现这一点(但是--您始终可以自己实现PEP!)。然而,正如您所发现的,有几种不同的方法 最轻量级的可能只是简单地使用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
>>def在20(无效)中:
... 断言无效>=20,“输入值<20”
...
>>>def输出lt30(返回、无效):
... 断言retval<30,“返回值>=30”
...
>>>@前提条件(in_ge20)
... @后置条件(输出lt30)
... def公司(价值):
... 返回值+1
...
>>>公司(5)
回溯(最近一次呼叫最后一次):
...
断言错误:输入值<20
现在,你提到了类不变量。这有点困难,但我要做的是定义一个可调用的方法来检查不变量,然后在每个方法调用结束时让类似于post-condition decorator的东西来检查不变量。作为第一个切入点,您可能只需按原样使用postcondition decorator。虽然不完全按合同设计,但一些测试框架支持属性测试方法,并且在概念上非常接近
随机测试某些属性是否在运行时保持,可以轻松检查:
- 不变量
- 输入和输出值的域
- 其他前置和后置条件
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()
课堂数学:
def平方根(自身,数字)
"""
计算C{number}的平方根
@前提条件:C{number>=0}
@后置条件:C{abs(结果*结果-数字)<0.01}
"""
断言编号>=0
结果=自平方根(数字)
断言abs(结果*结果-数字)<0.01
返回结果
定义平方根(自身,数字):
"""
实现L{square_root()}的抽象方法
"""
引发未实现的错误()
我从软件工程电台()上的一集“按合同设计”中得到了平方根作为按合同设计的一般示例。他们还提到了语言支持的必要性,因为断言对确保Liskov替换原则没有帮助,尽管我上面的示例旨在说明其他方面。我还应该提到C++ PIPML(私有实现)习语作为灵感来源,尽管这有一个完全不同的用途。
在我的工作中,我最近将这种契约检查重构到一个更大的类层次结构中(契约已经有文档记录,但没有经过系统测试)。现有的单元测试显示合同多次被违反。我只能得出这样的结论:这应该在很久以前就完成了,而且一旦契约式设计被应用,单元测试的覆盖率将得到更大的回报。我希望任何尝试这种技术组合的人都能做出同样的观察
更好的工具支持可能在将来为我们提供更多的功能,我对此表示欢迎。我们希望在生产代码中使用前置/后置条件/不变量,但发现所有当前的契约式设计库都缺乏信息性消息和适当的继承 因此我们发展了。通过重新遍历函数的反编译代码并计算所有涉及的值,自动生成错误消息:
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
我们发现该库在生产过程中(由于信息性消息)和开发过程中(因为它允许您在早期发现bug)都非常有用。请注意,您可以从TestCase继承,并在任何类中包含单元测试。对,但DBC有点不同,它将在生产和所有数据输入中运行检查。据我所知,单元测试是具有预定义数据集的运行时断言,而DBC是具有所有输入的断言之上的级别。明确地我认为在我的例子中使用DBC是有意义的,因为很多代码实际上都是重状态的,并且经常必须从外部数据库获取状态,该数据库具有频繁变化的模式和相当复杂的关系,这些关系对于模型来说非常混乱。契约式设计就是明确指定每段代码所遵循的规范。您不必在运行时对其进行完整测试。单元测试可以是该规范的一部分,也可以是其他规范的一部分。TDD是一个不同的概念