liveBook · Manning

Programming by contract

As explained in Software Design for Python Programmers

Programming by contract is a software development technique that specifies component obligations through preconditions, postconditions, and invariants.

Programming by contract makes a component’s obligations explicit through preconditions, postconditions, and class invariants. The resulting agreement reduces runtime surprises for callers and users. section 6.0 section 6.3

Apply the contract through three complementary obligations:

  • Check a precondition before calling a method. The caller is responsible for ensuring that the condition is true; otherwise, the method need not behave conveniently. section 6.3.2
  • Guarantee a postcondition before returning when the precondition was satisfied. The caller can then rely on successful behavior or on the specified exception contract. section 6.3.3
  • Maintain a class invariant before and after every state-mutating method, preventing objects from being left in invalid states. section 6.3

Circular buffer

A CircularBuffer requires space before add() and stored data before remove(): _count < _capacity and _count > 0. Its invariant keeps _head and _tail within capacity. Assertions check the contract, while client code tests preconditions before making calls. section 6.3.1 section 6.3.2

Listing 6.13 (Program 6.9 CircularBuffer-1): buffer.py
class CircularBuffer:

    def _class_invariant(self):    #1
        return (    (0 <= self._head < self._capacity)
                and (0 <= self._tail < self._capacity)
               )

    def __init__(self, capacity):
        self._capacity = capacity
        self._head = 0
        self._tail = 0
        self._count = 0
        self._buffer = [0]*capacity

        assert self._class_invariant(), \    #2
               'class invariant is false'     #2

    @property
    def count(self): return self._count

    def add_precondition(self):    #3
        return self._count < self._capacity

    def add_postcondition(self):    #4
        return self._count > 0

    def add(self, value):
        assert self.add_precondition(), \     #5
               'add_precondition is false'     #5

        self._buffer[self._tail] = value
        self._tail = (self._tail + 1)%self._capacity
        self._count += 1

        assert self.add_postcondition(), \     #6
               'add_postcondition is false'     #6
        assert self._class_invariant(), \    #7
               'class_invariant is false'    #7

    def remove_precondition(self):    #8
        return self._count > 0

    def remove_postcondition(self):    #9
        return self._count < self._capacity

    def remove(self):
        assert self.remove_precondition(), \     #10
               'remove_precondition is false'     #10

        value = self._buffer[self._head]
        self._head = (self._head + 1)%self._capacity
        self._count -= 1

        assert self.remove_postcondition(), \     #11
               'remove_postcondition is false'     #11
        assert self._class_invariant(), \    #12
               'class_invariant is false'      #12

        return value

Inspect the compact Python implementation showing an invariant, method-specific preconditions and postconditions, and assertion-based enforcement. section 6.3.1

Python has no native contract constructs, so assertions, public predicate methods, and exceptions can implement the technique. Use it selectively on critical classes rather than automatically on every class. section 6.3

Inheritance requires compatible contracts: subclass preconditions and postconditions must not conflict with expectations established by the superclass. section 7.5

How Software Design for Python Programmers covers it

How deep it goes
Defines three contract elements and demonstrates assertion-based implementation, while deferring native Python support and deployment strategy.
Examples
Demonstrates a CircularBuffer and Shipment subclasses with cost preconditions, day postconditions, assertions, and custom exceptions.
What it assumes
Assumes familiarity with classes and methods, while defining preconditions, postconditions, and invariants directly.
What you can do after
Apply it via Python assertions, predicate methods, and exceptions to specify and enforce critical class behavior.
How it connects
Connects preconditions, postconditions, class invariants, assertions, exceptions, inheritance, and the Code to the Interface Principle.

Also explained in

Compare how 3 books explain programming by contract →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Software Design for Python Programmers. The section links point to the parts of the book it draws on.