]> granicus.if.org Git - python/commitdiff
Arbitrary point checkpoint commit. Take no notice.
authorGuido van Rossum <guido@python.org>
Mon, 25 Aug 1997 15:37:59 +0000 (15:37 +0000)
committerGuido van Rossum <guido@python.org>
Mon, 25 Aug 1997 15:37:59 +0000 (15:37 +0000)
Demo/metaclasses/Eiffel.py [new file with mode: 0644]
Demo/metaclasses/Enum.py
Demo/metaclasses/Meta.py [new file with mode: 0644]
Demo/metaclasses/Trace.py

diff --git a/Demo/metaclasses/Eiffel.py b/Demo/metaclasses/Eiffel.py
new file mode 100644 (file)
index 0000000..e3efa7f
--- /dev/null
@@ -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()
index 71a8e52c693198ace14e7c588ef17552c003cab9..e1ae695e51d210ddc2887f978313cde5f188be47 100644 (file)
@@ -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 (file)
index 0000000..b63f781
--- /dev/null
@@ -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()
+
+    
index ed3944f2b9818142f4799768a2af64027511f2f4..a5b765ab1733c06e3fd3e2a28f9fac90e8cdf051 100644 (file)
@@ -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()
+