Jak mogę reprezentować „Enum” w Pythonie?

1143

Jestem głównie programistą C #, ale obecnie pracuję nad projektem w Pythonie.

Jak mogę reprezentować odpowiednik Enum w Pythonie?

sektrean
źródło

Odpowiedzi:

2686

Do języka Python 3.4 dodano wyliczenia, jak opisano w PEP 435 . Został również przeniesiony do wersji 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 i 2.4 na pypi.

Dla bardziej zaawansowanych technik Enum wypróbuj bibliotekę aenum (2.7, 3.3+, ten sam autor co enum34. Kod nie jest w pełni kompatybilny między py2 i py3, np. Będziesz potrzebować __order__w python 2 ).

  • Aby użyć enum34, zrób$ pip install enum34
  • Aby użyć aenum, zrób$ pip install aenum

Instalacja enum(bez numerów) spowoduje zainstalowanie zupełnie innej i niezgodnej wersji.


from enum import Enum     # for enum34, or the stdlib version
# from aenum import Enum  # for the aenum version
Animal = Enum('Animal', 'ant bee cat dog')

Animal.ant  # returns <Animal.ant: 1>
Animal['ant']  # returns <Animal.ant: 1> (string lookup)
Animal.ant.name  # returns 'ant' (inverse lookup)

lub równoważnie:

class Animal(Enum):
    ant = 1
    bee = 2
    cat = 3
    dog = 4

We wcześniejszych wersjach jednym ze sposobów osiągnięcia wyliczeń jest:

def enum(**enums):
    return type('Enum', (), enums)

który jest używany tak:

>>> Numbers = enum(ONE=1, TWO=2, THREE='three')
>>> Numbers.ONE
1
>>> Numbers.TWO
2
>>> Numbers.THREE
'three'

Możesz także z łatwością obsługiwać automatyczne wyliczanie za pomocą czegoś takiego:

def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    return type('Enum', (), enums)

i używane w ten sposób:

>>> Numbers = enum('ZERO', 'ONE', 'TWO')
>>> Numbers.ZERO
0
>>> Numbers.ONE
1

Obsługę konwersji wartości z powrotem na nazwy można dodać w ten sposób:

def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    reverse = dict((value, key) for key, value in enums.iteritems())
    enums['reverse_mapping'] = reverse
    return type('Enum', (), enums)

Zastępuje to wszystko tą nazwą, ale przydaje się do renderowania twoich wyliczeń w danych wyjściowych. Zgłasza KeyError, jeśli odwrotne mapowanie nie istnieje. W pierwszym przykładzie:

>>> Numbers.reverse_mapping['three']
'THREE'
Alec Thomas
źródło
1
Nie byłem w stanie zrozumieć, dlaczego przekazali kwargs (** nazwany) w metodzie enum (* sekwencyjny, ** nazwany)? Proszę wyjaśnić. Bez kwargów również będzie działać. Sprawdziłem to.
Seenu S,
Byłoby miło zaktualizować funkcję Python 2, aby była kompatybilna z funkcjonalnym API Enum Pythona 3 (nazwa, wartości)
bscan
Zmienna var kwargs ( **named) w funkcji wyliczania dla starszych wersji ma obsługiwać wartości niestandardowe:enum("blue", "red", "green", black=0)
Éric Araujo
822

Przed PEP 435 Python nie miał odpowiednika, ale można go zaimplementować.

Ja sam lubię to prostsze (widziałem w sieci kilka strasznie skomplikowanych przykładów), coś takiego ...

class Animal:
    DOG = 1
    CAT = 2

x = Animal.DOG

W Pythonie 3.4 ( PEP 435 ) możesz ustawić Enum jako klasę podstawową. To daje ci trochę dodatkowej funkcjonalności, opisanej w PEP. Na przykład członkowie enum różnią się od liczb całkowitych i składają się z a namei a value.

class Animal(Enum):
    DOG = 1
    CAT = 2

print(Animal.DOG)
# <Animal.DOG: 1>

print(Animal.DOG.value)
# 1

print(Animal.DOG.name)
# "DOG"

Jeśli nie chcesz wpisywać wartości, użyj następującego skrótu:

class Animal(Enum):
    DOG, CAT = range(2)

Enumimplementacje można konwertować na listy i są iterowalne . Kolejność członków jest kolejnością deklaracji i nie ma nic wspólnego z ich wartościami. Na przykład:

class Animal(Enum):
    DOG = 1
    CAT = 2
    COW = 0

list(Animal)
# [<Animal.DOG: 1>, <Animal.CAT: 2>, <Animal.COW: 0>]

[animal.value for animal in Animal]
# [1, 2, 0]

Animal.CAT in Animal
# True
Jundiaius
źródło
51
Nie, to zmienna klasowa.
Georg Schölly,
246
Python jest domyślnie dynamiczny. Nie ma żadnego uzasadnionego powodu do wymuszania bezpieczeństwa podczas kompilacji w języku takim jak Python, szczególnie gdy takiego nie ma. I jeszcze jedna rzecz ... dobry wzór jest dobry tylko w kontekście, w którym został stworzony. Dobry wzór można również zastąpić lub całkowicie bezużyteczny, w zależności od używanych narzędzi.
Alexandru Nedelcu
20
@ Longpoke, jeśli masz 100 wartości, to na pewno robisz coś złego;) Lubię liczby związane z moimi wyliczeniami ... są łatwe do napisania (vs ciągi), można je łatwo zapisać w bazie danych i są kompatybilne z enum C / C ++, które ułatwia zestawianie.
Alexandru Nedelcu,
50
Używam tego, z liczbami zastąpionymi przez object().
Tobu,
9
Oryginalny PEP354 nie jest już po prostu odrzucany, ale jest teraz oznaczony jako zastąpiony. PEP435 dodaje standardowe Enum dla Pythona 3.4. Zobacz python.org/dev/peps/pep-0435
Peter Hansen
321

Oto jedna implementacja:

class Enum(set):
    def __getattr__(self, name):
        if name in self:
            return name
        raise AttributeError

Oto jego użycie:

Animals = Enum(["DOG", "CAT", "HORSE"])

print(Animals.DOG)
shahjapan
źródło
51
Świetny. Można to jeszcze ulepszyć, zastępując __setattr__(self, name, value)i być może __delattr__(self, name), że jeśli przypadkowo napiszesz Animals.DOG = CAT, nie powiedzie się to po cichu.
Joonas Pulakka
15
@shahjapan: Ciekawe, ale stosunkowo powolne: dla każdego dostępu przeprowadzany jest test Animals.DOG; ponadto wartości constats są ciągami, więc porównania z tymi stałymi są wolniejsze, niż gdyby, powiedzmy, liczby całkowite były dozwolone jako wartości.
Eric O Lebigot,
3
@shahjapan: Twierdziłbym, że to rozwiązanie nie jest tak czytelne, jak na przykład krótsze rozwiązania Alexandru lub Marka. Jest to jednak interesujące rozwiązanie. :)
Eric O Lebigot,
Próbowałem użyć setattr()funkcji wewnątrz __init__()metody zamiast __getattr__()metody overidding . Zakładam, że to powinno działać w ten sam sposób: klasa Enum (obiekt): def __init __ (self, enum_string_list): if type (enum_string_list) == list: dla enum_string w enum_string_list: setattr (self, enum_string, enum_string) else: podbicie AttributeError
Harshith JV
8
@ AndréTerra: jak sprawdzasz członkostwo w try-exceptbloku?
bgusach
209

Jeśli potrzebujesz wartości liczbowych, oto najszybszy sposób:

dog, cat, rabbit = range(3)

W Pythonie 3.x możesz również dodać symbol zastępczy oznaczony gwiazdką na końcu, który pochłonie wszystkie pozostałe wartości zakresu, na wypadek gdybyś nie miał nic przeciwko marnowaniu pamięci i nie możesz liczyć:

dog, cat, rabbit, horse, *_ = range(100)
Mark Harrison
źródło
1
Ale to może zająć więcej pamięci!
MJ
Nie widzę sensu symbolu zastępczego oznaczonego gwiazdką, biorąc pod uwagę, że Python sprawdzi liczbę wartości do rozpakowania (więc zrobi to za Ciebie).
Gabriel Devillers,
@GabrielDevillers, myślę, że Python zgłosi wyjątek, jeśli wystąpi rozbieżność w liczbie elementów w krotce do przypisania.
Mark Harrison
1
Rzeczywiście, robi to w moim teście (Python2,3), ale to oznacza, że ​​każdy błąd liczenia od programisty zostanie wyłapany podczas pierwszego testu (z komunikatem podającym poprawną liczbę).
Gabriel Devillers,
1
Nie umiem liczyć Czy symbol zastępczy oznaczony gwiazdką może również naprawić moje finanse?
javadba
130

Najlepsze rozwiązanie dla Ciebie zależy od tego, czego oczekujesz od fałszywki enum .

Proste wyliczanie:

Jeśli potrzebujesz enumtylko listy nazw identyfikujących różne przedmioty , rozwiązanie autorstwa Mark Harrison (powyżej) jest świetne:

Pen, Pencil, Eraser = range(0, 3)

Użycie a rangepozwala również ustawić dowolną wartość początkową :

Pen, Pencil, Eraser = range(9, 12)

Oprócz powyższego, jeśli wymagasz również, aby przedmioty należały do ​​jakiegoś kontenera , umieść je w klasie:

class Stationery:
    Pen, Pencil, Eraser = range(0, 3)

Aby użyć elementu wyliczanego, musisz teraz użyć nazwy kontenera i nazwy elementu:

stype = Stationery.Pen

Złożone wyliczenie:

W przypadku długich list enum lub bardziej skomplikowanych zastosowań enum te rozwiązania nie będą wystarczające. Możesz zajrzeć do przepisu Will Ware'a na symulowanie wyliczeń w Pythonie opublikowanego w Python Cookbook . Wersja online jest dostępna tutaj .

Więcej informacji:

PEP 354: Wyliczenia w Pythonie zawierają interesujące szczegóły propozycji wyliczenia w Pythonie i dlaczego zostały odrzucone.

Ashwin
źródło
7
z rangemożesz pominąć pierwszy argument, jeśli ma wartość 0
ToonAlfrink
Innym fałszywym wyliczeniem, które pasuje do niektórych celów, jest my_enum = dict(map(reversed, enumerate(str.split('Item0 Item1 Item2')))). Następnie my_enummoże być użyty do wyszukiwania, np. my_enum['Item0']Może być indeksem w sekwencji. Możesz zawinąć wynik str.splitw funkcję, która zgłasza wyjątek, jeśli istnieją jakieś duplikaty.
Ana Nimbus,
Miły! W przypadku flag możeszFlag1, Flag2, Flag3 = [2**i for i in range(3)]
majkelx
To najlepsza odpowiedź
Jurij Pozniak
78

Bezpieczny wzorzec wyliczenia zastosowany w Javie przed JDK 5 ma wiele zalet. Podobnie jak w odpowiedzi Alexandru, tworzysz klasę, a pola na poziomie klasy są wartościami wyliczania; jednak wartości wyliczeniowe są instancjami klasy, a nie małymi liczbami całkowitymi. Ma to tę zaletę, że twoje wartości wyliczeniowe nie przypadkowo porównują wartości równe małym liczbom całkowitym, możesz kontrolować sposób ich drukowania, dodawać dowolne metody, jeśli jest to użyteczne, i dokonywać asercji za pomocą isinstance:

class Animal:
   def __init__(self, name):
       self.name = name

   def __str__(self):
       return self.name

   def __repr__(self):
       return "<Animal: %s>" % self

Animal.DOG = Animal("dog")
Animal.CAT = Animal("cat")

>>> x = Animal.DOG
>>> x
<Animal: dog>
>>> x == 1
False

Niedawny wątek na temat python-dev wykazał, że na wolności istnieje kilka bibliotek enum, w tym:

Aaron Maenpaa
źródło
16
Myślę, że to bardzo złe podejście. Animal.DOG = Animal („pies”) Animal.DOG2 = Animal („pies”) zapewnia Animal.DOG == Animal.DOG2 nie udaje się ...
Zamieszanie
11
@ Confusion Użytkownik nie powinien wywoływać konstruktora, fakt, że istnieje nawet konstruktor, jest szczegółem implementacji i musisz poinformować, kto kiedykolwiek używa Twojego kodu, że tworzenie nowych wartości wyliczania nie ma sensu, a wychodzący kod nie będzie "robić co należy". Oczywiście to nie powstrzymuje cię przed wdrożeniem Animal.from_name („dog”) -> Animal.DOG.
Aaron Maenpaa,
13
„Korzyść, której wartości wyliczeniowe nie mimowolnie porównują, równa się małym liczbom całkowitym” Jaka jest w tym zaleta? Co jest złego w porównaniu twojego wyliczenia do liczb całkowitych? Zwłaszcza jeśli przechowujesz wyliczenie w bazie danych, zwykle chcesz, aby był on zapisywany jako liczby całkowite, więc w pewnym momencie będziesz musiał porównać go z liczbami całkowitymi.
ibz
3
@Aaaron Maenpaa. poprawny. To wciąż zepsuty i zbyt skomplikowany sposób na zrobienie tego.
aaronasterling
4
@AaronMcSmooth To naprawdę zależy od tego, czy wchodzisz z perspektywy C „Enums to tylko nazwy dla kilku liczb wewnętrznych” czy bardziej zorientowanego obiektowo podejścia, w którym wartości wyliczeniowe są rzeczywistymi obiektami i metodami (tak jak wyliczenia w Javie 1.5 są, i dla którego typ bezpiecznego wzorca wyliczania miał być). Osobiście nie lubię instrukcji switch, więc skłaniam się ku wartościom wyliczającym, które są rzeczywistymi obiektami.
Aaron Maenpaa
61

Klasa Enum może być jednowarstwowa.

class Enum(tuple): __getattr__ = tuple.index

Jak z niego korzystać (wyszukiwanie do przodu i do tyłu, klucze, wartości, pozycje itp.)

>>> State = Enum(['Unclaimed', 'Claimed'])
>>> State.Claimed
1
>>> State[1]
'Claimed'
>>> State
('Unclaimed', 'Claimed')
>>> range(len(State))
[0, 1]
>>> [(k, State[k]) for k in range(len(State))]
[(0, 'Unclaimed'), (1, 'Claimed')]
>>> [(k, getattr(State, k)) for k in State]
[('Unclaimed', 0), ('Claimed', 1)]
Zoetic
źródło
Myślę, że to najprostsze i najbardziej eleganckie rozwiązanie. W Pythonie 2.4 (tak, stary starszy serwer) krotki nie indeksują się. Rozwiązałem zamianę na listę.
Massimo,
Próbowałem tego w notatniku Jupytera i odkryłem, że nie zadziała jako definicja jednowierszowa, ale zaakceptowanie umieszczenia definicji getattr w drugiej (wciętej) linii.
user5920660,
To rozwiązanie pozwoli mi użyć insłowa kluczowego do wyszukania członków, którzy są porządni. Przykładowe użycie:'Claimed' in Enum(['Unclaimed', 'Claimed'])
Farzad Abdolhosseini
51

Zgadzam się. Nie wymuszajmy bezpieczeństwa typu w Pythonie, ale chciałbym uchronić się przed głupimi błędami. Co myślimy o tym?

class Animal(object):
    values = ['Horse','Dog','Cat']

    class __metaclass__(type):
        def __getattr__(self, name):
            return self.values.index(name)

Chroni mnie przed kolizją wartości przy definiowaniu moich wyliczeń.

>>> Animal.Cat
2

Jest jeszcze jedna przydatna zaleta: naprawdę szybkie wyszukiwanie wsteczne:

def name_of(self, i):
    return self.values[i]
królewskich
źródło
Podoba mi się to, ale równie dobrze możesz zablokować wartości za pomocą krotki? Bawiłem się tym i wymyśliłem wersję, która ustawia wartości self.values ​​z args w init . Miło jest móc deklarować Animal = Enum('horse', 'dog', 'cat'). Łapię także ValueError w getattr w przypadku braku elementu w self.values ​​- wydaje się, że lepiej jest podnieść AttributeError za pomocą dostarczonego ciągu nazwy. Nie mogłem zmusić metaklasy do pracy w Pythonie 2.7 w oparciu o moją ograniczoną wiedzę w tym zakresie, ale moja niestandardowa klasa Enum działa dobrze z metodami prostych instancji.
trojjer
49

Python nie ma wbudowanego odpowiednika enum, a inne odpowiedzi mają pomysły na wdrożenie własnego (możesz być również zainteresowany ponadprzeciętną wersją książki kucharskiej Python).

Jednak w sytuacjach, w których enumw C byłoby wywoływane, zwykle używam prostych ciągów znaków : ze względu na sposób implementacji obiektów / atrybutów (C) Python jest zoptymalizowany do pracy bardzo szybko z krótkimi ciągami, więc nie to naprawdę przyniesie korzyści w postaci używania liczb całkowitych. Aby uchronić się przed literówkami / nieprawidłowymi wartościami, możesz wstawiać czeki w wybranych miejscach.

ANIMALS = ['cat', 'dog', 'python']

def take_for_a_walk(animal):
    assert animal in ANIMALS
    ...

(Wadą w porównaniu do korzystania z klasy jest utrata korzyści autouzupełniania)

dF.
źródło
2
Wolę to rozwiązanie. W miarę możliwości lubię używać wbudowanych typów.
Seun Osewa
Ta wersja nie jest naprawdę przesadna. Ma po prostu dużo dostarczonego kodu testowego
Casebash,
1
W rzeczywistości „poprawna” wersja znajduje się w komentarzach i jest znacznie bardziej złożona - główna wersja ma niewielki błąd.
Casebash,
38

W dniu 2013-05-10 Guido zgodził się zaakceptować PEP 435 do standardowej biblioteki Python 3.4. Oznacza to, że Python wreszcie ma wbudowaną obsługę wyliczeń!

Dostępny jest backport dla Python 3.3, 3.2, 3.1, 2.7, 2.6, 2.5 i 2.4. Jest na Pypi jako enum34 .

Deklaracja:

>>> from enum import Enum
>>> class Color(Enum):
...     red = 1
...     green = 2
...     blue = 3

Reprezentacja:

>>> print(Color.red)
Color.red
>>> print(repr(Color.red))
<Color.red: 1>

Iteracja:

>>> for color in Color:
...   print(color)
...
Color.red
Color.green
Color.blue

Dostęp programowy:

>>> Color(1)
Color.red
>>> Color['blue']
Color.blue

Aby uzyskać więcej informacji, zapoznaj się z wnioskiem . Oficjalna dokumentacja prawdopodobnie nastąpi wkrótce.

Danilo Bargen
źródło
33

Wolę definiować wyliczenia w Pythonie tak:

class Animal:
  class Dog: pass
  class Cat: pass

x = Animal.Dog

Jest to bardziej odporne na błędy niż używanie liczb całkowitych, ponieważ nie musisz się martwić o to, że liczby całkowite są unikalne (np. Jeśli powiesz, że Dog = 1 i Cat = 1, to byś wkręcił).

Jest bardziej odporny na błędy niż używanie ciągów, ponieważ nie musisz się martwić o literówki (np. X == „catt” zawodzi cicho, ale x == Animal.Catt to wyjątek czasu wykonywania).

mbac32768
źródło
31
def M_add_class_attribs(attribs):
    def foo(name, bases, dict_):
        for v, k in attribs:
            dict_[k] = v
        return type(name, bases, dict_)
    return foo

def enum(*names):
    class Foo(object):
        __metaclass__ = M_add_class_attribs(enumerate(names))
        def __setattr__(self, name, value):  # this makes it read-only
            raise NotImplementedError
    return Foo()

Użyj tego w ten sposób:

Animal = enum('DOG', 'CAT')
Animal.DOG # returns 0
Animal.CAT # returns 1
Animal.DOG = 2 # raises NotImplementedError

jeśli chcesz tylko unikatowych symboli i nie dbasz o wartości, zamień ten wiersz:

__metaclass__ = M_add_class_attribs(enumerate(names))

z tym:

__metaclass__ = M_add_class_attribs((object(), name) for name in names)
użytkowników18695
źródło
11
IMHO byłoby czystsze, gdybyś zmienił enum(names)na enum(*names)- wtedy możesz zostawić dodatkowy nawias podczas dzwonienia.
Chris Lutz,
Lubię to podejście. Właściwie to zmieniłem, aby ustawić wartość atrybutu na taki sam ciąg znaków, jak nazwa, która ma ładną właściwość Animal.DOG == 'DOG', więc automatycznie się dla ciebie nawiążą. (Ogromnie pomaga w wydrukowaniu wyników debugowania.)
Ted Mielczarek
22

Hmmm ... Przypuszczam, że najbliższym wyliczeniem byłby słownik zdefiniowany w następujący sposób:

months = {
    'January': 1,
    'February': 2,
    ...
}

lub

months = dict(
    January=1,
    February=2,
    ...
)

Następnie możesz użyć nazwy symbolicznej dla stałych w następujący sposób:

mymonth = months['January']

Istnieją inne opcje, takie jak lista krotek lub krotka krotek, ale słownik jest jedyną, która zapewnia „symboliczny” (stały ciąg) sposób dostępu do wartości.

Edycja: Podoba mi się również odpowiedź Alexandru!

dguaraglia
źródło
A przede wszystkim możesz łatwo iterować słownik, jeśli chcesz uzyskać dostęp do jego wartości, tak jakbyś potrzebował wartości ciągów, aby pojawiały się jako elementy pola kombi. Zamiast tego użyj słownika jako zamiennika wyliczeń.
LEMUEL ADANE
22

Kolejna, bardzo prosta implementacja wyliczenia w Pythonie, przy użyciu namedtuple:

from collections import namedtuple

def enum(*keys):
    return namedtuple('Enum', keys)(*keys)

MyEnum = enum('FOO', 'BAR', 'BAZ')

lub alternatywnie

# With sequential number values
def enum(*keys):
    return namedtuple('Enum', keys)(*range(len(keys)))

# From a dict / keyword args
def enum(**kwargs):
    return namedtuple('Enum', kwargs.keys())(*kwargs.values())

Podobnie jak w przypadku powyższej podklasy metoda setta umożliwia:

'FOO' in MyEnum
other = MyEnum.FOO
assert other == MyEnum.FOO

Ale ma większą elastyczność, ponieważ może mieć różne klucze i wartości. To pozwala

MyEnum.FOO < MyEnum.BAR

działać zgodnie z oczekiwaniami, jeśli używasz wersji, która wypełnia kolejne wartości liczbowe.

agf
źródło
22

Od Python 3.4 będzie istniało oficjalne wsparcie dla wyliczeń. Dokumentację i przykłady można znaleźć tutaj na stronie dokumentacji Python 3.4 .

Wyliczenia są tworzone przy użyciu składni klas, co czyni je łatwymi do odczytu i zapisu. Alternatywną metodę tworzenia opisano w Functional API. Aby zdefiniować wyliczenie, należy podklasować Wylicz w następujący sposób:

from enum import Enum
class Color(Enum):
     red = 1
     green = 2
     blue = 3
Cyril Gandon
źródło
Obsługiwane jest teraz także tylne portowanie. To jest odpowiednie rozwiązanie.
srock
20

Czego używam:

class Enum(object):
    def __init__(self, names, separator=None):
        self.names = names.split(separator)
        for value, name in enumerate(self.names):
            setattr(self, name.upper(), value)
    def tuples(self):
        return tuple(enumerate(self.names))

Jak używać:

>>> state = Enum('draft published retracted')
>>> state.DRAFT
0
>>> state.RETRACTED
2
>>> state.FOO
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
AttributeError: 'Enum' object has no attribute 'FOO'
>>> state.tuples()
((0, 'draft'), (1, 'published'), (2, 'retracted'))

To daje stałe całkowite, takie jak state.PUBLISHED i dwie krotki do użycia jako opcje w modelach Django.

Luciano Ramalho
źródło
17

davidg zaleca używanie dykt. Idę o krok dalej i używam zestawów:

months = set('January', 'February', ..., 'December')

Teraz możesz przetestować, czy wartość pasuje do jednej z wartości w zestawie, jak poniżej:

if m in months:

podobnie jak dF, zwykle używam tylko ciągów znaków zamiast wyliczeń.

smoking
źródło
yep !, znacznie lepiej, jeśli dziedziczysz zestaw i podajesz metodę getattr !
shahjapan
16

To jest najlepsze, jakie widziałem: „Pierwszorzędne wyliczenia w Pythonie”

http://code.activestate.com/recipes/413486/

Daje ci klasę, a klasa zawiera wszystkie wyliczenia. Wyliczenia można porównywać ze sobą, ale nie mają one żadnej szczególnej wartości; nie można ich użyć jako wartości całkowitej. (Na początku opierałem się temu, ponieważ jestem przyzwyczajony do wyliczeń C, które są wartościami całkowitymi. Ale jeśli nie możesz użyć go jako liczby całkowitej, nie możesz użyć go jako liczby całkowitej przez pomyłkę, więc ogólnie myślę, że to wygrana .) Każde wyliczenie ma unikalną wartość. Możesz drukować wyliczenia, możesz iterować nad nimi, możesz sprawdzić, czy wartość wyliczenia jest „w” wyliczeniu. Jest całkiem kompletny i elegancki.

Edycja (cfi): Powyższy link nie jest kompatybilny z Python 3. Oto mój port enum.py dla Python 3:

def cmp(a,b):
   if a < b: return -1
   if b < a: return 1
   return 0


def Enum(*names):
   ##assert names, "Empty enums are not supported" # <- Don't like empty enums? Uncomment!

   class EnumClass(object):
      __slots__ = names
      def __iter__(self):        return iter(constants)
      def __len__(self):         return len(constants)
      def __getitem__(self, i):  return constants[i]
      def __repr__(self):        return 'Enum' + str(names)
      def __str__(self):         return 'enum ' + str(constants)

   class EnumValue(object):
      __slots__ = ('__value')
      def __init__(self, value): self.__value = value
      Value = property(lambda self: self.__value)
      EnumType = property(lambda self: EnumType)
      def __hash__(self):        return hash(self.__value)
      def __cmp__(self, other):
         # C fans might want to remove the following assertion
         # to make all enums comparable by ordinal value {;))
         assert self.EnumType is other.EnumType, "Only values from the same enum are comparable"
         return cmp(self.__value, other.__value)
      def __lt__(self, other):   return self.__cmp__(other) < 0
      def __eq__(self, other):   return self.__cmp__(other) == 0
      def __invert__(self):      return constants[maximum - self.__value]
      def __nonzero__(self):     return bool(self.__value)
      def __repr__(self):        return str(names[self.__value])

   maximum = len(names) - 1
   constants = [None] * len(names)
   for i, each in enumerate(names):
      val = EnumValue(i)
      setattr(EnumClass, each, val)
      constants[i] = val
   constants = tuple(constants)
   EnumType = EnumClass()
   return EnumType


if __name__ == '__main__':
   print( '\n*** Enum Demo ***')
   print( '--- Days of week ---')
   Days = Enum('Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su')
   print( Days)
   print( Days.Mo)
   print( Days.Fr)
   print( Days.Mo < Days.Fr)
   print( list(Days))
   for each in Days:
      print( 'Day:', each)
   print( '--- Yes/No ---')
   Confirmation = Enum('No', 'Yes')
   answer = Confirmation.No
   print( 'Your answer is not', ~answer)
CFI
źródło
Ten przepis został wykorzystany jako podstawa PEP, która została odrzucona. python.org/dev/peps/pep-0354 Jedno rozszerzenie, które lubię: wartości enum powinny mieć zmienną składową, która pozwala uzyskać wewnętrzną wartość całkowitą. Przez przypadek nie powinno być możliwe rzutowanie wyliczenia na liczbę całkowitą, więc .__int__()metoda powinna zgłosić wyjątek dla wyliczenia; ale powinien istnieć sposób na uzyskanie wartości. I powinno być możliwe ustawienie określonych wartości całkowitych w czasie definicji klasy, abyś mógł użyć wyliczenia dla rzeczy takich jak stałe w statmodule.
steveha
16

Nie komplikuj:

class Enum(object): 
    def __init__(self, tupleList):
            self.tupleList = tupleList

    def __getattr__(self, name):
            return self.tupleList.index(name)

Następnie:

DIRECTION = Enum(('UP', 'DOWN', 'LEFT', 'RIGHT'))
DIRECTION.DOWN
1
zagrożenia89
źródło
13

Miałem okazję potrzebować klasy Enum do dekodowania formatu pliku binarnego. Funkcje, o które mi reprchodziło, to zwięzła definicja wyliczenia, możliwość swobodnego tworzenia wystąpień wyliczenia przez wartość całkowitą lub ciąg znaków oraz przydatna esentacja. Oto, z czym skończyłem:

>>> class Enum(int):
...     def __new__(cls, value):
...         if isinstance(value, str):
...             return getattr(cls, value)
...         elif isinstance(value, int):
...             return cls.__index[value]
...     def __str__(self): return self.__name
...     def __repr__(self): return "%s.%s" % (type(self).__name__, self.__name)
...     class __metaclass__(type):
...         def __new__(mcls, name, bases, attrs):
...             attrs['__slots__'] = ['_Enum__name']
...             cls = type.__new__(mcls, name, bases, attrs)
...             cls._Enum__index = _index = {}
...             for base in reversed(bases):
...                 if hasattr(base, '_Enum__index'):
...                     _index.update(base._Enum__index)
...             # create all of the instances of the new class
...             for attr in attrs.keys():
...                 value = attrs[attr]
...                 if isinstance(value, int):
...                     evalue = int.__new__(cls, value)
...                     evalue._Enum__name = attr
...                     _index[value] = evalue
...                     setattr(cls, attr, evalue)
...             return cls
... 

Kapryśny przykład użycia:

>>> class Citrus(Enum):
...     Lemon = 1
...     Lime = 2
... 
>>> Citrus.Lemon
Citrus.Lemon
>>> 
>>> Citrus(1)
Citrus.Lemon
>>> Citrus(5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in __new__
KeyError: 5
>>> class Fruit(Citrus):
...     Apple = 3
...     Banana = 4
... 
>>> Fruit.Apple
Fruit.Apple
>>> Fruit.Lemon
Citrus.Lemon
>>> Fruit(1)
Citrus.Lemon
>>> Fruit(3)
Fruit.Apple
>>> "%d %s %r" % ((Fruit.Apple,)*3)
'3 Apple Fruit.Apple'
>>> Fruit(1) is Citrus.Lemon
True

Kluczowe cechy:

  • str(), int()i repr()wszystkie dają najbardziej użyteczne wyjście, odpowiednio nazwę enumartion, jego wartość całkowitą i wyrażenie Python, które zwraca wartość do wyliczenia.
  • Wartości wyliczone zwrócone przez konstruktora są ściśle ograniczone do predefiniowanych wartości, bez przypadkowych wartości wyliczania.
  • Wartości wyliczone są singletonami; można je ściśle porównać zis
TokenMacGuy
źródło
Naprawdę podoba mi się użycie superklasy z własną metaklasą, aby ułatwić definiowanie wyliczeń. Brakuje tutaj metody __contains__. Chciałbym móc sprawdzić, czy dana zmienna jest częścią wyliczenia - głównie dlatego, że chcę wyliczeń dla dopuszczalnych wartości parametru funkcji.
xorsyst,
To właściwie nieco przycięta wersja tej, którą pierwotnie stworzyłem (którą można znaleźć tutaj: enum_strict.py ) v, która definiuje __instancecheck__metodę. Klasy nie są zbiorami instancji, więc 1 in Fruitjest absurdalne. Jednak wersja połączona obsługuje, isinstance(1, Fruit)co byłoby bardziej poprawne pod względem pojęcia klas i instancji.
SingleNegationElimination
Ale zapominając o zajęciach i myśleniu w kategoriach wyliczeń, warto myśleć o nich jako o zbiorach. Na przykład mogę mieć wyliczenie trybów otwierania plików (MODE.OPEN, MODE.WRITE itp.). Chcę zweryfikować parametry mojej funkcji: jeśli tryb w TRYBIE: odczytuje znacznie lepiej niż isintance (tryb, tryb)
xorsyst
W GitHub: github.com/hmeine/named_constants
gansub.com/hmeine/named_constants
11

Nowym standardem w Pythonie jest PEP 435 , więc klasa Enum będzie dostępna w przyszłych wersjach Pythona:

>>> from enum import Enum

Jednak, aby rozpocząć korzystanie z niego teraz, możesz zainstalować oryginalną bibliotekę, która motywowała PEP:

$ pip install flufl.enum

Następnie możesz użyć go zgodnie z jego przewodnikiem online :

>>> from flufl.enum import Enum
>>> class Colors(Enum):
...     red = 1
...     green = 2
...     blue = 3
>>> for color in Colors: print color
Colors.red
Colors.green
Colors.blue
Riaz Rizvi
źródło
10
def enum(*sequential, **named):
    enums = dict(zip(sequential, [object() for _ in range(len(sequential))]), **named)
    return type('Enum', (), enums)

Jeśli go nazwiesz, to jest twój problem, ale jeśli nie tworzysz obiektów zamiast wartości, możesz to zrobić:

>>> DOG = enum('BARK', 'WALK', 'SIT')
>>> CAT = enum('MEOW', 'WALK', 'SIT')
>>> DOG.WALK == CAT.WALK
False

Korzystając z innych implementacji wymienionych tutaj (również w przypadku nazwanych instancji w moim przykładzie), musisz mieć pewność, że nigdy nie próbujesz porównywać obiektów z różnych wyliczeń. Oto możliwe pułapki:

>>> DOG = enum('BARK'=1, 'WALK'=2, 'SIT'=3)
>>> CAT = enum('WALK'=1, 'SIT'=2)
>>> pet1_state = DOG.BARK
>>> pet2_state = CAT.WALK
>>> pet1_state == pet2_state
True

Yikes!

estani
źródło
9

Naprawdę podoba mi się rozwiązanie Aleca Thomasa (http://stackoverflow.com/a/1695250):

def enum(**enums):
    '''simple constant "enums"'''
    return type('Enum', (object,), enums)

Jest elegancki i czysty, ale to tylko funkcja, która tworzy klasę o określonych atrybutach.

Po niewielkiej modyfikacji funkcji możemy sprawić, by działała nieco bardziej „enumy”:

UWAGA: Utworzyłem następujące przykłady, próbując odtworzyć zachowanie „enums” nowego stylu pygtk (takich jak Gtk.MessageType.WARNING)

def enum_base(t, **enums):
    '''enums with a base class'''
    T = type('Enum', (t,), {})
    for key,val in enums.items():
        setattr(T, key, T(val))

    return T

Tworzy to wyliczenie na podstawie określonego typu. Oprócz zapewnienia dostępu do atrybutów, podobnie jak poprzednia funkcja, zachowuje się tak, jak można oczekiwać od Enum w odniesieniu do typów. Dziedziczy również klasę podstawową.

Na przykład wyliczenia liczb całkowitych:

>>> Numbers = enum_base(int, ONE=1, TWO=2, THREE=3)
>>> Numbers.ONE
1
>>> x = Numbers.TWO
>>> 10 + x
12
>>> type(Numbers)
<type 'type'>
>>> type(Numbers.ONE)
<class 'Enum'>
>>> isinstance(x, Numbers)
True

Inną interesującą rzeczą, którą można zrobić za pomocą tej metody, jest dostosowanie określonego zachowania poprzez przesłonięcie wbudowanych metod:

def enum_repr(t, **enums):
    '''enums with a base class and repr() output'''
    class Enum(t):
        def __repr__(self):
            return '<enum {0} of type Enum({1})>'.format(self._name, t.__name__)

    for key,val in enums.items():
        i = Enum(val)
        i._name = key
        setattr(Enum, key, i)

    return Enum



>>> Numbers = enum_repr(int, ONE=1, TWO=2, THREE=3)
>>> repr(Numbers.ONE)
'<enum ONE of type Enum(int)>'
>>> str(Numbers.ONE)
'1'
bj0
źródło
pomysł typu „bazowego” jest fajny :)
MestreLion
tak, zwróć uwagę, że możesz to zrobić również z nowym Enumem
bj0
7

Pakiet wyliczający od PyPI zapewnia niezawodną implementację wyliczania. Wcześniejsza odpowiedź wspominała PEP 354; zostało to odrzucone, ale propozycja została wdrożona http://pypi.python.org/pypi/enum .

Użycie jest łatwe i eleganckie:

>>> from enum import Enum
>>> Colors = Enum('red', 'blue', 'green')
>>> shirt_color = Colors.green
>>> shirt_color = Colors[2]
>>> shirt_color > Colors.red
True
>>> shirt_color.index
2
>>> str(shirt_color)
'green'
Ashwin
źródło
5

Sugestia Alexandru dotycząca użycia stałych klasowych dla wyliczeń działa całkiem dobrze.

Chciałbym również dodać słownik dla każdego zestawu stałych, aby wyszukać reprezentację ciągu czytelną dla człowieka.

Służy to dwóm celom: a) zapewnia prosty sposób wydrukowania enum ib) słownik logicznie grupuje stałe, aby można było przetestować członkostwo.

class Animal:    
  TYPE_DOG = 1
  TYPE_CAT = 2

  type2str = {
    TYPE_DOG: "dog",
    TYPE_CAT: "cat"
  }

  def __init__(self, type_):
    assert type_ in self.type2str.keys()
    self._type = type_

  def __repr__(self):
    return "<%s type=%s>" % (
        self.__class__.__name__, self.type2str[self._type].upper())
Rick Harris
źródło
4

Oto podejście z kilkoma różnymi cechami, które uważam za wartościowe:

  • pozwala na porównanie> i <na podstawie kolejności w wyliczeniu, a nie porządku leksykalnego
  • może adresować pozycję według nazwy, właściwości lub indeksu: xa, x ['a'] lub x [0]
  • obsługuje operacje krojenia takie jak [:] lub [-1]

i, co najważniejsze, zapobiega porównywaniu różnych rodzajów wyliczeń !

Oparte ściśle na http://code.activestate.com/recipes/413486-first-class-enums-in-python .

Wiele dokumentów zawartych tutaj w celu zilustrowania różnic w tym podejściu.

def enum(*names):
    """
SYNOPSIS
    Well-behaved enumerated type, easier than creating custom classes

DESCRIPTION
    Create a custom type that implements an enumeration.  Similar in concept
    to a C enum but with some additional capabilities and protections.  See
    http://code.activestate.com/recipes/413486-first-class-enums-in-python/.

PARAMETERS
    names       Ordered list of names.  The order in which names are given
                will be the sort order in the enum type.  Duplicate names
                are not allowed.  Unicode names are mapped to ASCII.

RETURNS
    Object of type enum, with the input names and the enumerated values.

EXAMPLES
    >>> letters = enum('a','e','i','o','u','b','c','y','z')
    >>> letters.a < letters.e
    True

    ## index by property
    >>> letters.a
    a

    ## index by position
    >>> letters[0]
    a

    ## index by name, helpful for bridging string inputs to enum
    >>> letters['a']
    a

    ## sorting by order in the enum() create, not character value
    >>> letters.u < letters.b
    True

    ## normal slicing operations available
    >>> letters[-1]
    z

    ## error since there are not 100 items in enum
    >>> letters[99]
    Traceback (most recent call last):
        ...
    IndexError: tuple index out of range

    ## error since name does not exist in enum
    >>> letters['ggg']
    Traceback (most recent call last):
        ...
    ValueError: tuple.index(x): x not in tuple

    ## enums must be named using valid Python identifiers
    >>> numbers = enum(1,2,3,4)
    Traceback (most recent call last):
        ...
    AssertionError: Enum values must be string or unicode

    >>> a = enum('-a','-b')
    Traceback (most recent call last):
        ...
    TypeError: Error when calling the metaclass bases
        __slots__ must be identifiers

    ## create another enum
    >>> tags = enum('a','b','c')
    >>> tags.a
    a
    >>> letters.a
    a

    ## can't compare values from different enums
    >>> letters.a == tags.a
    Traceback (most recent call last):
        ...
    AssertionError: Only values from the same enum are comparable

    >>> letters.a < tags.a
    Traceback (most recent call last):
        ...
    AssertionError: Only values from the same enum are comparable

    ## can't update enum after create
    >>> letters.a = 'x'
    Traceback (most recent call last):
        ...
    AttributeError: 'EnumClass' object attribute 'a' is read-only

    ## can't update enum after create
    >>> del letters.u
    Traceback (most recent call last):
        ...
    AttributeError: 'EnumClass' object attribute 'u' is read-only

    ## can't have non-unique enum values
    >>> x = enum('a','b','c','a')
    Traceback (most recent call last):
        ...
    AssertionError: Enums must not repeat values

    ## can't have zero enum values
    >>> x = enum()
    Traceback (most recent call last):
        ...
    AssertionError: Empty enums are not supported

    ## can't have enum values that look like special function names
    ## since these could collide and lead to non-obvious errors
    >>> x = enum('a','b','c','__cmp__')
    Traceback (most recent call last):
        ...
    AssertionError: Enum values beginning with __ are not supported

LIMITATIONS
    Enum values of unicode type are not preserved, mapped to ASCII instead.

    """
    ## must have at least one enum value
    assert names, 'Empty enums are not supported'
    ## enum values must be strings
    assert len([i for i in names if not isinstance(i, types.StringTypes) and not \
        isinstance(i, unicode)]) == 0, 'Enum values must be string or unicode'
    ## enum values must not collide with special function names
    assert len([i for i in names if i.startswith("__")]) == 0,\
        'Enum values beginning with __ are not supported'
    ## each enum value must be unique from all others
    assert names == uniquify(names), 'Enums must not repeat values'

    class EnumClass(object):
        """ See parent function for explanation """

        __slots__ = names

        def __iter__(self):
            return iter(constants)

        def __len__(self):
            return len(constants)

        def __getitem__(self, i):
            ## this makes xx['name'] possible
            if isinstance(i, types.StringTypes):
                i = names.index(i)
            ## handles the more normal xx[0]
            return constants[i]

        def __repr__(self):
            return 'enum' + str(names)

        def __str__(self):
            return 'enum ' + str(constants)

        def index(self, i):
            return names.index(i)

    class EnumValue(object):
        """ See parent function for explanation """

        __slots__ = ('__value')

        def __init__(self, value):
            self.__value = value

        value = property(lambda self: self.__value)

        enumtype = property(lambda self: enumtype)

        def __hash__(self):
            return hash(self.__value)

        def __cmp__(self, other):
            assert self.enumtype is other.enumtype, 'Only values from the same enum are comparable'
            return cmp(self.value, other.value)

        def __invert__(self):
            return constants[maximum - self.value]

        def __nonzero__(self):
            ## return bool(self.value)
            ## Original code led to bool(x[0])==False, not correct
            return True

        def __repr__(self):
            return str(names[self.value])

    maximum = len(names) - 1
    constants = [None] * len(names)
    for i, each in enumerate(names):
        val = EnumValue(i)
        setattr(EnumClass, each, val)
        constants[i] = val
    constants = tuple(constants)
    enumtype = EnumClass()
    return enumtype
Chris Johnson
źródło
3

Oto wariant rozwiązania Aleca Thomasa :

def enum(*args, **kwargs):
    return type('Enum', (), dict((y, x) for x, y in enumerate(args), **kwargs)) 

x = enum('POOH', 'TIGGER', 'EEYORE', 'ROO', 'PIGLET', 'RABBIT', 'OWL')
assert x.POOH == 0
assert x.TIGGER == 1
Roy Hyunjin Han
źródło
3

To rozwiązanie jest prostym sposobem uzyskania klasy dla wyliczenia zdefiniowanego jako lista (koniec z denerwującymi przypisaniami liczb całkowitych):

enumeration.py:

import new

def create(class_name, names):
    return new.classobj(
        class_name, (object,), dict((y, x) for x, y in enumerate(names))
    )

przyklad.py:

import enumeration

Colors = enumeration.create('Colors', (
    'red',
    'orange',
    'yellow',
    'green',
    'blue',
    'violet',
))
Michael Truog
źródło
2
Jest to bardzo stary sposób tworzenia klas. Dlaczego nie po prostu użyć type(class_name, (object,), dict(...))zamiast tego?
zakończenie
3

Podczas gdy pierwotna propozycja enum, PEP 354 , została odrzucona wiele lat temu, wciąż powraca. Jakiś rodzaj wyliczenia miał być dodany do 3.2, ale został przesunięty z powrotem do 3.3, a następnie zapomniany. A teraz jest PEP 435 przeznaczony do włączenia w Pythonie 3.4. Referencyjnym wdrożeniem PEP 435 jest flufl.enum.

W kwietniu 2013 r. Wydaje się, że istnieje ogólna zgoda co do tego, że należy coś dodać do standardowej biblioteki w 3.4 - o ile ludzie mogą uzgodnić, co to powinno być. To trudna część. Zobacz wątki zaczynające się tu i tutaj oraz pół tuzina innych wątków na początku 2013 r.

Tymczasem za każdym razem, gdy to pojawia się, na PyPI, ActiveState itp. Pojawia się mnóstwo nowych projektów i implementacji, więc jeśli nie podoba ci się projekt FLUFL, spróbuj wyszukać PyPI .

abarnert
źródło
3

Użyj następujących.

TYPE = {'EAN13':   u'EAN-13',
        'CODE39':  u'Code 39',
        'CODE128': u'Code 128',
        'i25':     u'Interleaved 2 of 5',}

>>> TYPE.items()
[('EAN13', u'EAN-13'), ('i25', u'Interleaved 2 of 5'), ('CODE39', u'Code 39'), ('CODE128', u'Code 128')]
>>> TYPE.keys()
['EAN13', 'i25', 'CODE39', 'CODE128']
>>> TYPE.values()
[u'EAN-13', u'Interleaved 2 of 5', u'Code 39', u'Code 128']

Użyłem tego przy wyborze modeli Django i wygląda to bardzo pytonicznie. To naprawdę nie jest Enum, ale działa.

Natim
źródło