| 
									
										
										
										
											2010-12-30 22:11:50 +00:00
										 |  |  | #!/usr/bin/env python3 | 
					
						
							| 
									
										
										
										
											2002-07-11 21:08:06 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-12-30 22:11:50 +00:00
										 |  |  | """
 | 
					
						
							|  |  |  | Support Eiffel-style preconditions and postconditions for functions. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | An example for Python metaclasses. | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import unittest | 
					
						
							| 
									
										
										
										
											2007-11-27 21:50:00 +00:00
										 |  |  | from types import FunctionType as function | 
					
						
							| 
									
										
										
										
											2002-07-11 21:08:06 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | class EiffelBaseMetaClass(type): | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2002-07-12 15:42:10 +00:00
										 |  |  |     def __new__(meta, name, bases, dict): | 
					
						
							|  |  |  |         meta.convert_methods(dict) | 
					
						
							| 
									
										
										
										
											2010-12-30 22:11:50 +00:00
										 |  |  |         return super(EiffelBaseMetaClass, meta).__new__( | 
					
						
							|  |  |  |             meta, name, bases, dict) | 
					
						
							| 
									
										
										
										
											2002-07-12 15:42:10 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-01-16 00:16:11 +00:00
										 |  |  |     @classmethod | 
					
						
							| 
									
										
										
										
											2002-07-11 21:08:06 +00:00
										 |  |  |     def convert_methods(cls, dict): | 
					
						
							|  |  |  |         """Replace functions in dict with EiffelMethod wrappers.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         The dict is modified in place. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         If a method ends in _pre or _post, it is removed from the dict | 
					
						
							|  |  |  |         regardless of whether there is a corresponding method. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         # find methods with pre or post conditions | 
					
						
							|  |  |  |         methods = [] | 
					
						
							| 
									
										
										
										
											2007-07-17 20:59:35 +00:00
										 |  |  |         for k, v in dict.items(): | 
					
						
							| 
									
										
										
										
											2002-07-11 21:08:06 +00:00
										 |  |  |             if k.endswith('_pre') or k.endswith('_post'): | 
					
						
							|  |  |  |                 assert isinstance(v, function) | 
					
						
							|  |  |  |             elif isinstance(v, function): | 
					
						
							|  |  |  |                 methods.append(k) | 
					
						
							|  |  |  |         for m in methods: | 
					
						
							|  |  |  |             pre = dict.get("%s_pre" % m) | 
					
						
							|  |  |  |             post = dict.get("%s_post" % m) | 
					
						
							|  |  |  |             if pre or post: | 
					
						
							| 
									
										
										
										
											2013-07-16 22:12:03 +03:00
										 |  |  |                 dict[m] = cls.make_eiffel_method(dict[m], pre, post) | 
					
						
							| 
									
										
										
										
											2002-07-11 21:08:06 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-12-30 22:11:50 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2002-07-11 21:17:26 +00:00
										 |  |  | class EiffelMetaClass1(EiffelBaseMetaClass): | 
					
						
							|  |  |  |     # an implementation of the "eiffel" meta class that uses nested functions | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-01-16 00:16:11 +00:00
										 |  |  |     @staticmethod | 
					
						
							| 
									
										
										
										
											2002-07-11 21:08:06 +00:00
										 |  |  |     def make_eiffel_method(func, pre, post): | 
					
						
							|  |  |  |         def method(self, *args, **kwargs): | 
					
						
							|  |  |  |             if pre: | 
					
						
							|  |  |  |                 pre(self, *args, **kwargs) | 
					
						
							| 
									
										
										
										
											2010-12-30 22:11:50 +00:00
										 |  |  |             rv = func(self, *args, **kwargs) | 
					
						
							| 
									
										
										
										
											2002-07-11 21:08:06 +00:00
										 |  |  |             if post: | 
					
						
							| 
									
										
										
										
											2010-12-30 22:11:50 +00:00
										 |  |  |                 post(self, rv, *args, **kwargs) | 
					
						
							|  |  |  |             return rv | 
					
						
							| 
									
										
										
										
											2002-07-11 21:08:06 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if func.__doc__: | 
					
						
							|  |  |  |             method.__doc__ = func.__doc__ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return method | 
					
						
							| 
									
										
										
										
											2004-07-18 05:56:09 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-12-30 22:11:50 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2002-07-11 21:08:06 +00:00
										 |  |  | class EiffelMethodWrapper: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, inst, descr): | 
					
						
							|  |  |  |         self._inst = inst | 
					
						
							|  |  |  |         self._descr = descr | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __call__(self, *args, **kwargs): | 
					
						
							|  |  |  |         return self._descr.callmethod(self._inst, args, kwargs) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-12-30 22:11:50 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | class EiffelDescriptor: | 
					
						
							| 
									
										
										
										
											2002-07-11 21:08:06 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, func, pre, post): | 
					
						
							|  |  |  |         self._func = func | 
					
						
							|  |  |  |         self._pre = pre | 
					
						
							|  |  |  |         self._post = post | 
					
						
							| 
									
										
										
										
											2004-07-18 05:56:09 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2002-07-11 21:08:06 +00:00
										 |  |  |         self.__name__ = func.__name__ | 
					
						
							|  |  |  |         self.__doc__ = func.__doc__ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-29 01:27:42 -07:00
										 |  |  |     def __get__(self, obj, cls=None): | 
					
						
							| 
									
										
										
										
											2002-07-11 21:08:06 +00:00
										 |  |  |         return EiffelMethodWrapper(obj, self) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def callmethod(self, inst, args, kwargs): | 
					
						
							|  |  |  |         if self._pre: | 
					
						
							|  |  |  |             self._pre(inst, *args, **kwargs) | 
					
						
							|  |  |  |         x = self._func(inst, *args, **kwargs) | 
					
						
							|  |  |  |         if self._post: | 
					
						
							|  |  |  |             self._post(inst, x, *args, **kwargs) | 
					
						
							|  |  |  |         return x | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-12-30 22:11:50 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2002-07-12 15:42:10 +00:00
										 |  |  | class EiffelMetaClass2(EiffelBaseMetaClass): | 
					
						
							| 
									
										
										
										
											2002-07-11 21:08:06 +00:00
										 |  |  |     # an implementation of the "eiffel" meta class that uses descriptors | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     make_eiffel_method = EiffelDescriptor | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-12-30 22:11:50 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | class Tests(unittest.TestCase): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def testEiffelMetaClass1(self): | 
					
						
							|  |  |  |         self._test(EiffelMetaClass1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def testEiffelMetaClass2(self): | 
					
						
							|  |  |  |         self._test(EiffelMetaClass2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _test(self, metaclass): | 
					
						
							|  |  |  |         class Eiffel(metaclass=metaclass): | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         class Test(Eiffel): | 
					
						
							|  |  |  |             def m(self, arg): | 
					
						
							|  |  |  |                 """Make it a little larger""" | 
					
						
							|  |  |  |                 return arg + 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             def m2(self, arg): | 
					
						
							|  |  |  |                 """Make it a little larger""" | 
					
						
							|  |  |  |                 return arg + 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             def m2_pre(self, arg): | 
					
						
							|  |  |  |                 assert arg > 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             def m2_post(self, result, arg): | 
					
						
							|  |  |  |                 assert result > arg | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         class Sub(Test): | 
					
						
							|  |  |  |             def m2(self, arg): | 
					
						
							|  |  |  |                 return arg**2 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             def m2_post(self, Result, arg): | 
					
						
							|  |  |  |                 super(Sub, self).m2_post(Result, arg) | 
					
						
							|  |  |  |                 assert Result < 100 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         t = Test() | 
					
						
							|  |  |  |         self.assertEqual(t.m(1), 2) | 
					
						
							|  |  |  |         self.assertEqual(t.m2(1), 2) | 
					
						
							|  |  |  |         self.assertRaises(AssertionError, t.m2, 0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         s = Sub() | 
					
						
							|  |  |  |         self.assertRaises(AssertionError, s.m2, 1) | 
					
						
							|  |  |  |         self.assertRaises(AssertionError, s.m2, 10) | 
					
						
							|  |  |  |         self.assertEqual(s.m2(5), 25) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2002-07-11 21:08:06 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__": | 
					
						
							| 
									
										
										
										
											2010-12-30 22:11:50 +00:00
										 |  |  |     unittest.main() |