Welcome, guest | Sign In | My Account | Store | Cart

This sample show how you can use intospection to create a function wraper that verify Eiffel like contracts.

Python, 86 lines
 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.

4 comments

Gene Chiaramonte 20 years ago  # | flag

Very Nice. This is a nice implementation of eiffel's contracts. It even works for __init__! But then again, why wouldn't it?

Alain Pointdexter 18 years, 8 months ago  # | flag

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

Sébastien Keim (author) 18 years, 8 months ago  # | flag

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.

Philippe Fremy 17 years, 2 months ago  # | flag

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.