在 Python 中使用契约设计

发布于 2024-12-22 04:59:26 字数 610 浏览 1 评论 0原文

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

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

I am looking to start using DBC on a large number of Python-based projects at work and am wondering what experiences others have had with it. So far my research turned up the following:

My questions are: have you used DBC with Python for mature production code? How well did it work/was it worth the effort? Which tools would you recommend?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

浅唱ヾ落雨殇 2024-12-29 04:59:26

您发现的 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

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

The PEP you found hasn't yet been accepted, so there isn't a standard or accepted way of doing this (yet -- you could always implement the PEP yourself!). However, there are a few different approaches, as you have found.

Probably the most light-weight is just to simply use Python decorators. There's a set of decorators for pre-/post-conditions in the Python Decorator Library that are quite straight-forward to use. Here's an example from that page:

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

Now, you mention class invariants. These are a bit more difficult, but the way I would go about it is to define a callable to check the invariant, then have something like the post-condition decorator check that invariant at the end of every method call. As a first cut you could probably just use the postcondition decorator as-is.

真心难拥有 2024-12-29 04:59:26

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

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

我从 软件工程广播中关于合同设计的一集。他们还提到了语言支持的必要性,声称断言无助于执行里氏替换原则,尽管我上面的例子旨在证明相反的情况。我还应该提及 C++ pimpl(私有实现)习惯用法作为灵感来源,尽管它具有完全不同的目的。

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

更好的工具支持可能会在未来为我们提供更强大的力量;我对此表示欢迎。

In my experience, design-by-contract is worth doing, even without language support. For methods that aren't designed to be overridden, assertions and docstrings are sufficient for both pre- and post-conditions. For methods that are designed to be overridden, we split the method in two: a public method which checks the pre- and post-conditions, and a protected method which provides the implementation, and may be overridden by subclasses. Here an example of the latter:

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

I got the square root as a general example of design-by-contract from an episode on design-by-contract on software-engineering radio. They also mentioned the need for language support, claiming that assertions don't help enforce the Liskov substitution principle, though my example above aims to demonstrate otherwise. I should also mention the C++ pimpl (private implementation) idiom as a source of inspiration, though that has an entirely different purpose.

In my work, I recently refactored this kind of contract-checking into a larger class hierarchy (the contract was already documented, but not systematically tested). Existing unit-tests revealed that the contracts were violated multiple times. I can only conclude this should have been done a long time ago, and that unit-test coverage pays off even more once design-by-contract is applied. I expect anyone who tries out this combination of techniques to make the same observations.

Better tool-support may offer us even more power in the future; I would welcome that.

撩发小公举 2024-12-29 04:59:26

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

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

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

We wanted to use pre/post-conditions/invariants in our production code, but found that all current design-by-contract libraries lacked informative messages and proper inheritance.

Therefore we developed icontract. The error messages are automatically generated by re-traversing the decompiled code of the function and evaluating all the involved values:

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

We found the library pretty useful both in the production (due to informative messages) and during the development (since it allows you to spot bugs early on).

百善笑为先 2024-12-29 04:59:26

我没有在Python中使用过契约设计,所以我无法回答你所有的问题。但是,我花了一些时间查看 contracts 库,其最新版本最近已发布,并且看起来很不错。

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

I haven't used design by contract in python, so I can't answer to all your questions. However, I've spent some time looking at contracts library, whose latest version has been released recently, and it looks pretty nice.

There was some discussion about this library in reddit.

木森分化 2024-12-29 04:59:26

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

随机测试某些属性在运行时是否成立,可以轻松检查:

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

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

While not exactly design by contract, some testing frameworks favour property testing approach are very close conceptually.

Randomized testing for if certain properties hold in runtime allows to easily check:

  • invariants
  • domains of input and output values
  • other pre- and postconditions

For Python there are some QuickCheck-style testing frameworks:

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文