Hooks, aspect-oriented programming, and design by contract

Gustavo Niemeyer niemeyer at conectiva.com
Tue Jan 22 15:19:25 EST 2002


> Yes... you're right. I forgot about the __init__ stuff. Here's a fixed
> version (note the changes in ContractInstance.__init__) that will
> probably do the job.

Oops... forgot about parameters. Here is a last one including parameters
support for __init__().

I-should-publish-this-somewhere-ly y'rs

-- 
Gustavo Niemeyer

[ 2AAC 7928 0FBF 0299 5EB5  60E2 2253 B29A 6664 3A0C ]
-------------- next part --------------
from types import FunctionType

CONTRACT_NONE = 0
CONTRACT_PRE = 1
CONTRACT_POST = 2
CONTRACT_INV = 3

ContractLevel = CONTRACT_INV

# Trick to work with 2.1 and 2.2
if not __builtins__.__dict__.get("object"):
	class object: pass

class ContractMeta(object):
	def __init__(self, name, bases, namespace):
		self.__name__ = name
		self.__bases__ = bases
		self.__namespace__ = namespace
	
	def __call__(self, *args, **kw):
		return ContractInstance(self, *args, **kw)

class ContractInstance:
	def __init__(self, klass, *args, **kw):
		self.__klass__ = klass
		init = klass.__namespace__.get("__init__")
		if init:
			init(self, *args, **kw)
	
	def __getattr__(self, name):
		namespace = self.__klass__.__namespace__
		try:
			attr = namespace[name]
		except KeyError:
			raise AttributeError, name
		if type(attr) is FunctionType:
			inv = namespace.get("invariant")
			if name == "invariant":
				return ContractSpecialMethod(attr, self, None, ContractInvError)
			if name[-4:] == "_pre":
				return ContractSpecialMethod(attr, self, inv, ContractPreError)
			if name[-5:] == "_post":
				return ContractSpecialMethod(attr, self, inv, ContractPostError)
			pre = namespace.get(name+"_pre")
			post = namespace.get(name+"_post")
			return ContractMethod(name, attr, self, inv, pre, post)
		return attr

class ContractSpecialMethod:
	"""Handle calling of pre/post conditions and invariant."""

	def __init__(self, function, instance, invariant, exception):
		self.__function = function
		self.__instance = instance
		self.__invariant = invariant
		self.__exception = exception
	
	def __call__(self, *parm, **kw):
		if self.__invariant:
			try:
				self.__invariant(self.__instance)
			except AssertionError, msg:
				raise ContractInvError(msg)
		try:
			self.__function(*(self.__instance,)+parm, **kw)
		except AssertionError, msg:
			raise self.__exception(msg)

class ContractMethod:
	"""Handle calling of usual methods."""
	
	def __init__(self, name, function, instance, inv, pre, post):
		self.__name = name
		self.__function = function
		self.__instance = instance
		self.__inv = inv
		self.__pre = pre
		self.__post = post
	
	def __call__(self, *parm, **kw):
		namespace = self.__instance.__klass__.__namespace__

		if self.__inv:
			try: 
				self.__inv(self.__instance)
			except AssertionError, msg:
				raise ContractInvError(msg)
		
		if self.__pre:
			try: 
				self.__pre(*(self.__instance,)+parm, **kw)
			except AssertionError, msg:
				raise ContractPreError(msg)
		
		result = self.__function(*(self.__instance,)+parm, **kw)
		
		if self.__post:
			try:
				self.__post(*(self.__instance, result)+parm, **kw)
			except AssertionError, msg:
				raise ContractPostError(msg)
		
		if self.__inv:
			try: 
				self.__inv(self.__instance)
			except AssertionError, msg:
				raise ContractInvError(msg)

		return result

class ContractError(AssertionError): pass
class ContractPreError(ContractError): pass
class ContractPostError(ContractError): pass
class ContractInvError(ContractError): pass

Contract = ContractMeta("Contract", (), {})

if __name__ == "__main__":
	# Usage example
	class A(Contract):
		def __init__(self, x):
			self.x = x

		def invariant(self):
			assert self.x == 0
			print "invariant()"
		
		def a(self):
			self.x = 1
			print "a()"
		
		def a_pre(self):
			print "a_pre()"
		
		def a_post(self, result):
			print "a_post()"
	
	a = A(0)
	a.a()

# vim:ts=4:sw=4
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 240 bytes
Desc: not available
URL: <http://mail.python.org/pipermail/python-list/attachments/20020122/7a570bd6/attachment.sig>


More information about the Python-list mailing list