Enum Klasse (mit Magie!)

Code-Stücke können hier veröffentlicht werden.
Antworten
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Enums sollen ja mit Python 3.4 kommen. Aber man kann sich auch selber was bauen:

Code: Alles auswählen

from collections import defaultdict
from itertools import count

class MetaEnum(type):

    @classmethod
    def __prepare__(metacls, name, bases):
        return defaultdict(count().__next__)

    def __new__(cls, name, bases, cdict):
        return type.__new__(cls, name, bases, dict(cdict))

class Enum(metaclass=MetaEnum):
    pass
Verwenden kann man es so:

Code: Alles auswählen

class MyEnum(Enum):
    foo
    bar
    baz
    zig = 5
    zag = baz

print(MyEnum.foo)
print(MyEnum.bar)
print(MyEnum.baz)
print(MyEnum.zig)
print(MyEnum.zag)
Ergebnis:

Code: Alles auswählen

$ python enum.py
1
2
3
5
3
Das nur als Basis, die anderen Sachen, die das 3.4er Enum kann, könnte man leicht zusätzlich einbauen. Der Vorteil bei meiner Version ist, dass man nicht explizit Werte zuweisen muss, da diese, wenn nötig, automatisch generiert werden. Ich plädiere übrigens nicht dafür, meine Version zu verwenden, sondern wollte nur mal zeigen, was mit __prepare__() möglich ist. Ursprünglich wollte ich nur etwas damit rumspielen und herauskriegen, ob und wie man NameErrors vermeiden kann, wenn im Class Body nicht initialisierte Namen stehen. Wie sich herausstellt, funktioniert es, und zwar so:

Wenn Python eine Klassendefinition ausführt, nimmt es als Namensraum für die Klasse das, was von der __prepare__()-Methode ihrer Metaklasse zurückgegeben wird. Das kann im Prinzip jede Art von Dictionary sein, in meinem Fall ist es ein defaultdict mit der Factory Funktion count().__next__, so dass für jeden nicht gefundenen Namen dieser angelegt und mit der nächsten Zahl assoziiert wird. Die erste Zahl des Enums ist deswegen 1 und nicht 0, weil als erstes anscheinend das __name__-Attribut im Namensraum gesucht wird, was den ersten Aufruf von count().__next__() triggert. Das Attribut wird allerdings später wieder irgendwo mit dem Namen der Klasse überschrieben.

Wichtig: In __new__() sollte man dieses Dictionary wieder durch ein normales dict ersetzen, sonst passieren komische Sachen.
In specifications, Murphy's Law supersedes Ohm's.
Antworten