Enum HOWTO

Ett Enum är en uppsättning symboliska namn som är bundna till unika värden. De liknar globala variabler, men de erbjuder en mer användbar repr(), gruppering, typsäkerhet och några andra funktioner.

De är mest användbara när du har en variabel som kan ta ett av ett begränsat urval av värden. Till exempel veckodagarna:

>>> from enum import Enum
>>> class Weekday(Enum):
...     MONDAY = 1
...     TUESDAY = 2
...     WEDNESDAY = 3
...     THURSDAY = 4
...     FRIDAY = 5
...     SATURDAY = 6
...     SUNDAY = 7

Eller kanske RGB-primärfärgerna:

>>> from enum import Enum
>>> class Color(Enum):
...     RED = 1
...     GREEN = 2
...     BLUE = 3

Som du kan se är det lika enkelt att skapa en Enum som att skriva en klass som ärver från Enum själv.

Anteckning

Fall med Enum-medlemmar

Eftersom enumer används för att representera konstanter, och för att undvika namnkrockar mellan metoder/attribut i mixin-klasser och enumnamn, rekommenderar vi starkt att du använder UPPER_CASE-namn för medlemmar, och vi kommer att använda den stilen i våra exempel.

Beroende på enumets karaktär kan en medlems värde vara viktigt eller inte, men i vilket fall som helst kan det värdet användas för att få motsvarande medlem:

>>> Weekday(3)
<Weekday.WEDNESDAY: 3>

Som du kan se visar repr() för en medlem enumnamnet, medlemsnamnet och värdet. str() för en medlem visar bara enumnamnet och medlemsnamnet:

>>> print(Weekday.THURSDAY)
Weekday.THURSDAY

Typen av en uppräkningsmedlem är den uppräkning som den tillhör:

>>> type(Weekday.MONDAY)
<enum 'Weekday'>
>>> isinstance(Weekday.FRIDAY, Weekday)
True

Enum-medlemmar har ett attribut som bara innehåller deras name:

>>> print(Weekday.TUESDAY.name)
TUESDAY

På samma sätt har de ett attribut för sin value:

>>> Weekday.WEDNESDAY.value
3

Till skillnad från många språk som behandlar uppräkningar enbart som namn/värde-par, kan Python Enums få beteende tillagt. Till exempel har datetime.date två metoder för att returnera veckodagen: weekday() och isoweekday(). Skillnaden är att en av dem räknar från 0-6 och den andra från 1-7. Istället för att hålla reda på det själva kan vi lägga till en metod till Weekday enum för att extrahera dagen från date instansen och returnera den matchande enum medlemmen:

@classmethod
def from_date(cls, date):
    return cls(date.isoweekday())

Den kompletta Weekday enum ser nu ut så här:

>>> class Weekday(Enum):
...     MONDAY = 1
...     TUESDAY = 2
...     WEDNESDAY = 3
...     THURSDAY = 4
...     FRIDAY = 5
...     SATURDAY = 6
...     SUNDAY = 7
...     #
...     @classmethod
...     def from_date(cls, date):
...         return cls(date.isoweekday())

Nu kan vi ta reda på vad det är för dag! Observera:

>>> from datetime import date
>>> Weekday.from_date(date.today())
<Weekday.TUESDAY: 2>

Om du läser det här någon annan dag får du naturligtvis se den dagen i stället.

Denna Weekday enum är bra om vår variabel bara behöver en dag, men vad händer om vi behöver flera? Vi kanske skriver en funktion för att plotta sysslor under en vecka, och vill inte använda en list – vi kan använda en annan typ av Enum:

>>> from enum import Flag
>>> class Weekday(Flag):
...     MONDAY = 1
...     TUESDAY = 2
...     WEDNESDAY = 4
...     THURSDAY = 8
...     FRIDAY = 16
...     SATURDAY = 32
...     SUNDAY = 64

Vi har ändrat två saker: vi ärver från Flag, och värdena är alla potenser av 2.

Precis som i den ursprungliga Weekday enum ovan kan vi ha ett enda val:

>>> first_week_day = Weekday.MONDAY
>>> first_week_day
<Weekday.MONDAY: 1>

Men Flag tillåter oss också att kombinera flera medlemmar i en enda variabel:

>>> weekend = Weekday.SATURDAY | Weekday.SUNDAY
>>> weekend
<Weekday.SATURDAY|SUNDAY: 96>

Du kan till och med iterera över en Flag-variabel:

>>> for day in weekend:
...     print(day)
Weekday.SATURDAY
Weekday.SUNDAY

Okej, låt oss göra några sysslor:

>>> chores_for_ethan = {
...     'feed the cat': Weekday.MONDAY | Weekday.WEDNESDAY | Weekday.FRIDAY,
...     'do the dishes': Weekday.TUESDAY | Weekday.THURSDAY,
...     'answer SO questions': Weekday.SATURDAY,
...     }

Och en funktion för att visa sysslorna för en viss dag:

>>> def show_chores(chores, day):
...     for chore, days in chores.items():
...         if day in days:
...             print(chore)
...
>>> show_chores(chores_for_ethan, Weekday.SATURDAY)
answer SO questions

I de fall där medlemmarnas faktiska värden inte spelar någon roll kan du spara dig lite arbete och använda auto() för värdena:

>>> from enum import auto
>>> class Weekday(Flag):
...     MONDAY = auto()
...     TUESDAY = auto()
...     WEDNESDAY = auto()
...     THURSDAY = auto()
...     FRIDAY = auto()
...     SATURDAY = auto()
...     SUNDAY = auto()
...     WEEKEND = SATURDAY | SUNDAY

Programmatisk åtkomst till uppräkningsmedlemmar och deras attribut

Ibland är det användbart att komma åt medlemmar i uppräkningar programmatiskt (t.ex. situationer där Color.RED inte fungerar eftersom den exakta färgen inte är känd vid programskrivningstillfället). Enum tillåter sådan åtkomst:

>>> Color(1)
<Color.RED: 1>
>>> Color(3)
<Color.BLUE: 3>

Om du vill komma åt enum-medlemmar med namn använder du item access:

>>> Color['RED']
<Color.RED: 1>
>>> Color['GREEN']
<Color.GREEN: 2>

Om du har en enum-medlem och behöver dess name eller value:

>>> member = Color.RED
>>> member.name
'RED'
>>> member.value
1

Duplicering av enum-medlemmar och -värden

Att ha två enum-medlemmar med samma namn är ogiltigt:

>>> class Shape(Enum):
...     SQUARE = 2
...     SQUARE = 3
...
Traceback (most recent call last):
...
TypeError: 'SQUARE' already defined as 2

En enum-medlem kan dock ha andra namn associerade med sig. Givet två poster A och B med samma värde (och A definierad först), är B ett alias för medlemmen A. By-value lookup av värdet på A kommer att returnera medlemmen A. By-name lookup av A kommer att returnera medlemmen A. By-name lookup av B kommer också att returnera medlemmen A:

>>> class Shape(Enum):
...     SQUARE = 2
...     DIAMOND = 1
...     CIRCLE = 3
...     ALIAS_FOR_SQUARE = 2
...
>>> Shape.SQUARE
<Shape.SQUARE: 2>
>>> Shape.ALIAS_FOR_SQUARE
<Shape.SQUARE: 2>
>>> Shape(2)
<Shape.SQUARE: 2>

Anteckning

Det är inte tillåtet att försöka skapa en medlem med samma namn som ett redan definierat attribut (en annan medlem, en metod etc.) eller att försöka skapa ett attribut med samma namn som en medlem.

Säkerställa unika uppräkningsvärden

Som standard tillåter uppräkningar flera namn som alias för samma värde. När detta beteende inte är önskvärt kan du använda unique() dekoratorn:

>>> from enum import Enum, unique
>>> @unique
... class Mistake(Enum):
...     ONE = 1
...     TWO = 2
...     THREE = 3
...     FOUR = 3
...
Traceback (most recent call last):
...
ValueError: duplicate values found in <enum 'Mistake'>: FOUR -> THREE

Använda automatiska värden

Om det exakta värdet är oviktigt kan du använda auto:

>>> from enum import Enum, auto
>>> class Color(Enum):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> [member.value for member in Color]
[1, 2, 3]

Värdena väljs av _generate_next_value_(), som kan åsidosättas:

>>> class AutoName(Enum):
...     @staticmethod
...     def _generate_next_value_(name, start, count, last_values):
...         return name
...
>>> class Ordinal(AutoName):
...     NORTH = auto()
...     SOUTH = auto()
...     EAST = auto()
...     WEST = auto()
...
>>> [member.value for member in Ordinal]
['NORTH', 'SOUTH', 'EAST', 'WEST']

Anteckning

Metoden _generate_next_value_() måste definieras före alla medlemmar.

Iteration

Iterering över medlemmarna i ett enum ger inte alias:

>>> list(Shape)
[<Shape.SQUARE: 2>, <Shape.DIAMOND: 1>, <Shape.CIRCLE: 3>]
>>> list(Weekday)
[<Weekday.MONDAY: 1>, <Weekday.TUESDAY: 2>, <Weekday.WEDNESDAY: 4>, <Weekday.THURSDAY: 8>, <Weekday.FRIDAY: 16>, <Weekday.SATURDAY: 32>, <Weekday.SUNDAY: 64>]

Observera att aliasen Shape.ALIAS_FOR_SQUARE och Weekday.WEEKEND inte visas.

Specialattributet __members__ är en skrivskyddad ordnad mappning av namn till medlemmar. Den innehåller alla namn som definieras i uppräkningen, inklusive alias:

>>> for name, member in Shape.__members__.items():
...     name, member
...
('SQUARE', <Shape.SQUARE: 2>)
('DIAMOND', <Shape.DIAMOND: 1>)
('CIRCLE', <Shape.CIRCLE: 3>)
('ALIAS_FOR_SQUARE', <Shape.SQUARE: 2>)

Attributet __members__ kan användas för detaljerad programmatisk åtkomst till uppräkningens medlemmar. Till exempel, hitta alla alias:

>>> [name for name, member in Shape.__members__.items() if member.name != name]
['ALIAS_FOR_SQUARE']

Anteckning

Alias för flaggor inkluderar värden med flera flaggor inställda, t.ex. 3, och inga flaggor inställda, t.ex. 0.

Jämförelser

Uppräkningens medlemmar jämförs med hjälp av identity:

>>> Color.RED is Color.RED
True
>>> Color.RED is Color.BLUE
False
>>> Color.RED is not Color.BLUE
True

Ordnade jämförelser mellan enumerationsvärden stöds inte. Enum-medlemmar är inte heltal (men se IntEnum nedan):

>>> Color.RED < Color.BLUE
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'Color' and 'Color'

Jämlikhetsjämförelser definieras genom:

>>> Color.BLUE == Color.RED
False
>>> Color.BLUE != Color.RED
True
>>> Color.BLUE == Color.BLUE
True

Jämförelser mot värden som inte är uppräkningsvärden kommer alltid att jämföra inte lika (återigen, IntEnum var uttryckligen utformad för att bete sig annorlunda, se nedan):

>>> Color.BLUE == 2
False

Varning

Det är möjligt att ladda om moduler - om en omladdad modul innehåller enumer kommer de att återskapas, och de nya medlemmarna kanske inte är identiska/likvärdiga med de ursprungliga medlemmarna.

Tillåtna medlemmar och attribut för uppräkningar

I de flesta av exemplen ovan används heltal för uppräkningsvärden. Att använda heltal är kort och praktiskt (och tillhandahålls som standard av Functional API), men inte strikt påtvingat. I de allra flesta användningsfall bryr man sig inte om vad det faktiska värdet på en uppräkning är. Men om värdet är viktigt kan uppräkningar ha godtyckliga värden.

Uppräkningar är Python-klasser, och kan ha metoder och specialmetoder som vanligt. Om vi har den här uppräkningen:

>>> class Mood(Enum):
...     FUNKY = 1
...     HAPPY = 3
...
...     def describe(self):
...         # self is the member here
...         return self.name, self.value
...
...     def __str__(self):
...         return 'my custom str! {0}'.format(self.value)
...
...     @classmethod
...     def favorite_mood(cls):
...         # cls here is the enumeration
...         return cls.HAPPY
...

Sedan:

>>> Mood.favorite_mood()
<Mood.HAPPY: 3>
>>> Mood.HAPPY.describe()
('HAPPY', 3)
>>> str(Mood.FUNKY)
'my custom str! 1'

Reglerna för vad som är tillåtet är följande: namn som börjar och slutar med ett enda understreck är reserverade av enum och kan inte användas; alla andra attribut som definieras inom en uppräkning blir medlemmar av denna uppräkning, med undantag för speciella metoder (__str__(), __add__(), etc.), deskriptorer (metoder är också deskriptorer) och variabelnamn som anges i _ignore_.

Obs: om din uppräkning definierar __new__() och/eller __init__(), kommer alla värden som ges till enum-medlemmen att skickas till dessa metoder. Se Planet för ett exempel.

Anteckning

Metoden __new__(), om den är definierad, används vid skapandet av Enum-medlemmarna; den ersätts sedan av Enums __new__() som används efter att klassen skapats för uppslagning av befintliga medlemmar. Se När ska man använda __new__() vs. __init__() för mer information.

Begränsad subklassning av Enum

En ny Enum-klass måste ha en bas-enumklass, upp till en konkret datatyp och så många object-baserade mixin-klasser som behövs. Ordningen på dessa basklasser är:

class EnumName([mix-in, ...,] [data-type,] base-enum):
    pass

Dessutom är subklassning av en uppräkning endast tillåten om uppräkningen inte definierar några medlemmar. Så detta är förbjudet:

>>> class MoreColor(Color):
...     PINK = 17
...
Traceback (most recent call last):
...
TypeError: <enum 'MoreColor'> cannot extend <enum 'Color'>

Men detta är tillåtet:

>>> class Foo(Enum):
...     def some_behavior(self):
...         pass
...
>>> class Bar(Foo):
...     HAPPY = 1
...     SAD = 2
...

Att tillåta subklassning av enumerationer som definierar medlemmar skulle leda till ett brott mot några viktiga invarianter av typer och instanser. Å andra sidan är det vettigt att tillåta att en grupp av uppräkningar delar vissa gemensamma beteenden. (Se OrderedEnum för ett exempel.)

Stöd för dataklasser

Vid arv från en dataclass utelämnar __repr__() den ärvda klassens namn. Till exempel:

>>> from dataclasses import dataclass, field
>>> @dataclass
... class CreatureDataMixin:
...     size: str
...     legs: int
...     tail: bool = field(repr=False, default=True)
...
>>> class Creature(CreatureDataMixin, Enum):
...     BEETLE = 'small', 6
...     DOG = 'medium', 4
...
>>> Creature.DOG
<Creature.DOG: size='medium', legs=4>

Använd dataclass()-argumentet repr=False för att använda standard repr().

Ändrad i version 3.12: Endast dataklassens fält visas i värdeområdet, inte dataklassens namn.

Anteckning

Att lägga till dataclass() dekorator till Enum och dess underklasser stöds inte. Det kommer inte att ge upphov till några fel, men det kommer att ge mycket konstiga resultat vid körning, till exempel att medlemmar är lika med varandra:

>>> @dataclass               # don't do this: it does not make any sense
... class Color(Enum):
...    RED = 1
...    BLUE = 2
...
>>> Color.RED is Color.BLUE
False
>>> Color.RED == Color.BLUE  # problem is here: they should not be equal
True

Inläggning

Uppräkningar kan vara inlagda och oinlagda:

>>> from test.test_enum import Fruit
>>> from pickle import dumps, loads
>>> Fruit.TOMATO is loads(dumps(Fruit.TOMATO))
True

De vanliga restriktionerna för pickling gäller: picklbara enumer måste definieras på den översta nivån i en modul, eftersom unpickling kräver att de kan importeras från den modulen.

Anteckning

Med pickle protocol version 4 är det möjligt att enkelt plocka enumer som är inbäddade i andra klasser.

Det är möjligt att modifiera hur enum-medlemmar syltas/osyltas genom att definiera __reduce_ex__() i enumerationsklassen. Standardmetoden är by-value, men enumerationer med komplicerade värden kanske vill använda by-name:

>>> import enum
>>> class MyEnum(enum.Enum):
...     __reduce_ex__ = enum.pickle_by_enum_name

Anteckning

Att använda by-name för flaggor rekommenderas inte, eftersom icke namngivna alias inte kommer att unpickle.

Funktionellt API

Klassen Enum är anropsbar och tillhandahåller följande funktionella API:

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG')
>>> Animal
<enum 'Animal'>
>>> Animal.ANT
<Animal.ANT: 1>
>>> list(Animal)
[<Animal.ANT: 1>, <Animal.BEE: 2>, <Animal.CAT: 3>, <Animal.DOG: 4>]

Semantiken för detta API liknar namedtuple. Det första argumentet i anropet till Enum är namnet på uppräkningen.

Det andra argumentet är källan till uppräkningens medlemsnamn. Det kan vara en namnsträng separerad med vitt mellanrum, en namnsekvens, en sekvens av 2-tupler med nyckel/värde-par eller en mappning (t.ex. en ordbok) av namn till värden. De två sista alternativen gör det möjligt att tilldela godtyckliga värden till uppräkningar; de övriga tilldelar automatiskt ökande heltal som börjar med 1 (använd parametern start för att ange ett annat startvärde). En ny klass som härrör från Enum returneras. Med andra ord, ovanstående tilldelning till Animal är likvärdig med:

>>> class Animal(Enum):
...     ANT = 1
...     BEE = 2
...     CAT = 3
...     DOG = 4
...

Anledningen till att startnumret som standard är 1 och inte 0 är att 0 är False i boolesk mening, men som standard utvärderas alla enum-medlemmar till True.

Pickling av uppräkningar som skapats med det funktionella API:et kan vara knepigt eftersom implementeringsdetaljer för ramstacken används för att försöka lista ut vilken modul uppräkningen skapas i (t.ex. kommer det att misslyckas om du använder en verktygsfunktion i en separat modul, och kanske inte heller fungerar på IronPython eller Jython). Lösningen är att ange modulnamnet explicit enligt följande:

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', module=__name__)

Varning

Om module inte anges, och Enum inte kan avgöra vad det är, kommer de nya Enum-medlemmarna inte att vara obearbetbara; för att hålla felen närmare källan kommer bearbetningen att avaktiveras.

Det nya pickle-protokollet 4 förlitar sig också, under vissa omständigheter, på att __qualname__ anges till den plats där pickle kan hitta klassen. Till exempel, om klassen gjordes tillgänglig i klassen SomeData i det globala omfånget:

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', qualname='SomeData.Animal')

Den fullständiga signaturen är:

Enum(
    value='NewEnumName',
    names=<...>,
    *,
    module='...',
    qualname='...',
    type=<mixed-in class>,
    start=1,
    )
  • värde: Det som den nya enumklassen kommer att registrera som sitt namn.

  • namn: Enumets medlemmar. Detta kan vara en sträng separerad med blanksteg eller kommatecken (värdena börjar på 1 om inget annat anges):

    'RED GREEN BLUE' | 'RED,GREEN,BLUE' | 'RED, GREEN, BLUE'
    

    eller en iterator av namn:

    ['RED', 'GREEN', 'BLUE']
    

    eller en iterator av (namn, värde)-par:

    [('CYAN', 4), ('MAGENTA', 5), ('YELLOW', 6)]
    

    eller en mappning:

    {'CHARTREUSE': 7, 'SEA_GREEN': 11, 'ROSEMARY': 42}
    
  • module: namn på modul där ny enumklass kan hittas.

  • qualname: var i modulen den nya enumklassen kan hittas.

  • type: typ att blanda in i ny enumklass.

  • start: nummer att börja räkna med om endast namn skickas in.

Ändrad i version 3.5: Parametern start har lagts till.

Härledda uppräkningar

IntEnum

Den första varianten av Enum som tillhandahålls är också en subklass av int. Medlemmar i en IntEnum kan jämföras med heltal; efter utökning kan heltalsuppräkningar av olika typer också jämföras med varandra:

>>> from enum import IntEnum
>>> class Shape(IntEnum):
...     CIRCLE = 1
...     SQUARE = 2
...
>>> class Request(IntEnum):
...     POST = 1
...     GET = 2
...
>>> Shape == 1
False
>>> Shape.CIRCLE == 1
True
>>> Shape.CIRCLE == Request.POST
True

De kan dock fortfarande inte jämföras med vanliga Enum-uppräkningar:

>>> class Shape(IntEnum):
...     CIRCLE = 1
...     SQUARE = 2
...
>>> class Color(Enum):
...     RED = 1
...     GREEN = 2
...
>>> Shape.CIRCLE == Color.RED
False

IntEnum-värden beter sig som heltal på andra sätt som du kan förvänta dig:

>>> int(Shape.CIRCLE)
1
>>> ['a', 'b', 'c'][Shape.CIRCLE]
'b'
>>> [i for i in range(Shape.SQUARE)]
[0, 1]

StrEnum

Den andra varianten av Enum som tillhandahålls är också en subklass av str. Medlemmar i en StrEnum kan jämföras med strängar; i förlängningen kan stränguppräkningar av olika typer också jämföras med varandra.

Tillagd i version 3.11.

IntFlag

Nästa variant av Enum som tillhandahålls, IntFlag, är också baserad på int. Skillnaden är att IntFlag-medlemmar kan kombineras med hjälp av bitvisa operatorer (&, |, ^, ~) och resultatet är fortfarande en IntFlag-medlem, om möjligt. Liksom IntEnum är IntFlag-medlemmar också heltal och kan användas var som helst där en int används.

Anteckning

Alla operationer på en IntFlag-medlem förutom de bitvisa operationerna kommer att förlora IntFlag-medlemskapet.

Bitvisa operationer som resulterar i ogiltiga IntFlag-värden kommer att förlora IntFlag-medlemskapet. Se FlagBoundary för detaljer.

Tillagd i version 3.6.

Ändrad i version 3.11.

Exempel IntFlag klass:

>>> from enum import IntFlag
>>> class Perm(IntFlag):
...     R = 4
...     W = 2
...     X = 1
...
>>> Perm.R | Perm.W
<Perm.R|W: 6>
>>> Perm.R + Perm.W
6
>>> RW = Perm.R | Perm.W
>>> Perm.R in RW
True

Det är också möjligt att namnge kombinationerna:

>>> class Perm(IntFlag):
...     R = 4
...     W = 2
...     X = 1
...     RWX = 7
...
>>> Perm.RWX
<Perm.RWX: 7>
>>> ~Perm.RWX
<Perm: 0>
>>> Perm(7)
<Perm.RWX: 7>

Anteckning

Namngivna kombinationer betraktas som alias. Alias visas inte under iteration, men kan returneras från by-value-uppslagningar.

Ändrad i version 3.11.

En annan viktig skillnad mellan IntFlag och Enum är att om inga flaggor är inställda (värdet är 0), är dess booleska utvärdering False:

>>> Perm.R & Perm.X
<Perm: 0>
>>> bool(Perm.R & Perm.X)
False

Eftersom IntFlag-medlemmar också är underklasser till int kan de kombineras med dem (men kan förlora IntFlag-medlemskap:

>>> Perm.X | 4
<Perm.R|X: 5>

>>> Perm.X + 8
9

Anteckning

Negationsoperatorn, ~, returnerar alltid en IntFlag-medlem med ett positivt värde:

>>> (~Perm.X).value == (Perm.R|Perm.W).value == 6
True

IntFlag-medlemmar kan också itereras över:

>>> list(RW)
[<Perm.R: 4>, <Perm.W: 2>]

Tillagd i version 3.11.

Flagga

Den sista variationen är Flag. Liksom IntFlag kan Flag-medlemmar kombineras med hjälp av bitvisa operatorer (&, |, ^, ~). Till skillnad från IntFlag kan de inte kombineras med, eller jämföras mot, någon annan Flag-uppräkning, eller int. Det är möjligt att ange värdena direkt, men det rekommenderas att använda auto som värde och låta Flag välja ett lämpligt värde.

Tillagd i version 3.6.

Liksom IntFlag, om en kombination av Flag-medlemmar resulterar i att inga flaggor sätts, är den booleska utvärderingen False:

>>> from enum import Flag, auto
>>> class Color(Flag):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.RED & Color.GREEN
<Color: 0>
>>> bool(Color.RED & Color.GREEN)
False

Enskilda flaggor bör ha värden som är potenser av två (1, 2, 4, 8, …), medan kombinationer av flaggor inte kommer att ha det:

>>> class Color(Flag):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...     WHITE = RED | BLUE | GREEN
...
>>> Color.WHITE
<Color.WHITE: 7>

Att ge ett namn till villkoret ”inga flaggor satta” ändrar inte dess booleska värde:

>>> class Color(Flag):
...     BLACK = 0
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.BLACK
<Color.BLACK: 0>
>>> bool(Color.BLACK)
False

Flag-medlemmar kan också itereras över:

>>> purple = Color.RED | Color.BLUE
>>> list(purple)
[<Color.RED: 1>, <Color.BLUE: 2>]

Tillagd i version 3.11.

Anteckning

För majoriteten av ny kod rekommenderas Enum och Flag starkt, eftersom IntEnum och IntFlag bryter några semantiska löften om en uppräkning (genom att vara jämförbara med heltal, och därmed genom transitivitet till andra orelaterade uppräkningar). IntEnum och IntFlag bör endast användas i de fall där Enum och Flag inte räcker till; till exempel när heltalskonstanter ersätts med uppräkningar, eller för interoperabilitet med andra system.

Andra

Medan IntEnum är en del av enum-modulen, skulle det vara mycket enkelt att implementera oberoende:

class IntEnum(int, ReprEnum):   # or Enum instead of ReprEnum
    pass

Detta visar hur liknande härledda uppräkningar kan definieras; till exempel en FloatEnum som blandar in float istället för int.

Vissa regler:

  1. Vid subklassning av Enum måste mix-in-typer visas före själva Enum-klassen i bassekvensen, som i exemplet med IntEnum ovan.

  2. Mix-in-typer måste vara underklassbara. Till exempel är bool och range inte underklassbara och kommer att ge ett felmeddelande under skapandet av Enum om de används som mix-in-typ.

  3. Medan Enum kan ha medlemmar av vilken typ som helst, måste alla medlemmar ha värden av den typen när du blandar in en ytterligare typ, t.ex. int ovan. Denna begränsning gäller inte för mix-ins som bara lägger till metoder och inte anger någon annan typ.

  4. När en annan datatyp blandas in är attributet value inte detsamma som enum-medlemmen i sig, även om det är likvärdigt och kommer att jämföras lika.

  5. En ”datatyp” är en mixin som definierar __new__(), eller en dataclass

  6. %-style formatering: %s och %r anropar Enum-klassens __str__() respektive __repr__(); andra koder (som %i eller %h för IntEnum) behandlar enum-medlemmen som dess mixade typ.

  7. Formaterade stränglitteraler, str.format(), och format() kommer att använda enumets __str__() metod.

Anteckning

Eftersom IntEnum, IntFlag och StrEnum är utformade för att ersätta befintliga konstanter har deras __str__()-metod återställts till deras datatypers __str__()-metod.

När ska man använda __new__() vs. __init__()

__new__() måste användas när du vill anpassa det faktiska värdet för medlemmen Enum. Alla andra ändringar kan göras i antingen __new__() eller __init__(), där __init__() är att föredra.

Om du t.ex. vill skicka flera objekt till konstruktören, men bara vill att ett av dem ska vara värdet:

>>> class Coordinate(bytes, Enum):
...     """
...     Coordinate with binary codes that can be indexed by the int code.
...     """
...     def __new__(cls, value, label, unit):
...         obj = bytes.__new__(cls, [value])
...         obj._value_ = value
...         obj.label = label
...         obj.unit = unit
...         return obj
...     PX = (0, 'P.X', 'km')
...     PY = (1, 'P.Y', 'km')
...     VX = (2, 'V.X', 'km/s')
...     VY = (3, 'V.Y', 'km/s')
...

>>> print(Coordinate['PY'])
Coordinate.PY

>>> print(Coordinate(3))
Coordinate.VY

Varning

Anropa inte super().__new__(), eftersom det är __new__ som bara hittas vid uppslagning; använd i stället datatypen direkt.

Finare punkter

Namn som stöds för __dunder__

__members__ är en skrivskyddad ordnad mappning av member_name:member objekt. Den är endast tillgänglig för klassen.

__new__(), om det anges, måste skapa och returnera enum-medlemmarna; det är också en mycket bra idé att ställa in medlemmens _value_ på lämpligt sätt. När alla medlemmar har skapats används den inte längre.

Namn med stöd för _sunder_

  • _name_ – namnet på medlemmen

  • _value_ – medlemmens värde; kan ställas in i __new__

  • _missing_() – en uppslagningsfunktion som används när ett värde inte hittas; kan åsidosättas

  • _ignore_ – en lista med namn, antingen som en list eller en str, som inte kommer att omvandlas till medlemmar och som kommer att tas bort från den slutliga klassen

  • _generate_next_value_() – används för att få ett lämpligt värde för en enum-medlem; kan åsidosättas

  • _add_alias_() – lägger till ett nytt namn som alias till en befintlig medlem.

  • _add_value_alias_() – lägger till ett nytt värde som ett alias till en befintlig medlem. Se MultiValueEnum för ett exempel.

    Anteckning

    För standardklasserna Enum är nästa värde som väljs det högsta värde som setts, ökat med ett.

    För klasserna Flag kommer nästa värde som väljs att vara den näst högsta tvåpotensen.

    Ändrad i version 3.13: I tidigare versioner användes det senast sedda värdet i stället för det högsta värdet.

Tillagd i version 3.6: _missing_, _order_, _generate_next_value_

Tillagd i version 3.7: _ignore_

Tillagd i version 3.13: _add_alias_, _add_value_alias_

För att hjälpa till att hålla Python 2 / Python 3-koden synkroniserad kan ett _order_-attribut tillhandahållas. Det kommer att kontrolleras mot den faktiska ordningen i uppräkningen och ge upphov till ett fel om de två inte matchar varandra:

>>> class Color(Enum):
...     _order_ = 'RED GREEN BLUE'
...     RED = 1
...     BLUE = 3
...     GREEN = 2
...
Traceback (most recent call last):
...
TypeError: member order does not match _order_:
  ['RED', 'BLUE', 'GREEN']
  ['RED', 'GREEN', 'BLUE']

Anteckning

I Python 2-koden är attributet _order_ nödvändigt eftersom definitionsordningen går förlorad innan den kan registreras.

_Private__names

Privata namn konverteras inte till enum-medlemmar, utan förblir normala attribut.

Ändrad i version 3.11.

Enum medlemstyp

Enum-medlemmar är instanser av sin enum-klass och nås normalt som EnumClass.member. I vissa situationer, t.ex. när man skriver ett anpassat enum-beteende, är det användbart att kunna komma åt en medlem direkt från en annan, och detta stöds; för att undvika namnkrockar mellan medlemsnamn och attribut/metoder från blandade klasser rekommenderas dock starkt versaler.

Ändrad i version 3.5.

Skapa medlemmar som blandas med andra datatyper

När man subklassar andra datatyper, t.ex. int eller str, med Enum, skickas alla värden efter = till datatypens konstruktör. Till exempel:

>>> class MyEnum(IntEnum):      # help(int) -> int(x, base=10) -> integer
...     example = '11', 16      # so x='11' and base=16
...
>>> MyEnum.example.value        # and hex(11) is...
17

Booleskt värde för klasser och medlemmar i Enum

Enumklasser som blandas med non-Enum -typer (t.ex. int, str, etc.) utvärderas enligt den blandade typens regler; annars utvärderas alla medlemmar som True. För att göra din egen enums booleska utvärdering beroende av medlemmens värde, lägg till följande i din klass:

def __bool__(self):
    return bool(self.value)

Vanliga Enum-klasser utvärderas alltid som True.

Enum klasser med metoder

Om du ger din enum-underklass extra metoder, som klassen Planet nedan, kommer dessa metoder att dyka upp i en dir() för medlemmen, men inte för klassen:

>>> dir(Planet)
['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__']
>>> dir(Planet.EARTH)
['__class__', '__doc__', '__module__', 'mass', 'name', 'radius', 'surface_gravity', 'value']

Kombinerar medlemmar av Flag

Iterering över en kombination av Flag-medlemmar kommer endast att returnera de medlemmar som består av en enda bit:

>>> class Color(Flag):
...     RED = auto()
...     GREEN = auto()
...     BLUE = auto()
...     MAGENTA = RED | BLUE
...     YELLOW = RED | GREEN
...     CYAN = GREEN | BLUE
...
>>> Color(3)  # named combination
<Color.YELLOW: 3>
>>> Color(7)      # not named combination
<Color.RED|GREEN|BLUE: 7>

minutia för ”Flagg” och ”IntFlagg

Använd följande snutt för våra exempel:

>>> class Color(IntFlag):
...     BLACK = 0
...     RED = 1
...     GREEN = 2
...     BLUE = 4
...     PURPLE = RED | BLUE
...     WHITE = RED | GREEN | BLUE
...

följande är sant:

  • enbitsflaggor är kanoniska

  • multibit- och nollbitflaggor är alias

  • endast kanoniska flaggor returneras under iteration:

    >>> list(Color.WHITE)
    [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]
    
  • negering av en flagga eller flagguppsättning ger en ny flagga/flagguppsättning med motsvarande positiva heltalsvärde:

    >>> Color.BLUE
    <Color.BLUE: 4>
    
    >>> ~Color.BLUE
    <Color.RED|GREEN: 3>
    
  • namn på pseudoflaggor konstrueras från deras medlemmars namn:

    >>> (Color.RED | Color.GREEN).name
    'RED|GREEN'
    
    >>> class Perm(IntFlag):
    ...     R = 4
    ...     W = 2
    ...     X = 1
    ...
    >>> (Perm.R & Perm.W).name is None  # effectively Perm(0)
    True
    
  • multi-bit flaggor, aka alias, kan returneras från operationer:

    >>> Color.RED | Color.BLUE
    <Color.PURPLE: 5>
    
    >>> Color(7)  # or Color(-1)
    <Color.WHITE: 7>
    
    >>> Color(0)
    <Color.BLACK: 0>
    
  • kontroll av medlemskap / inneslutning: nollvärdesflaggor anses alltid vara inneslutna:

    >>> Color.BLACK in Color.WHITE
    True
    

    annars, endast om alla bitar i en flagga är i den andra flaggan kommer True att returneras:

    >>> Color.PURPLE in Color.WHITE
    True
    
    >>> Color.GREEN in Color.PURPLE
    False
    

Det finns en ny boundary-mekanism som styr hur out-of-range / ogiltiga bitar hanteras: STRICT, CONFORM, EJECT och KEEP:

  • STRICT –> ger upphov till ett undantag när ogiltiga värden presenteras

  • CONFORM –> förkastar alla ogiltiga bitar

  • EJECT –> förlorar Flag-status och blir en normal int med det angivna värdet

  • KEEP –> behåll de extra bitarna

    • håller Flaggstatus och extra bitar

    • extra bitar dyker inte upp i iterationen

    • extra bitar dyker upp i repr() och str()

Standardvärdet för Flag är STRICT, standardvärdet för IntFlag är EJECT och standardvärdet för _convert_ är KEEP (se ssl.Options för ett exempel på när KEEP behövs).

Hur skiljer sig Enums och Flags åt?

Enum har en egen metaklass som påverkar många aspekter av både härledda Enum-klasser och deras instanser (medlemmar).

Enumklasser

Metaklassen EnumType ansvarar för att tillhandahålla metoderna __contains__(), __dir__(), __iter__() och andra metoder som gör att man kan göra saker med en Enum-klass som misslyckas med en vanlig klass, till exempel list(Color) eller some_enum_var in Color. EnumType är ansvarig för att säkerställa att olika andra metoder på den slutliga Enum klassen är korrekta (såsom __new__(), __getnewargs__(), __str__() och __repr__()).

Flaggklasser

Flaggor har en utvidgad syn på alias: för att vara kanonisk måste värdet på en flagga vara ett värde i tvåpotens och inte ett duplicerat namn. Så, utöver Enum definitionen av alias, anses en flagga utan värde (a.k.a. 0) eller med mer än ett tvåpotensvärde (t.ex. 3) vara ett alias.

Enum-medlemmar (även kallade instanser)

Det mest intressanta med enum-medlemmar är att de är singletons. EnumType skapar dem alla när den skapar enum-klassen själv, och sätter sedan en anpassad __new__() på plats för att säkerställa att inga nya någonsin instansieras genom att endast returnera de befintliga medlemsinstanserna.

Flaggmedlemmar

Flaggmedlemmar kan itereras över precis som i klassen Flag, och endast de kanoniska medlemmarna returneras. Till exempel:

>>> list(Color)
[<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]

(Observera att BLACK, PURPLE och WHITE inte förekommer)

Om du inverterar en flaggmedlem returneras motsvarande positiva värde, snarare än ett negativt värde — till exempel:

>>> ~Color.RED
<Color.GREEN|BLUE: 6>

Flaggmedlemmar har en längd som motsvarar antalet tvåpotensvärden de innehåller. Till exempel:

>>> len(Color.PURPLE)
2

Enum kokbok

Även om Enum, IntEnum, StrEnum, Flag och IntFlag förväntas täcka de flesta användningsfall, kan de inte täcka alla. Här finns recept på några olika typer av uppräkningar som kan användas direkt eller som exempel för att skapa egna.

Utelämnande av värden

I många användningsfall bryr man sig inte om vad det faktiska värdet på en uppräkning är. Det finns flera sätt att definiera den här typen av enkla uppräkningar:

  • använda instanser av auto för värdet

  • använda instanser av object som värde

  • använda en beskrivande sträng som värde

  • använda en tupel som värde och en anpassad __new__() för att ersätta tupeln med ett int-värde

Genom att använda någon av dessa metoder signalerar man till användaren att dessa värden inte är viktiga och gör det också möjligt att lägga till, ta bort eller ändra ordning på medlemmar utan att behöva numrera om de återstående medlemmarna.

Använda auto

Att använda auto skulle se ut så här:

>>> class Color(Enum):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.GREEN
<Color.GREEN: 3>

Använda object

Att använda object skulle se ut så här:

>>> class Color(Enum):
...     RED = object()
...     GREEN = object()
...     BLUE = object()
...
>>> Color.GREEN
<Color.GREEN: <object object at 0x...>>

Detta är också ett bra exempel på varför du kanske vill skriva din egen __repr__():

>>> class Color(Enum):
...     RED = object()
...     GREEN = object()
...     BLUE = object()
...     def __repr__(self):
...         return "<%s.%s>" % (self.__class__.__name__, self._name_)
...
>>> Color.GREEN
<Color.GREEN>

Använda en beskrivande sträng

Om du använder en sträng som värde skulle det se ut så här:

>>> class Color(Enum):
...     RED = 'stop'
...     GREEN = 'go'
...     BLUE = 'too fast!'
...
>>> Color.GREEN
<Color.GREEN: 'go'>

Använda en anpassad __new__`()

Om man använder en automatisk numrering __new__() skulle det se ut så här:

>>> class AutoNumber(Enum):
...     def __new__(cls):
...         value = len(cls.__members__) + 1
...         obj = object.__new__(cls)
...         obj._value_ = value
...         return obj
...
>>> class Color(AutoNumber):
...     RED = ()
...     GREEN = ()
...     BLUE = ()
...
>>> Color.GREEN
<Color.GREEN: 2>

För att skapa ett mer allmänt AutoNumber, lägg till *args till signaturen:

>>> class AutoNumber(Enum):
...     def __new__(cls, *args):      # this is the only change from above
...         value = len(cls.__members__) + 1
...         obj = object.__new__(cls)
...         obj._value_ = value
...         return obj
...

När du sedan ärver från AutoNumber kan du skriva din egen __init__ för att hantera eventuella extra argument:

>>> class Swatch(AutoNumber):
...     def __init__(self, pantone='unknown'):
...         self.pantone = pantone
...     AUBURN = '3497'
...     SEA_GREEN = '1246'
...     BLEACHED_CORAL = () # New color, no Pantone code yet!
...
>>> Swatch.SEA_GREEN
<Swatch.SEA_GREEN: 2>
>>> Swatch.SEA_GREEN.pantone
'1246'
>>> Swatch.BLEACHED_CORAL.pantone
'unknown'

Anteckning

Metoden __new__(), om den är definierad, används vid skapandet av Enum-medlemmarna; den ersätts sedan av Enums __new__() som används efter klassens skapande för uppslagning av befintliga medlemmar.

Varning

Anropa inte super().__new__(), eftersom det är __new__ som bara hittas vid uppslagning; använd i stället datatypen direkt – t.ex.:

obj = int.__new__(cls, value)

BeställdEnum

En ordnad uppräkning som inte är baserad på IntEnum och som därför bibehåller de normala Enum-invarianterna (t.ex. att den inte är jämförbar med andra uppräkningar):

>>> class OrderedEnum(Enum):
...     def __ge__(self, other):
...         if self.__class__ is other.__class__:
...             return self.value >= other.value
...         return NotImplemented
...     def __gt__(self, other):
...         if self.__class__ is other.__class__:
...             return self.value > other.value
...         return NotImplemented
...     def __le__(self, other):
...         if self.__class__ is other.__class__:
...             return self.value <= other.value
...         return NotImplemented
...     def __lt__(self, other):
...         if self.__class__ is other.__class__:
...             return self.value < other.value
...         return NotImplemented
...
>>> class Grade(OrderedEnum):
...     A = 5
...     B = 4
...     C = 3
...     D = 2
...     F = 1
...
>>> Grade.C < Grade.A
True

DuplikatFriEnum

Utlöser ett fel om ett duplicerat medlemsvärde hittas istället för att skapa ett alias:

>>> class DuplicateFreeEnum(Enum):
...     def __init__(self, *args):
...         cls = self.__class__
...         if any(self.value == e.value for e in cls):
...             a = self.name
...             e = cls(self.value).name
...             raise ValueError(
...                 "aliases not allowed in DuplicateFreeEnum:  %r --> %r"
...                 % (a, e))
...
>>> class Color(DuplicateFreeEnum):
...     RED = 1
...     GREEN = 2
...     BLUE = 3
...     GRENE = 2
...
Traceback (most recent call last):
  ...
ValueError: aliases not allowed in DuplicateFreeEnum:  'GRENE' --> 'GREEN'

Anteckning

Detta är ett användbart exempel på subklassning av Enum för att lägga till eller ändra andra beteenden samt för att inte tillåta alias. Om den enda önskade ändringen är att inte tillåta alias kan dekoratorn unique() användas istället.

MultiValueEnum

Stöder att ha mer än ett värde per medlem:

>>> class MultiValueEnum(Enum):
...     def __new__(cls, value, *values):
...         self = object.__new__(cls)
...         self._value_ = value
...         for v in values:
...             self._add_value_alias_(v)
...         return self
...
>>> class DType(MultiValueEnum):
...     float32 = 'f', 8
...     double64 = 'd', 9
...
>>> DType('f')
<DType.float32: 'f'>
>>> DType(9)
<DType.double64: 'd'>

Planet

Om __new__() eller __init__() är definierade, kommer enum-medlemmens värde att skickas till dessa metoder:

>>> class Planet(Enum):
...     MERCURY = (3.303e+23, 2.4397e6)
...     VENUS   = (4.869e+24, 6.0518e6)
...     EARTH   = (5.976e+24, 6.37814e6)
...     MARS    = (6.421e+23, 3.3972e6)
...     JUPITER = (1.9e+27,   7.1492e7)
...     SATURN  = (5.688e+26, 6.0268e7)
...     URANUS  = (8.686e+25, 2.5559e7)
...     NEPTUNE = (1.024e+26, 2.4746e7)
...     def __init__(self, mass, radius):
...         self.mass = mass       # in kilograms
...         self.radius = radius   # in meters
...     @property
...     def surface_gravity(self):
...         # universal gravitational constant  (m3 kg-1 s-2)
...         G = 6.67300E-11
...         return G * self.mass / (self.radius * self.radius)
...
>>> Planet.EARTH.value
(5.976e+24, 6378140.0)
>>> Planet.EARTH.surface_gravity
9.802652743337129

TidPeriod

Ett exempel som visar hur attributet _ignore_ används:

>>> from datetime import timedelta
>>> class Period(timedelta, Enum):
...     "different lengths of time"
...     _ignore_ = 'Period i'
...     Period = vars()
...     for i in range(367):
...         Period['day_%d' % i] = i
...
>>> list(Period)[:2]
[<Period.day_0: datetime.timedelta(0)>, <Period.day_1: datetime.timedelta(days=1)>]
>>> list(Period)[-2:]
[<Period.day_365: datetime.timedelta(days=365)>, <Period.day_366: datetime.timedelta(days=366)>]

Underklassificering av EnumType

Medan de flesta enum-behov kan tillgodoses genom att anpassa Enum-subklasser, antingen med klassdekoratorer eller anpassade funktioner, kan EnumType subklassas för att ge en annan Enum-upplevelse.