This sample show how you can use intospection to create a function wraper that verify Eiffel like contracts.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | import inspect
def signature(function):
"""Build a string with source code of the function declaration"""
desc = inspect.getargspec(function)
if desc[3]:
ldefault = len(desc[3])
default = desc[3]
sign = ','.join(desc[0][:-ldefault])
else:
ldefault = 0
default=[]
sign = ','.join(desc[0])
for n,v in zip(desc[0][-ldefault:],default):
sign += ','+n+"="+str(v)
if desc[1]:
sign +=',*'+desc[1]
if desc[2]:
sign +=',**'+desc[2]
if sign and sign[0]==',': sign = sign[1:]
return sign
def callsignature(function):
"""Build a string with source code of the function call"""
desc = inspect.getargspec(function)
sign = ','.join(desc[0])
if desc[1]:
sign +=',*'+desc[1]
if desc[2]:
sign +=',**'+desc[2]
if sign and sign[0]==',': sign = sign[1:]
return sign
def contract(function,before,after):
"""Create a function wraper wich test Eiffel-like contract
before is a string that contain preconditions separed by ;
after is a string that contain postconditions
"""
if __debug__:
def mk_condition(code,text):
lc=code.split(';')
code_ins=''
for i in lc:
txt = i.strip()
if txt : code_ins +=' assert '+ txt + ",'" + txt + "'\n"
return code_ins
precondition = mk_condition (before,'precondition')
postcondition = mk_condition (after, 'postcondition')
real_function = function
source = ("def func_contract("+ signature(function) +"):\n" +
precondition +
" result = undercontract_"+function.func_name+"("+
callsignature(function)+")\n"+
postcondition+" return result\n")
exec source
globals()["undercontract_"+function.func_name]=function
return func_contract
else:
return function
# contract test case
def foo(x,y,g=2,z=1,*rt):
return x+y+z*g
foo = contract(foo,'x>1;0<y<100','result>0')
foo(12,31)
class bar:
def __init__(self):
self.x = 0
self.y = 0
def instr(self,n):
self.x = n+1
return n+self.y
instr=contract(instr,'n>1;self.y<10','self.y==0')
x=bar()
x.instr(2)
|
Python introspection is really powerfull and the recipe use the inspect module to dynamically create a function wraper wich verify the contract.In fact signature and callsignature functions can be used for any kind of wraper functions, not only for design by contract.
The basic idea of design by contract as implemented in the Eiffel language is that a system is being made of a number of complementary components -- classes -- which cooperate on the basis of precisely defined statements of mutual obligations and benefits: "contracts", as in contracts between two businesses.
Preconditions are the obligations that must respect the caller of the function. Postconditions are the benefits promited by the functions if preconditions are true.
Very Nice. This is a nice implementation of eiffel's contracts. It even works for __init__! But then again, why wouldn't it?
What about inherited methods. The contracts do not seem to be inherited by a overridden method as we have in Eiffel. I am a Python newbie so I am unable to figure it out myself and I would appreciate such a feature
Contratcs and inheritance. Unfortunately the recipe doesn't offer this kind of support. If you need to deal with derived classes, you should take a look at the files Eiffel.py in the directories Demo/metaclass and Demo/newmetaclass of Python distribution.
exec ? I am concerned about using exec. I suspect it would be very easy to make the application do something completely different by manipulating the data that is going to be passed to exec. I am afraid of a security leak.