summaryrefslogtreecommitdiff
path: root/Demo/newmetaclasses
diff options
context:
space:
mode:
authorJeremy Hylton <jeremy@alum.mit.edu>2002-07-11 21:08:06 +0000
committerJeremy Hylton <jeremy@alum.mit.edu>2002-07-11 21:08:06 +0000
commita00331636bb8361f8c11ea9f279781fb6cc98e15 (patch)
tree0beacdccf2a9e11c84ebee1d5229ddfe48cd4903 /Demo/newmetaclasses
parent20e5abc86ec641286e3958799333966febdf1b63 (diff)
downloadcpython-git-a00331636bb8361f8c11ea9f279781fb6cc98e15.tar.gz
Add Enum and Eiffel examples using new-style classes.
Diffstat (limited to 'Demo/newmetaclasses')
-rw-r--r--Demo/newmetaclasses/Eiffel.py145
-rw-r--r--Demo/newmetaclasses/Enum.py178
2 files changed, 323 insertions, 0 deletions
diff --git a/Demo/newmetaclasses/Eiffel.py b/Demo/newmetaclasses/Eiffel.py
new file mode 100644
index 0000000000..c9944feac6
--- /dev/null
+++ b/Demo/newmetaclasses/Eiffel.py
@@ -0,0 +1,145 @@
+"""Support Eiffel-style preconditions and postconditions."""
+
+from new import function
+
+class EiffelBaseMetaClass(type):
+
+ 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 = []
+ prenpost = []
+ for k, v in dict.iteritems():
+ if k.endswith('_pre') or k.endswith('_post'):
+ assert isinstance(v, function)
+ prenpost.append(k)
+ 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:
+ dict[k] = cls.make_eiffel_method(dict[m], pre, post)
+
+ convert_methods = classmethod(convert_methods)
+
+ def make_eiffel_method(func, pre, post):
+ def method(self, *args, **kwargs):
+ if pre:
+ pre(self, *args, **kwargs)
+ x = func(self, *args, **kwargs)
+ if post:
+ post(self, x, *args, **kwargs)
+ return x
+
+ if func.__doc__:
+ method.__doc__ = func.__doc__
+
+ return method
+
+ make_eiffel_method = staticmethod(make_eiffel_method)
+
+class EiffelMetaClass1(EiffelBaseMetaClass):
+ # an implementation of the "eiffel" meta class that uses nested functions
+
+ def __new__(meta, name, bases, dict):
+ meta.convert_methods(dict)
+ return super(EiffelMetaClass1, meta).__new__(meta, name, bases, dict)
+
+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)
+
+
+class EiffelDescriptor(object):
+
+ def __init__(self, func, pre, post):
+ self._func = func
+ self._pre = pre
+ self._post = post
+
+ self.__name__ = func.__name__
+ self.__doc__ = func.__doc__
+
+ def __get__(self, obj, cls):
+ 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
+
+class EiffelMetaClass2(EiffelMetaClass1):
+ # an implementation of the "eiffel" meta class that uses descriptors
+
+ make_eiffel_method = EiffelDescriptor
+
+def _test(metaclass):
+ class Eiffel:
+ __metaclass__ = metaclass
+
+ 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()
+ t.m(1)
+ t.m2(1)
+ try:
+ t.m2(0)
+ except AssertionError:
+ pass
+ else:
+ assert False
+
+ s = Sub()
+ try:
+ s.m2(1)
+ except AssertionError:
+ pass # result == arg
+ else:
+ assert False
+ try:
+ s.m2(10)
+ except AssertionError:
+ pass # result == 100
+ else:
+ assert False
+
+if __name__ == "__main__":
+ _test(EiffelMetaClass1)
+ _test(EiffelMetaClass2)
+
diff --git a/Demo/newmetaclasses/Enum.py b/Demo/newmetaclasses/Enum.py
new file mode 100644
index 0000000000..8a00b59f21
--- /dev/null
+++ b/Demo/newmetaclasses/Enum.py
@@ -0,0 +1,178 @@
+"""Enumeration metaclass."""
+
+class EnumMetaclass(type):
+ """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__(cls, name, bases, dict):
+ super(EnumMetaclass, cls).__init__(name, bases, dict)
+ cls._members = []
+ for attr in dict.keys():
+ if not (attr.startswith('__') and attr.endswith('__')):
+ enumval = EnumInstance(name, attr, dict[attr])
+ setattr(cls, attr, enumval)
+ cls._members.append(attr)
+
+ def __getattr__(cls, name):
+ if name == "__members__":
+ return cls._members
+ raise AttributeError, name
+
+ def __repr__(cls):
+ s1 = s2 = ""
+ enumbases = [base.__name__ for base in cls.__bases__
+ if isinstance(base, EnumMetaclass) and not base is Enum]
+ if enumbases:
+ s1 = "(%s)" % ", ".join(enumbases)
+ enumvalues = ["%s: %d" % (val, getattr(cls, val))
+ for val in cls._members]
+ if enumvalues:
+ s2 = ": {%s}" % ", ".join(enumvalues)
+ return "%s%s%s" % (cls.__name__, s1, s2)
+
+class FullEnumMetaclass(EnumMetaclass):
+ """Metaclass for full enumerations.
+
+ A full enumeration displays all the values defined in base classes.
+ """
+
+ def __init__(cls, name, bases, dict):
+ super(FullEnumMetaclass, cls).__init__(name, bases, dict)
+ for obj in cls.__mro__:
+ if isinstance(obj, EnumMetaclass):
+ for attr in obj._members:
+ # XXX inefficient
+ if not attr in cls._members:
+ cls._members.append(attr)
+
+class EnumInstance(int):
+ """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 __new__(cls, classname, enumname, value):
+ return int.__new__(cls, value)
+
+ def __init__(self, classname, enumname, value):
+ self.__classname = classname
+ self.__enumname = enumname
+
+ def __repr__(self):
+ return "EnumInstance(%s, %s, %d)" % (self.__classname, self.__enumname,
+ self)
+
+ def __str__(self):
+ return "%s.%s" % (self.__classname, self.__enumname)
+
+class Enum:
+ __metaclass__ = EnumMetaclass
+
+class FullEnum:
+ __metaclass__ = FullEnumMetaclass
+
+def _test():
+
+ class Color(Enum):
+ red = 1
+ green = 2
+ blue = 3
+
+ print Color.red
+
+ print `Color.red`
+ 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
+
+def _test2():
+
+ class Color(FullEnum):
+ red = 1
+ green = 2
+ blue = 3
+
+ print Color.red
+
+ print `Color.red`
+ 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(FullEnum):
+ 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()
+ _test2()
+