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:
Vid subklassning av
Enum
måste mix-in-typer visas före självaEnum
-klassen i bassekvensen, som i exemplet medIntEnum
ovan.Mix-in-typer måste vara underklassbara. Till exempel är
bool
ochrange
inte underklassbara och kommer att ge ett felmeddelande under skapandet av Enum om de används som mix-in-typ.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.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.En ”datatyp” är en mixin som definierar
__new__()
, eller endataclass
%-style formatering:
%s
och%r
anroparEnum
-klassens__str__()
respektive__repr__()
; andra koder (som%i
eller%h
för IntEnum) behandlar enum-medlemmen som dess mixade typ.Formaterade stränglitteraler,
str.format()
, ochformat()
kommer att använda enumets__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 enlist
eller enstr
, 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)
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ärdetanvända instanser av
object
som värdeanvä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 ettint
-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.