Using Design by Contract in Python

PythonDesign by-Contract

Python Problem Overview


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?

Python Solutions


Solution 1 - Python

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.

Solution 2 - Python

In my experience design-by-contract is worth doing, even without language support. For methods that aren't overridden assertions, along with docstrings are sufficient for both pre- and postconditions. For methods that are overridden we split the method in two: a public method which check the pre- and post-conditions, and a protected method which provide 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 (http://www.se-radio.net/2007/03/episode-51-design-by-contract/). They also mentioned the need for language support because assertions weren't helpful in ensuring 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 welcome that.

Solution 3 - Python

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.

Solution 4 - Python

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

Solution 5 - Python

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:

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionipartolaView Question on Stackoverflow
Solution 1 - Pythonsnim2View Answer on Stackoverflow
Solution 2 - PythonRobert Jørgensgaard EngdahlView Answer on Stackoverflow
Solution 3 - PythonjcolladoView Answer on Stackoverflow
Solution 4 - Pythonmarko.ristinView Answer on Stackoverflow
Solution 5 - PythonsastaninView Answer on Stackoverflow