Enum.py 4.32 KB
Newer Older
1 2 3 4 5
"""Enumeration metaclass.

XXX This is very much a work in progress.

"""
6 7 8 9 10 11 12 13 14

import string

class EnumMetaClass:
    """Metaclass for enumeration.

    To define your own enumeration, do something like

    class Color(Enum):
Guido van Rossum's avatar
Guido van Rossum committed
15 16 17
        red = 1
        green = 2
        blue = 3
18 19 20 21 22 23 24 25 26 27

    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):
Guido van Rossum's avatar
Guido van Rossum committed
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
        """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)
47 48

    def __getattr__(self, name):
Guido van Rossum's avatar
Guido van Rossum committed
49
        """Return an enumeration value.
50

Guido van Rossum's avatar
Guido van Rossum committed
51
        For example, Color.red returns the value corresponding to red.
52

Guido van Rossum's avatar
Guido van Rossum committed
53
        XXX Perhaps the values should be created in the constructor?
54

Guido van Rossum's avatar
Guido van Rossum committed
55 56
        This looks in the class dictionary and if it is not found
        there asks the base classes.
57

Guido van Rossum's avatar
Guido van Rossum committed
58 59 60
        The special attribute __members__ returns the list of names
        defined in this class (it does not merge in the names defined
        in base classes).
61

Guido van Rossum's avatar
Guido van Rossum committed
62 63 64
        """
        if name == '__members__':
            return self.__dict.keys()
65

Guido van Rossum's avatar
Guido van Rossum committed
66 67 68 69 70 71 72 73
        try:
            return self.__dict[name]
        except KeyError:
            for base in self.__bases__:
                try:
                    return getattr(base, name)
                except AttributeError:
                    continue
74

Guido van Rossum's avatar
Guido van Rossum committed
75
        raise AttributeError, name
76 77

    def __repr__(self):
Guido van Rossum's avatar
Guido van Rossum committed
78 79 80 81 82 83 84 85 86 87
        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
88 89 90 91 92 93 94 95 96 97 98 99 100 101


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):
Guido van Rossum's avatar
Guido van Rossum committed
102 103 104
        self.__classname = classname
        self.__enumname = enumname
        self.__value = value
105 106

    def __int__(self):
Guido van Rossum's avatar
Guido van Rossum committed
107
        return self.__value
108 109

    def __repr__(self):
110 111 112
        return "EnumInstance(%r, %r, %r)" % (self.__classname,
                                             self.__enumname,
                                             self.__value)
113 114

    def __str__(self):
Guido van Rossum's avatar
Guido van Rossum committed
115
        return "%s.%s" % (self.__classname, self.__enumname)
116 117

    def __cmp__(self, other):
Guido van Rossum's avatar
Guido van Rossum committed
118
        return cmp(self.__value, int(other))
119 120 121 122 123 124 125 126 127 128


# Create the base class for enumerations.
# It is an empty enumeration.
Enum = EnumMetaClass("Enum", (), {})


def _test():

    class Color(Enum):
Guido van Rossum's avatar
Guido van Rossum committed
129 130 131
        red = 1
        green = 2
        blue = 3
132 133 134 135 136 137 138 139 140 141

    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):
Guido van Rossum's avatar
Guido van Rossum committed
142 143 144 145 146
        white = 0
        orange = 4
        yellow = 5
        purple = 6
        black = 7
147 148 149 150 151 152 153

    print ExtendedColor.orange
    print ExtendedColor.red

    print Color.red == ExtendedColor.red

    class OtherColor(Enum):
Guido van Rossum's avatar
Guido van Rossum committed
154 155
        white = 4
        blue = 5
156 157

    class MergedColor(Color, OtherColor):
Guido van Rossum's avatar
Guido van Rossum committed
158
        pass
159 160 161 162 163 164 165 166 167 168 169

    print MergedColor.red
    print MergedColor.white

    print Color
    print ExtendedColor
    print OtherColor
    print MergedColor

if __name__ == '__main__':
    _test()