PyContracts overhead in production and in testing

In production, all checks can be disabled using the function contracts.disable_all().

All checks can also be disabled via the DISABLE_CONTRACTS environment variable. If the variable is defined, then checks are disabled and cannot be enabled via contracts.enable_all().

Overhead when disabled

The performance in disabled mode depends on whether contracts.disable_all() is called before or after the decorated function is loaded.

If disable_all() is called before, then the decorator gets ignored and the overhead is 0. Example:

contracts.disable_all()

@contract(x='int,>=0', returns='int,>=1')
def f(x):
    return x + 1

f(0) # no overhead

If disable_all() is called after, then there is some overhead, because PyContracts will call the function as f(*args,**kwargs), which has some overhead. Example:

@contract(x='int,>=0', returns='int,>=1')
def f(x):
    return x + 1

contracts.disable_all()

f(0) # some overhead

Performance hit when enabled

I haven’t done any rigorous benchmarking, but PyContract’s performance hit depends mainly on two factors:

  1. PyContracts calls inspect methods to associate arguments to arguments names. This takes a surprising amount of time and probably there is a smarter way to do it.

  2. The time to check the contract for each argument. This depends on the complexity of the contract.

    Each contract is parsed only once and then represented as a syntactic tree. For example, the representation of int,>0 is:

    python -c "import contracts; print '%r' % contracts.parse('int,>=0')"
    And([CheckType(int), CheckOrder(None,'>=',SimpleRValue(0))])
    

    Python being Python, there’s some overhead for calling object methods in the contract tree.