diff options
author | Guido van Rossum <guido@python.org> | 1997-08-23 21:14:37 (GMT) |
---|---|---|
committer | Guido van Rossum <guido@python.org> | 1997-08-23 21:14:37 (GMT) |
commit | bff110f3f10027cf03457556e42af2d3d87d5e7f (patch) | |
tree | 4d34a0b72fa24f21c572a1e6fb65a30f796a1368 | |
parent | 558f66ff53e913070ca518559420148d5a9a034b (diff) | |
download | cpython-bff110f3f10027cf03457556e42af2d3d87d5e7f.zip cpython-bff110f3f10027cf03457556e42af2d3d87d5e7f.tar.gz cpython-bff110f3f10027cf03457556e42af2d3d87d5e7f.tar.bz2 |
Examples of metaprogramming in pure Python.
-rw-r--r-- | Demo/metaclasses/Enum.py | 165 | ||||
-rw-r--r-- | Demo/metaclasses/Trace.py | 126 |
2 files changed, 291 insertions, 0 deletions
diff --git a/Demo/metaclasses/Enum.py b/Demo/metaclasses/Enum.py new file mode 100644 index 0000000..71a8e52 --- /dev/null +++ b/Demo/metaclasses/Enum.py @@ -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 index 0000000..ed3944f --- /dev/null +++ b/Demo/metaclasses/Trace.py @@ -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() |