2003-06-09 00:04:03 -04:00
|
|
|
|
PEP: 316
|
|
|
|
|
Title: Programming by Contract for Python
|
|
|
|
|
Version: $Revision$
|
|
|
|
|
Last-Modified: $Date$
|
|
|
|
|
Author: Terence Way <terry@wayforward.net>
|
2003-06-09 00:34:03 -04:00
|
|
|
|
Status: Deferred
|
2003-06-09 00:04:03 -04:00
|
|
|
|
Type: Standards Track
|
|
|
|
|
Content-Type: text/x-rst
|
|
|
|
|
Created: 02-May-2003
|
|
|
|
|
Post-History:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Abstract
|
|
|
|
|
========
|
|
|
|
|
|
|
|
|
|
This submission describes programming by contract for Python.
|
|
|
|
|
Eiffel's Design By Contract(tm) is perhaps the most popular use of
|
|
|
|
|
programming contracts [#dbc]_.
|
|
|
|
|
|
|
|
|
|
Programming contracts extends the language to include invariant
|
|
|
|
|
expressions for classes and modules, and pre- and post-condition
|
|
|
|
|
expressions for functions and methods.
|
|
|
|
|
|
2003-06-09 00:34:03 -04:00
|
|
|
|
These expressions (contracts) are similar to assertions: they must be
|
|
|
|
|
true or the program is stopped, and run-time checking of the contracts
|
|
|
|
|
is typically only enabled while debugging. Contracts are higher-level
|
|
|
|
|
than straight assertions and are typically included in documentation.
|
2003-06-09 00:04:03 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Motivation
|
|
|
|
|
==========
|
|
|
|
|
|
|
|
|
|
Python already has assertions, why add extra stuff to the language to
|
|
|
|
|
support something like contracts? The two best reasons are 1) better,
|
|
|
|
|
more accurate documentation, and 2) easier testing.
|
|
|
|
|
|
|
|
|
|
Complex modules and classes never seem to be documented quite right.
|
|
|
|
|
The documentation provided may be enough to convince a programmer to
|
|
|
|
|
use a particular module or class over another, but the programmer
|
|
|
|
|
almost always has to read the source code when the real debugging
|
|
|
|
|
starts.
|
|
|
|
|
|
|
|
|
|
Contracts extend the excellent example provided by the ``doctest``
|
|
|
|
|
module [#doctest]_. Documentation is readable by programmers, yet has
|
|
|
|
|
executable tests embedded in it.
|
|
|
|
|
|
|
|
|
|
Testing code with contracts is easier too. Comprehensive contracts
|
|
|
|
|
are equivalent to unit tests [#xp]_. Tests exercise the full range of
|
|
|
|
|
pre-conditions, and fail if the post-conditions are triggered.
|
|
|
|
|
Theoretically, a correctly specified function can be tested completely
|
|
|
|
|
randomly.
|
|
|
|
|
|
|
|
|
|
So why add this to the language? Why not have several different
|
|
|
|
|
implementations, or let programmers implement their own assertions?
|
2003-06-09 00:34:03 -04:00
|
|
|
|
The answer is the behavior of contracts under inheritance.
|
|
|
|
|
|
|
|
|
|
Suppose Alice and Bob use different assertions packages. If Alice
|
|
|
|
|
produces a class library protected by assertions, Bob cannot derive
|
|
|
|
|
classes from Alice's library and expect proper checking of
|
|
|
|
|
post-conditions and invariants. If they both use the same assertions
|
|
|
|
|
package, then Bob can override Alice's methods yet still test against
|
|
|
|
|
Alice's contract assertions. The natural place to find this
|
2003-06-09 00:04:03 -04:00
|
|
|
|
assertions system is in the language's run-time library.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Specification
|
|
|
|
|
=============
|
|
|
|
|
|
|
|
|
|
The docstring of any module or class can include invariant contracts
|
|
|
|
|
marked off with a line that starts with the keyword ``inv`` followed
|
|
|
|
|
by a colon (:). Whitespace at the start of the line and around the
|
|
|
|
|
colon is ignored. The colon is either immediately followed by a
|
|
|
|
|
single expression on the same line, or by a series of expressions on
|
|
|
|
|
following lines indented past the ``inv`` keyword. The normal Python
|
|
|
|
|
rules about implicit and explicit line continuations are followed
|
|
|
|
|
here. Any number of invariant contracts can be in a docstring.
|
|
|
|
|
|
|
|
|
|
Some examples::
|
|
|
|
|
|
|
|
|
|
# state enumeration
|
|
|
|
|
START, CONNECTING, CONNECTED, CLOSING, CLOSED = range(5)
|
|
|
|
|
|
|
|
|
|
class conn:
|
2003-06-09 00:34:03 -04:00
|
|
|
|
|
2003-06-09 00:04:03 -04:00
|
|
|
|
"""A network connection
|
|
|
|
|
|
|
|
|
|
inv: self.state in [START, CLOSED, # closed states
|
|
|
|
|
CONNECTING, CLOSING, # transition states
|
|
|
|
|
CONNECTED]
|
|
|
|
|
|
|
|
|
|
inv: 0 <= self.seqno < 256
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
class circbuf:
|
2003-06-09 00:34:03 -04:00
|
|
|
|
|
2003-06-09 00:04:03 -04:00
|
|
|
|
"""A circular buffer.
|
|
|
|
|
|
|
|
|
|
inv:
|
|
|
|
|
# there can be from 0 to max items on the buffer
|
|
|
|
|
0 <= self.len <= len(self.buf)
|
|
|
|
|
|
|
|
|
|
# g is a valid index into buf
|
|
|
|
|
0 <= self.g < len(self.buf)
|
|
|
|
|
|
|
|
|
|
# p is also a valid index into buf
|
|
|
|
|
0 <= self.p < len(self.buf)
|
|
|
|
|
|
|
|
|
|
# there are len items between get and put
|
|
|
|
|
(self.p - self.g) % len(self.buf) == \
|
|
|
|
|
self.len % len(self.buf)
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
Module invariants must be true after the module is loaded, and at the
|
|
|
|
|
entry and exit of every public function within the module.
|
|
|
|
|
|
|
|
|
|
Class invariants must be true after the ``__init__`` function returns,
|
|
|
|
|
at the entry of the ``__del__`` function, and at the entry and exit of
|
|
|
|
|
every other public method of the class. Class invariants must use the
|
|
|
|
|
self variable to access instance variables.
|
|
|
|
|
|
|
|
|
|
A method or function is public if its name doesn't start with an
|
|
|
|
|
underscore (_), unless it starts and ends with '__' (two underscores).
|
|
|
|
|
|
|
|
|
|
The docstring of any function or method can have pre-conditions
|
|
|
|
|
documented with the keyword ``pre`` following the same rules above.
|
|
|
|
|
Post-conditions are documented with the keyword ``post`` optionally
|
|
|
|
|
followed by a list of variables. The variables are in the same scope
|
|
|
|
|
as the body of the function or method. This list declares the
|
|
|
|
|
variables that the function/method is allowed to modify.
|
|
|
|
|
|
|
|
|
|
An example::
|
|
|
|
|
|
|
|
|
|
class circbuf:
|
2003-06-09 00:34:03 -04:00
|
|
|
|
|
2003-06-09 00:04:03 -04:00
|
|
|
|
def __init__(self, leng):
|
|
|
|
|
"""Construct an empty circular buffer.
|
|
|
|
|
|
|
|
|
|
pre: leng > 0
|
|
|
|
|
post[self]:
|
|
|
|
|
self.is_empty()
|
|
|
|
|
len(self.buf) == leng
|
|
|
|
|
"""
|
|
|
|
|
|
2003-06-09 00:34:03 -04:00
|
|
|
|
A double-colon (::) can be used instead of a single colon (:) to
|
|
|
|
|
support docstrings written using reStructuredText [#rst]_. For
|
|
|
|
|
example, the following two docstrings describe the same contract::
|
|
|
|
|
|
|
|
|
|
"""pre: leng > 0"""
|
|
|
|
|
"""pre:: leng > 0"""
|
|
|
|
|
|
2003-06-09 00:04:03 -04:00
|
|
|
|
Expressions in pre- and post-conditions are defined in the module
|
|
|
|
|
namespace -- they have access to nearly all the variables that the
|
|
|
|
|
function can access, except closure variables.
|
|
|
|
|
|
|
|
|
|
The contract expressions in post-conditions have access to two
|
|
|
|
|
additional variables: ``__old__`` which is filled with shallow copies
|
|
|
|
|
of values declared in the variable list immediately following the post
|
|
|
|
|
keyword, and ``__return__`` which is bound to the return value of the
|
|
|
|
|
function or method.
|
|
|
|
|
|
|
|
|
|
An example::
|
|
|
|
|
|
|
|
|
|
class circbuf:
|
2003-06-09 00:34:03 -04:00
|
|
|
|
|
2003-06-09 00:04:03 -04:00
|
|
|
|
def get(self):
|
|
|
|
|
"""Pull an entry from a non-empty circular buffer.
|
|
|
|
|
|
|
|
|
|
pre: not self.is_empty()
|
|
|
|
|
post[self.g, self.len]:
|
|
|
|
|
__return__ == self.buf[__old__.self.g]
|
|
|
|
|
self.len == __old__.self.len - 1
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
All contract expressions have access to some additional convenience
|
|
|
|
|
functions. To make evaluating the truth of sequences easier, two
|
|
|
|
|
functions ``forall`` and ``exists`` are defined as::
|
|
|
|
|
|
|
|
|
|
def forall(a, fn = bool):
|
|
|
|
|
"""Return True only if all elements in a are true.
|
|
|
|
|
|
|
|
|
|
>>> forall([])
|
|
|
|
|
1
|
|
|
|
|
>>> even = lambda x: x % 2 == 0
|
|
|
|
|
>>> forall([2, 4, 6, 8], even)
|
|
|
|
|
1
|
|
|
|
|
>>> forall('this is a test'.split(), lambda x: len(x) == 4)
|
|
|
|
|
0
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def exists(a, fn = bool):
|
|
|
|
|
"""Returns True if there is at least one true value in a.
|
|
|
|
|
|
|
|
|
|
>>> exists([])
|
|
|
|
|
0
|
|
|
|
|
>>> exists('this is a test'.split(), lambda x: len(x) == 4)
|
|
|
|
|
1
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
An example::
|
|
|
|
|
|
|
|
|
|
def sort(a):
|
|
|
|
|
"""Sort a list.
|
|
|
|
|
|
|
|
|
|
pre: isinstance(a, type(list))
|
|
|
|
|
post[a]:
|
|
|
|
|
# array size is unchanged
|
|
|
|
|
len(a) == len(__old__.a)
|
|
|
|
|
|
|
|
|
|
# array is ordered
|
|
|
|
|
forall([a[i] >= a[i-1] for i in range(1, len(a))])
|
|
|
|
|
|
|
|
|
|
# all the old elements are still in the array
|
|
|
|
|
forall(__old__.a, lambda e: __old__.a.count(e) == a.count(e))
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
To make evaluating conditions easier, the function ``implies`` is
|
|
|
|
|
defined. With two arguments, this is similar to the logical implies
|
|
|
|
|
(=>) operator. With three arguments, this is similar to C's
|
|
|
|
|
conditional expression (x?a:b). This is defined as::
|
|
|
|
|
|
|
|
|
|
implies(False, a) => True
|
|
|
|
|
implies(True, a) => a
|
|
|
|
|
implies(False, a, b) => b
|
|
|
|
|
implies(True, a, b) => a
|
|
|
|
|
|
|
|
|
|
On entry to a function, the function's pre-conditions are checked. An
|
|
|
|
|
assertion error is raised if any pre-condition is false. If the
|
|
|
|
|
function is public, then the class or module's invariants are also
|
|
|
|
|
checked. Copies of variables declared in the post are saved, the
|
|
|
|
|
function is called, and if the function exits without raising an
|
|
|
|
|
exception, the post-conditions are checked.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Exceptions
|
|
|
|
|
----------
|
|
|
|
|
|
|
|
|
|
Class/module invariants are checked even if a function or method exits
|
|
|
|
|
by signalling an exception (post-conditions are not).
|
|
|
|
|
|
|
|
|
|
All failed contracts raise exceptions which are subclasses of the
|
|
|
|
|
``ContractViolationError`` exception, which is in turn a subclass of the
|
|
|
|
|
``AssertionError`` exception. Failed pre-conditions raise a
|
|
|
|
|
``PreconditionViolationError`` exception. Failed post-conditions raise
|
|
|
|
|
a ``PostconditionViolationError`` exception, and failed invariants raise
|
|
|
|
|
a ``InvariantViolationError`` exception.
|
|
|
|
|
|
|
|
|
|
The class hierarchy::
|
|
|
|
|
|
|
|
|
|
AssertionError
|
|
|
|
|
ContractViolationError
|
|
|
|
|
PreconditionViolationError
|
|
|
|
|
PostconditionViolationError
|
|
|
|
|
InvariantViolationError
|
2003-06-09 00:34:03 -04:00
|
|
|
|
InvalidPreconditionError
|
|
|
|
|
|
|
|
|
|
The ``InvalidPreconditionError`` is raised when pre-conditions are
|
|
|
|
|
illegally strengthened, see the next section on Inheritance.
|
2003-06-09 00:04:03 -04:00
|
|
|
|
|
|
|
|
|
Example::
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
some_func()
|
|
|
|
|
except contract.PreconditionViolationError:
|
|
|
|
|
# failed pre-condition, ok
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Inheritance
|
|
|
|
|
-----------
|
|
|
|
|
|
|
|
|
|
A class's invariants include all the invariants for all super-classes
|
|
|
|
|
(class invariants are ANDed with super-class invariants). These
|
|
|
|
|
invariants are checked in method-resolution order.
|
|
|
|
|
|
|
|
|
|
A method's post-conditions also include all overridden post-conditions
|
|
|
|
|
(method post-conditions are ANDed with all overridden method
|
|
|
|
|
post-conditions).
|
|
|
|
|
|
2003-06-09 00:34:03 -04:00
|
|
|
|
An overridden method's pre-conditions can be ignored if the overriding
|
|
|
|
|
method's pre-conditions are met. However, if the overriding method's
|
|
|
|
|
pre-conditions fail, *all* of the overridden method's pre-conditions
|
|
|
|
|
must also fail. If not, a separate exception is raised, the
|
|
|
|
|
InvalidPreconditionError. This supports weakening pre-conditions.
|
2003-06-09 00:04:03 -04:00
|
|
|
|
|
|
|
|
|
A somewhat contrived example::
|
|
|
|
|
|
|
|
|
|
class SimpleMailClient:
|
2003-06-09 00:34:03 -04:00
|
|
|
|
|
2003-06-09 00:04:03 -04:00
|
|
|
|
def send(self, msg, dest):
|
|
|
|
|
"""Sends a message to a destination:
|
|
|
|
|
|
|
|
|
|
pre: self.is_open() # we must have an open connection
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def recv(self):
|
|
|
|
|
"""Gets the next unread mail message.
|
|
|
|
|
|
|
|
|
|
Returns None if no message is available.
|
|
|
|
|
|
|
|
|
|
pre: self.is_open() # we must have an open connection
|
2021-09-21 15:55:36 -04:00
|
|
|
|
post: __return__ is None or isinstance(__return__, Message)
|
2003-06-09 00:04:03 -04:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
class ComplexMailClient(SimpleMailClient):
|
|
|
|
|
def send(self, msg, dest):
|
|
|
|
|
"""Sends a message to a destination.
|
|
|
|
|
|
|
|
|
|
The message is sent immediately if currently connected.
|
|
|
|
|
Otherwise, the message is queued locally until a
|
|
|
|
|
connection is made.
|
|
|
|
|
|
|
|
|
|
pre: True # weakens the pre-condition from SimpleMailClient
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def recv(self):
|
|
|
|
|
"""Gets the next unread mail message.
|
|
|
|
|
|
|
|
|
|
Waits until a message is available.
|
|
|
|
|
|
|
|
|
|
pre: True # can always be called
|
|
|
|
|
post: isinstance(__return__, Message)
|
|
|
|
|
"""
|
|
|
|
|
|
2003-06-09 00:34:03 -04:00
|
|
|
|
Because pre-conditions can only be weakened, a ``ComplexMailClient`` can
|
|
|
|
|
replace a ``SimpleMailClient`` with no fear of breaking existing code.
|
2003-06-09 00:04:03 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rationale
|
|
|
|
|
=========
|
|
|
|
|
|
|
|
|
|
Except for the following differences, programming-by-contract for
|
|
|
|
|
Python mirrors the Eiffel DBC specification [#oosc]_.
|
|
|
|
|
|
|
|
|
|
Embedding contracts in docstrings is patterned after the doctest
|
|
|
|
|
module. It removes the need for extra syntax, ensures that programs
|
|
|
|
|
with contracts are backwards-compatible, and no further work is
|
|
|
|
|
necessary to have the contracts included in the docs.
|
|
|
|
|
|
|
|
|
|
The keywords ``pre``, ``post``, and ``inv`` were chosen instead of the
|
|
|
|
|
Eiffel-style ``REQUIRE``, ``ENSURE``, and ``INVARIANT`` because
|
|
|
|
|
they're shorter, more in line with mathematical notation, and for a
|
|
|
|
|
more subtle reason: the word 'require' implies caller
|
|
|
|
|
responsibilities, while 'ensure' implies provider guarantees. Yet
|
|
|
|
|
pre-conditions can fail through no fault of the caller when using
|
|
|
|
|
multiple inheritance, and post-conditions can fail through no fault of
|
|
|
|
|
the function when using multiple threads.
|
|
|
|
|
|
|
|
|
|
Loop invariants as used in Eiffel are unsupported. They're a pain to
|
|
|
|
|
implement, and not part of the documentation anyway.
|
|
|
|
|
|
|
|
|
|
The variable names ``__old__`` and ``__return__`` were picked to avoid
|
|
|
|
|
conflicts with the ``return`` keyword and to stay consistent with
|
|
|
|
|
Python naming conventions: they're public and provided by the Python
|
|
|
|
|
implementation.
|
|
|
|
|
|
|
|
|
|
Having variable declarations after a post keyword describes exactly
|
|
|
|
|
what the function or method is allowed to modify. This removes the
|
|
|
|
|
need for the ``NoChange`` syntax in Eiffel, and makes the
|
|
|
|
|
implementation of ``__old__`` much easier. It also is more in line
|
|
|
|
|
with Z schemas [#z]_, which are divided into two parts: declaring what
|
|
|
|
|
changes followed by limiting the changes.
|
|
|
|
|
|
|
|
|
|
Shallow copies of variables for the ``__old__`` value prevent an
|
|
|
|
|
implementation of contract programming from slowing down a system too
|
|
|
|
|
much. If a function changes values that wouldn't be caught by a
|
|
|
|
|
shallow copy, it can declare the changes like so::
|
|
|
|
|
|
|
|
|
|
post[self, self.obj, self.obj.p]
|
|
|
|
|
|
|
|
|
|
The ``forall``, ``exists``, and ``implies`` functions were added after
|
|
|
|
|
spending some time documenting existing functions with contracts.
|
|
|
|
|
These capture a majority of common specification idioms. It might
|
|
|
|
|
seem that defining ``implies`` as a function might not work (the
|
|
|
|
|
arguments are evaluated whether needed or not, in contrast with other
|
|
|
|
|
boolean operators), but it works for contracts since there should be
|
|
|
|
|
no side-effects for any expression in a contract.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Reference Implementation
|
|
|
|
|
========================
|
|
|
|
|
|
|
|
|
|
A reference implementation is available [#imp]_. It replaces existing
|
|
|
|
|
functions with new functions that do contract checking, by directly
|
|
|
|
|
changing the class' or module's namespace.
|
|
|
|
|
|
|
|
|
|
Other implementations exist that either hack ``__getattr__`` [#dbc4p]_
|
|
|
|
|
or use ``__metaclass__`` [#pydbc]_.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
References
|
|
|
|
|
==========
|
|
|
|
|
|
2003-06-09 00:34:03 -04:00
|
|
|
|
.. [#imp] Implementation described in this document.
|
|
|
|
|
(http://www.wayforward.net/pycontract/)
|
|
|
|
|
|
2003-06-09 00:04:03 -04:00
|
|
|
|
.. [#dbc] Design By Contract is a registered trademark of Eiffel
|
|
|
|
|
Software Inc.
|
|
|
|
|
(http://archive.eiffel.com/doc/manuals/technology/contract/)
|
|
|
|
|
|
|
|
|
|
.. [#oosc] Object-oriented Software Construction, Bertrand Meyer,
|
|
|
|
|
ISBN 0-13-629031-0
|
|
|
|
|
|
2008-10-02 08:51:05 -04:00
|
|
|
|
.. [#doctest] http://docs.python.org/library/doctest.html
|
2003-06-09 00:04:03 -04:00
|
|
|
|
doctest -- Test docstrings represent reality
|
|
|
|
|
|
|
|
|
|
.. [#dbc4p] Design by Contract for Python, R. Plosch
|
|
|
|
|
*IEEE Proceedings of the Joint Asia Pacific Software Engineering
|
|
|
|
|
Conference (APSEC97/ICSC97), Hong Kong, December 2-5, 1997*
|
|
|
|
|
(http://www.swe.uni-linz.ac.at/publications/abstract/TR-SE-97.24.html)
|
|
|
|
|
|
|
|
|
|
.. [#pydbc] PyDBC -- Design by Contract for Python 2.2+,
|
|
|
|
|
Daniel Arbuckle
|
|
|
|
|
(http://www.nongnu.org/pydbc/)
|
|
|
|
|
|
2003-06-09 00:34:03 -04:00
|
|
|
|
.. [#rst] ReStructuredText (http://docutils.sourceforge.net/rst.html)
|
2003-06-09 00:04:03 -04:00
|
|
|
|
|
|
|
|
|
.. [#xp] Extreme Programming Explained, Kent Beck,
|
|
|
|
|
ISBN 0-201-61641-6
|
|
|
|
|
|
|
|
|
|
.. [#z] The Z Notation, Second Edition, J.M. Spivey
|
|
|
|
|
ISBN 0-13-978529-9
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Copyright
|
|
|
|
|
=========
|
|
|
|
|
|
|
|
|
|
This document has been placed in the public domain.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
..
|
|
|
|
|
Local Variables:
|
|
|
|
|
mode: indented-text
|
|
|
|
|
indent-tabs-mode: nil
|
|
|
|
|
sentence-end-double-space: t
|
|
|
|
|
fill-column: 70
|
|
|
|
|
End:
|