]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Examples of metaprogramming in pure Python.
authorGuido van Rossum <guido@python.org>
Sat, 23 Aug 1997 21:14:37 +0000 (21:14 +0000)
committerGuido van Rossum <guido@python.org>
Sat, 23 Aug 1997 21:14:37 +0000 (21:14 +0000)
Demo/metaclasses/Enum.py [new file with mode: 0644]
Demo/metaclasses/Trace.py [new file with mode: 0644]

diff --git a/Demo/metaclasses/Enum.py b/Demo/metaclasses/Enum.py
new file mode 100644 (file)
index 0000000..71a8e52
--- /dev/null
@@ -0,0 +1,165 @@
+"""Enumeration metaclass."""
+
+import string
+
+class EnumMetaClass:
+    """Metaclass for enumeration.
+
+    To define your own enumeration, do something like
+
+    class Color(Enum):
+       red = 1
+       green = 2
+       blue = 3
+
+    Now, Color.red, Color.green and Color.blue behave totally
+    different: they are enumerated values, not integers.
+
+    Enumerations cannot be instantiated; however they can be
+    subclassed.
+
+    """
+
+    def __init__(self, name, bases, dict):
+       """Constructor -- create an enumeration.
+
+       Called at the end of the class statement.  The arguments are
+       the name of the new class, a tuple containing the base
+       classes, and a dictionary containing everything that was
+       entered in the class' namespace during execution of the class
+       statement.  In the above example, it would be {'red': 1,
+       'green': 2, 'blue': 3}.
+
+       """
+       for base in bases:
+           if base.__class__ is not EnumMetaClass:
+               raise TypeError, "Enumeration base class must be enumeration"
+       bases = filter(lambda x: x is not Enum, bases)
+       self.__name__ = name
+       self.__bases__ = bases
+       self.__dict = {}
+       for key, value in dict.items():
+           self.__dict[key] = EnumInstance(name, key, value)
+
+    def __getattr__(self, name):
+       """Return an enumeration value.
+
+       For example, Color.red returns the value corresponding to red.
+
+       XXX Perhaps the values should be created in the constructor?
+
+       This looks in the class dictionary and if it is not found
+       there asks the base classes.
+
+       The special attribute __members__ returns the list of names
+       defined in this class (it does not merge in the names defined
+       in base classes).
+
+       """
+       if name == '__members__':
+           return self.__dict.keys()
+
+       try:
+           return self.__dict[name]
+       except KeyError:
+           for base in self.__bases__:
+               try:
+                   return getattr(base, name)
+               except AttributeError:
+                   continue
+
+       raise AttributeError, name
+
+    def __repr__(self):
+       s = self.__name__
+       if self.__bases__:
+           s = s + '(' + string.join(map(lambda x: x.__name__,
+                                         self.__bases__), ", ") + ')'
+       if self.__dict:
+           list = []
+           for key, value in self.__dict.items():
+               list.append("%s: %s" % (key, int(value)))
+           s = "%s: {%s}" % (s, string.join(list, ", "))
+       return s
+
+
+class EnumInstance:
+    """Class to represent an enumeration value.
+
+    EnumInstance('Color', 'red', 12) prints as 'Color.red' and behaves
+    like the integer 12 when compared, but doesn't support arithmetic.
+
+    XXX Should it record the actual enumeration rather than just its
+    name?
+
+    """
+
+    def __init__(self, classname, enumname, value):
+       self.__classname = classname
+       self.__enumname = enumname
+       self.__value = value
+
+    def __int__(self):
+       return self.__value
+
+    def __repr__(self):
+       return "EnumInstance(%s, %s, %s)" % (`self.__classname`,
+                                            `self.__enumname`,
+                                            `self.__value`)
+
+    def __str__(self):
+       return "%s.%s" % (self.__classname, self.__enumname)
+
+    def __cmp__(self, other):
+       return cmp(self.__value, int(other))
+
+
+# Create the base class for enumerations.
+# It is an empty enumeration.
+Enum = EnumMetaClass("Enum", (), {})
+
+
+def _test():
+
+    class Color(Enum):
+       red = 1
+       green = 2
+       blue = 3
+
+    print Color.red
+    print dir(Color)
+
+    print Color.red == Color.red
+    print Color.red == Color.blue
+    print Color.red == 1
+    print Color.red == 2
+
+    class ExtendedColor(Color):
+       white = 0
+       orange = 4
+       yellow = 5
+       purple = 6
+       black = 7
+
+    print ExtendedColor.orange
+    print ExtendedColor.red
+
+    print Color.red == ExtendedColor.red
+
+    class OtherColor(Enum):
+       white = 4
+       blue = 5
+
+    class MergedColor(Color, OtherColor):
+       pass
+
+    print MergedColor.red
+    print MergedColor.white
+
+    print Color
+    print ExtendedColor
+    print OtherColor
+    print MergedColor
+
+if __name__ == '__main__':
+    _test()
diff --git a/Demo/metaclasses/Trace.py b/Demo/metaclasses/Trace.py
new file mode 100644 (file)
index 0000000..ed3944f
--- /dev/null
@@ -0,0 +1,126 @@
+"""Tracing metaclass."""
+
+import types
+
+class TraceMetaClass:
+    """Metaclass for tracing.
+
+    Classes defined using this metaclass have an automatic tracing
+    feature -- by setting the __trace_output__ instance (or class)
+    variable to a file object, trace messages about all calls are
+    written to the file.  The trace formatting can be changed by
+    defining a suitable __trace_call__ method.
+
+    """
+
+    __inited = 0
+
+    def __init__(self, name, bases, dict):
+       self.__name__ = name
+       self.__bases__ = bases
+       self.__dict = dict
+       # XXX Can't define __dict__, alas
+       self.__inited = 1
+
+    def __getattr__(self, name):
+       try:
+           return self.__dict[name]
+       except KeyError:
+           for base in self.__bases__:
+               try:
+                   return getattr(base, name)
+               except AttributeError:
+                   pass
+           raise AttributeError, name
+
+    def __setattr__(self, name, value):
+       if not self.__inited:
+           self.__dict__[name] = value
+       else:
+           self.__dict[name] = value
+
+    def __call__(self, *args, **kw):
+       inst = TracingInstance()
+       inst.__meta_init__(self)
+       try:
+           init = inst.__getattr__('__init__')
+       except AttributeError:
+           init = lambda: None
+       apply(init, args, kw)
+       return inst
+
+    __trace_output__ = None
+
+class TracingInstance:
+    """Helper class to represent an instance of a tracing class."""
+
+    def __trace_call__(self, fp, fmt, *args):
+       fp.write((fmt+'\n') % args)
+
+    def __meta_init__(self, klass):
+       self.__class = klass
+
+    def __getattr__(self, name):
+       # Invoked for any attr not in the instance's __dict__
+       try:
+           raw = self.__class.__getattr__(name)
+       except AttributeError:
+           raise AttributeError, name
+       if type(raw) != types.FunctionType:
+           return raw
+       # It's a function
+       fullname = self.__class.__name__ + "." + name
+       if not self.__trace_output__ or name == '__trace_call__':
+           return NotTracingWrapper(fullname, raw, self)
+       else:
+           return TracingWrapper(fullname, raw, self)
+
+class NotTracingWrapper:
+    def __init__(self, name, func, inst):
+       self.__name__ = name
+       self.func = func
+       self.inst = inst
+    def __call__(self, *args, **kw):
+       return apply(self.func, (self.inst,) + args, kw)
+
+class TracingWrapper(NotTracingWrapper):
+    def __call__(self, *args, **kw):
+       self.inst.__trace_call__(self.inst.__trace_output__,
+                                "calling %s, inst=%s, args=%s, kw=%s",
+                                self.__name__, self.inst, args, kw)
+       try:
+           rv = apply(self.func, (self.inst,) + args, kw)
+       except:
+           t, v, tb = sys.exc_info()
+           self.inst.__trace_call__(self.inst.__trace_output__,
+                                    "returning from %s with exception %s: %s",
+                                    self.__name__, t, v)
+           raise t, v, tb
+       else:
+           self.inst.__trace_call__(self.inst.__trace_output__,
+                                    "returning from %s with value %s",
+                                    self.__name__, rv)
+           return rv
+
+Traced = TraceMetaClass('Traced', (), {'__trace_output__': None})
+
+
+def _test():
+    import sys
+    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
+    x = C(4321)
+    print x
+    print x.x
+    print x.m1(100)
+    print x.m1(10)
+    print x.m2(33)
+    print x.m1(5)
+    print x.m2(4000)
+    print x.x
+
+if __name__ == '__main__':
+    _test()