summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Demo/metaclasses/Eiffel.py113
-rw-r--r--Demo/metaclasses/Enum.py6
-rw-r--r--Demo/metaclasses/Meta.py106
-rw-r--r--Demo/metaclasses/Trace.py29
4 files changed, 248 insertions, 6 deletions
diff --git a/Demo/metaclasses/Eiffel.py b/Demo/metaclasses/Eiffel.py
new file mode 100644
index 0000000..e3efa7f
--- /dev/null
+++ b/Demo/metaclasses/Eiffel.py
@@ -0,0 +1,113 @@
+"""Support Eiffel-style preconditions and postconditions.
+
+For example,
+
+class C:
+ def m1(self, arg):
+ require arg > 0
+ return whatever
+ ensure Result > arg
+
+can be written (clumsily, I agree) as:
+
+class C(Eiffel):
+ def m1(self, arg):
+ return whatever
+ def m1_pre(self, arg):
+ assert arg > 0
+ def m1_post(self, Result, arg):
+ assert Result > arg
+
+Pre- and post-conditions for a method, being implemented as methods
+themselves, are inherited independently from the method. This gives
+much of the same effect of Eiffel, where pre- and post-conditions are
+inherited when a method is overridden by a derived class. However,
+when a derived class in Python needs to extend a pre- or
+post-condition, it must manually merge the base class' pre- or
+post-condition with that defined in the derived class', for example:
+
+class D(C):
+ def m1(self, arg):
+ return whatever**2
+ def m1_post(self, Result, arg):
+ C.m1_post(self, Result, arg)
+ assert Result < 100
+
+This gives derived classes more freedom but also more responsibility
+than in Eiffel, where the compiler automatically takes care of this.
+
+In Eiffel, pre-conditions combine using contravariance, meaning a
+derived class can only make a pre-condition weaker; in Python, this is
+up to the derived class. For example, a derived class that takes away
+the requirement that arg > 0 could write:
+
+ def m1_pre(self, arg):
+ pass
+
+but one could equally write a derived class that makes a stronger
+requirement:
+
+ def m1_pre(self, arg):
+ require arg > 50
+
+It would be easy to modify the classes shown here so that pre- and
+post-conditions can be disabled (separately, on a per-class basis).
+
+A different design would have the pre- or post-condition testing
+functions return true for success and false for failure. This would
+make it possible to implement automatic combination of inherited
+and new pre-/post-conditions. All this is left as an exercise to the
+reader.
+
+"""
+
+from Meta import MetaClass, MetaHelper, MetaMethodWrapper
+
+class EiffelMethodWrapper(MetaMethodWrapper):
+
+ def __init__(self, func, inst):
+ MetaMethodWrapper.__init__(self, func, inst)
+ # Note that the following causes recursive wrappers around
+ # the pre-/post-condition testing methods. These are harmless
+ # but inefficient; to avoid them, the lookup must be done
+ # using the class.
+ try:
+ self.pre = getattr(inst, self.__name__ + "_pre")
+ except AttributeError:
+ self.pre = None
+ try:
+ self.post = getattr(inst, self.__name__ + "_post")
+ except AttributeError:
+ self.post = None
+
+ def __call__(self, *args, **kw):
+ if self.pre:
+ apply(self.pre, args, kw)
+ Result = apply(self.func, (self.inst,) + args, kw)
+ if self.post:
+ apply(self.post, (Result,) + args, kw)
+ return Result
+
+class EiffelHelper(MetaHelper):
+ __methodwrapper__ = EiffelMethodWrapper
+
+class EiffelMetaClass(MetaClass):
+ __helper__ = EiffelHelper
+
+Eiffel = EiffelMetaClass('Eiffel', (), {})
+
+
+def _test():
+ class C(Eiffel):
+ def m1(self, arg):
+ return arg+1
+ def m1_pre(self, arg):
+ assert arg > 0, "precondition for m1 failed"
+ def m1_post(self, Result, arg):
+ assert Result > arg
+ x = C()
+ x.m1(12)
+ x.m1(-1)
+
+if __name__ == '__main__':
+ _test()
diff --git a/Demo/metaclasses/Enum.py b/Demo/metaclasses/Enum.py
index 71a8e52..e1ae695 100644
--- a/Demo/metaclasses/Enum.py
+++ b/Demo/metaclasses/Enum.py
@@ -1,4 +1,8 @@
-"""Enumeration metaclass."""
+"""Enumeration metaclass.
+
+XXX This is very much a work in progress.
+
+"""
import string
diff --git a/Demo/metaclasses/Meta.py b/Demo/metaclasses/Meta.py
new file mode 100644
index 0000000..b63f781
--- /dev/null
+++ b/Demo/metaclasses/Meta.py
@@ -0,0 +1,106 @@
+"""Generic metaclass.
+
+XXX This is very much a work in progress.
+
+"""
+
+import types
+
+class MetaMethodWrapper:
+
+ def __init__(self, func, inst):
+ self.func = func
+ self.inst = inst
+ self.__name__ = self.func.__name__
+
+ def __call__(self, *args, **kw):
+ return apply(self.func, (self.inst,) + args, kw)
+
+class MetaHelper:
+
+ __methodwrapper__ = MetaMethodWrapper # For derived helpers to override
+
+ def __helperinit__(self, formalclass):
+ self.__formalclass__ = formalclass
+
+ def __getattr__(self, name):
+ # Invoked for any attr not in the instance's __dict__
+ try:
+ raw = self.__formalclass__.__getattr__(name)
+ except AttributeError:
+ try:
+ _getattr_ = self.__dict__['_getattr_']
+ except KeyError:
+ raise AttributeError, name
+ return _getattr_(name)
+ if type(raw) != types.FunctionType:
+ return raw
+ return self.__methodwrapper__(raw, self)
+
+class MetaClass:
+
+ """A generic metaclass.
+
+ This can be subclassed to implement various kinds of meta-behavior.
+
+ """
+
+ __helper__ = MetaHelper # For derived metaclasses to override
+
+ __inited = 0
+
+ def __init__(self, name, bases, dict):
+ if dict.has_key('__getattr__'):
+ raise TypeError, "Can't override __getattr__; use _getattr_"
+ self.__name__ = name
+ self.__bases__ = bases
+ self.__realdict__ = dict
+ self.__inited = 1
+
+ def __getattr__(self, name):
+ try:
+ return self.__realdict__[name]
+ except KeyError:
+ for base in self.__bases__:
+ try:
+ return base.__getattr__(name)
+ except AttributeError:
+ pass
+ raise AttributeError, name
+
+ def __setattr__(self, name, value):
+ if not self.__inited:
+ self.__dict__[name] = value
+ else:
+ self.__realdict__[name] = value
+
+ def __call__(self, *args, **kw):
+ inst = self.__helper__()
+ inst.__helperinit__(self)
+ try:
+ init = inst.__getattr__('__init__')
+ except AttributeError:
+ init = lambda: None
+ apply(init, args, kw)
+ return inst
+
+
+Meta = MetaClass('Meta', (), {})
+
+
+def _test():
+ class C(Meta):
+ def __init__(self, *args):
+ print "__init__, args =", args
+ def m1(self, x):
+ print "m1(x=%s)" %`x`
+ print C
+ x = C()
+ print x
+ x.m1(12)
+
+
+if __name__ == '__main__':
+ _test()
+
+
diff --git a/Demo/metaclasses/Trace.py b/Demo/metaclasses/Trace.py
index ed3944f..a5b765a 100644
--- a/Demo/metaclasses/Trace.py
+++ b/Demo/metaclasses/Trace.py
@@ -1,6 +1,10 @@
-"""Tracing metaclass."""
+"""Tracing metaclass.
-import types
+XXX This is very much a work in progress.
+
+"""
+
+import types, sys
class TraceMetaClass:
"""Metaclass for tracing.
@@ -28,7 +32,7 @@ class TraceMetaClass:
except KeyError:
for base in self.__bases__:
try:
- return getattr(base, name)
+ return base.__getattr__(name)
except AttributeError:
pass
raise AttributeError, name
@@ -106,12 +110,15 @@ Traced = TraceMetaClass('Traced', (), {'__trace_output__': None})
def _test():
- import sys
+ global C, D
class C(Traced):
def __init__(self, x=0): self.x = x
def m1(self, x): self.x = x
def m2(self, y): return self.x + y
- C.__trace_output__ = sys.stdout
+ __trace_output__ = sys.stdout
+ class D(C):
+ def m2(self, y): print "D.m2(%s)" % `y`; return C.m2(self, y)
+ __trace_output__ = None
x = C(4321)
print x
print x.x
@@ -122,5 +129,17 @@ def _test():
print x.m2(4000)
print x.x
+ print C.__init__
+ print C.m2
+ print D.__init__
+ print D.m2
+
+ y = D()
+ print y
+ print y.m1(10)
+ print y.m2(100)
+ print y.x
+
if __name__ == '__main__':
_test()
+