|
| 1 | +Enumerations |
| 2 | +============ |
| 3 | + |
| 4 | +The ``enum.Enum`` class behaves differently from other Python classes in several |
| 5 | +ways that require special-case handling in type checkers. This section discusses |
| 6 | +the Enum behaviors that should be supported by type checkers and others which |
| 7 | +may be supported optionally. It is recommended that library and type stub |
| 8 | +authors avoid using optional behaviors because these may not be supported |
| 9 | +by some type checkers. |
| 10 | + |
| 11 | + |
| 12 | +Enum Definition |
| 13 | +--------------- |
| 14 | + |
| 15 | +Enum classes can be defined using a "class syntax" or a "function syntax". |
| 16 | +The function syntax offers several ways to specify enum members: names passed |
| 17 | +as individual arguments, a list or tuple of names, a string of |
| 18 | +comma-delimited or space-delimited names, a list or tuple of tuples that contain |
| 19 | +name/value pairs, and a dictionary of name/value items. |
| 20 | + |
| 21 | +Type checkers should support the class syntax, but the function syntax (in |
| 22 | +its various forms) is optional:: |
| 23 | + |
| 24 | + class Color1(Enum): # Supported |
| 25 | + RED = 1 |
| 26 | + GREEN = 2 |
| 27 | + BLUE = 3 |
| 28 | + |
| 29 | + Color2 = Enum('Color2', 'RED', 'GREEN', 'BLUE') # Optional |
| 30 | + Color3 = Enum('Color3', ['RED', 'GREEN', 'BLUE']) # Optional |
| 31 | + Color4 = Enum('Color4', ('RED', 'GREEN', 'BLUE')) # Optional |
| 32 | + Color5 = Enum('Color5', 'RED, GREEN, BLUE') # Optional |
| 33 | + Color6 = Enum('Color6', 'RED GREEN BLUE') # Optional |
| 34 | + Color7 = Enum('Color7', [('RED': 1), ('GREEN': 2), ('BLUE': 3)]) # Optional |
| 35 | + Color8 = Enum('Color8', (('RED': 1), ('GREEN': 2), ('BLUE': 3))) # Optional |
| 36 | + Color9 = Enum('Color9', {'RED': 1, 'GREEN': 2, 'BLUE': 3}) # Optional |
| 37 | + |
| 38 | +Enum classes can also be defined using a subclass of ``enum.Enum`` or any class |
| 39 | +that uses ``enum.EnumType`` (or a subclass thereof) as a metaclass. Note that |
| 40 | +``enum.EnumType`` was named ``enum.EnumMeta`` prior to Python 3.11. Type |
| 41 | +checkers should treat such classes as enums:: |
| 42 | + |
| 43 | + class CustomEnum1(Enum): |
| 44 | + pass |
| 45 | + |
| 46 | + class Color7(CustomEnum1): # Supported |
| 47 | + RED = 1 |
| 48 | + GREEN = 2 |
| 49 | + BLUE = 3 |
| 50 | + |
| 51 | + class CustomEnumType(EnumType): |
| 52 | + pass |
| 53 | + |
| 54 | + class CustomEnum2(metaclass=CustomEnumType): |
| 55 | + pass |
| 56 | + |
| 57 | + class Color8(CustomEnum2): # Supported |
| 58 | + RED = 1 |
| 59 | + GREEN = 2 |
| 60 | + BLUE = 3 |
| 61 | + |
| 62 | + |
| 63 | +Enum Behaviors |
| 64 | +-------------- |
| 65 | + |
| 66 | +Enum classes are iterable and indexable, and they can be called with a value |
| 67 | +to look up the enum member with that value. Type checkers should support these |
| 68 | +behaviors:: |
| 69 | + |
| 70 | + class Color(Enum): |
| 71 | + RED = 1 |
| 72 | + GREEN = 2 |
| 73 | + BLUE = 3 |
| 74 | + |
| 75 | + for color in Color: |
| 76 | + reveal_type(color) # Revealed type is 'Color' |
| 77 | + |
| 78 | + reveal_type(Color["RED"]) # Revealed type is 'Literal[Color.RED]' (or 'Color') |
| 79 | + reveal_type(Color(3)) # Revealed type is 'Literal[Color.BLUE]' (or 'Color') |
| 80 | + |
| 81 | +Unlike most Python classes, Calling an enum class does not invoke its constructor. |
| 82 | +Instead, the call performs a value-based lookup of an enum member. |
| 83 | + |
| 84 | +An Enum class with one or more defined members cannot be subclassed. They are |
| 85 | +implicitly "final". Type checkers should enforce this:: |
| 86 | + |
| 87 | + class EnumWithNoMembers(Enum): |
| 88 | + pass |
| 89 | + |
| 90 | + class Shape(EnumWithNoMembers): # OK (because no members are defined) |
| 91 | + SQUARE = 1 |
| 92 | + CIRCLE = 2 |
| 93 | + |
| 94 | + class ExtendedShape(Shape): # Type checker error: Shape is implicitly final |
| 95 | + TRIANGLE = 3 |
| 96 | + |
| 97 | + |
| 98 | +Defining Members |
| 99 | +---------------- |
| 100 | + |
| 101 | +When using the "class syntax", enum classes can define both members and |
| 102 | +other (non-member) attributes. The ``EnumType`` metaclass applies a set |
| 103 | +of rules to distinguish between members and non-members. Type checkers |
| 104 | +should honor the most common of these rules. The lesser-used rules are |
| 105 | +optional. Some of these rules may be impossible to evaluate and enforce |
| 106 | +statically in cases where dynamic values are used. |
| 107 | + |
| 108 | +* If an attribute is defined in the class body with a type annotation but |
| 109 | + with no assigned value, a type checker should assume this is a non-member |
| 110 | + attribute:: |
| 111 | + |
| 112 | + class Pet(Enum): |
| 113 | + genus: str # Non-member attribute |
| 114 | + species: str # Non-member attribute |
| 115 | + |
| 116 | + CAT = 1 # Member attribute |
| 117 | + DOG = 2 # Member attribute |
| 118 | + |
| 119 | + Within a type stub, members can be defined using the actual runtime values, |
| 120 | + or a placeholder of ``...`` can be used:: |
| 121 | + |
| 122 | + class Pet(Enum): |
| 123 | + genus: str # Non-member attribute |
| 124 | + species: str # Non-member attribute |
| 125 | + |
| 126 | + CAT = ... # Member attribute |
| 127 | + DOG = ... # Member attribute |
| 128 | + |
| 129 | +* Members defined within an enum class should not include explicit type |
| 130 | + annotations. Type checkers should infer a literal type for all members. |
| 131 | + A type checker should report an error if a type annotation is used |
| 132 | + for an enum member because this type will be incorrect and misleading |
| 133 | + to readers of the code:: |
| 134 | + |
| 135 | + class Pet(Enum): |
| 136 | + CAT = 1 # OK |
| 137 | + DOG: int = 2 # Type checker error |
| 138 | + |
| 139 | +* Methods, callables, descriptors (including properties), and nested classes |
| 140 | + that are defined in the class are not treated as enum members by the |
| 141 | + ``EnumType`` metaclass and should likewise not be treated as enum members by |
| 142 | + a type checker:: |
| 143 | + |
| 144 | + def identity(x): return x |
| 145 | + |
| 146 | + class Pet(Enum): |
| 147 | + CAT = 1 # Member attribute |
| 148 | + DOG = 2 # Member attribute |
| 149 | + |
| 150 | + converter = lambda x: str(x) # Non-member attribute |
| 151 | + transform = identity # Non-member attribute |
| 152 | + |
| 153 | + @property |
| 154 | + def species(self) -> str: # Non-member property |
| 155 | + return "mammal" |
| 156 | + |
| 157 | + def speak(self) -> None: # Non-member method |
| 158 | + print("meow" if self is Pet.CAT else "woof") |
| 159 | + |
| 160 | + class Nested: ... # Non-member nested class |
| 161 | + |
| 162 | +* An attribute that is assigned the value of another member of the same enum |
| 163 | + is not a member itself. Instead, it is an alias for the first member:: |
| 164 | + |
| 165 | + class TrafficLight(Enum): |
| 166 | + RED = 1 |
| 167 | + GREEN = 2 |
| 168 | + YELLOW = 3 |
| 169 | + |
| 170 | + AMBER = YELLOW # Alias for YELLOW |
| 171 | + |
| 172 | + reveal_type(TrafficLight.AMBER) # Revealed type is Literal[TrafficLight.YELLOW] |
| 173 | + |
| 174 | +* If using Python 3.11 or newer, the ``enum.member`` and ``enum.nonmember`` |
| 175 | + classes can be used to unambiguously distinguish members from non-members. |
| 176 | + Type checkers should support these classes:: |
| 177 | + |
| 178 | + class Example(Enum): |
| 179 | + a = member(1) # Member attribute |
| 180 | + b = nonmember(2) # Non-member attribute |
| 181 | + |
| 182 | + @member |
| 183 | + def c(self) -> None: # Member method |
| 184 | + pass |
| 185 | + |
| 186 | + reveal_type(Example.a) # Revealed type is Literal[Example.a] |
| 187 | + reveal_type(Example.b) # Revealed type is int or Literal[2] |
| 188 | + reveal_type(Example.c) # Revealed type is Literal[Example.c] |
| 189 | + |
| 190 | +* An attribute with a private name (beginning with, but not ending in, a double |
| 191 | + underscore) is treated as a non-member. |
| 192 | + |
| 193 | + class Example(Enum): |
| 194 | + A = 1 # Member attribute |
| 195 | + __B = 2 # Non-member attribute |
| 196 | + |
| 197 | + reveal_type(Example.A) # Revealed type is Literal[Example.A] |
| 198 | + reveal_type(Example.__B) # Type Error: Private name is mangled |
| 199 | + |
| 200 | +* An enum class can define a class symbol named ``_ignore_``. This can be a list |
| 201 | + of names or a string containing a space-delimited list of names that are |
| 202 | + deleted from the enum class at runtime. Type checkers may support this |
| 203 | + mechanism:: |
| 204 | + |
| 205 | + class Pet(Enum): |
| 206 | + _ignore_ = "DOG FISH" |
| 207 | + CAT = 1 # Member attribute |
| 208 | + DOG = 2 # temporary variable, will be removed from the final enum class |
| 209 | + FISH = 3 # temporary variable, will be removed from the final enum class |
| 210 | + |
| 211 | + |
| 212 | +Member Names |
| 213 | +------------ |
| 214 | + |
| 215 | +All enum member objects have an attribute ``_name_`` that contains the member's |
| 216 | +name. They also have a property ``name`` that returns the same name. Type |
| 217 | +checkers may infer a literal type for the name of a member:: |
| 218 | + |
| 219 | + class Color(Enum): |
| 220 | + RED = 1 |
| 221 | + GREEN = 2 |
| 222 | + BLUE = 3 |
| 223 | + |
| 224 | + reveal_type(Color.RED._name_) # Revealed type is Literal["RED"] (or str) |
| 225 | + reveal_type(Color.RED.name) # Revealed type is Literal["RED"] (or str) |
| 226 | + |
| 227 | + def func1(red_or_blue: Literal[Color.RED, Color.BLUE]): |
| 228 | + reveal_type(red_or_blue.name) # Revealed type is Literal["RED", "BLUE"] (or str) |
| 229 | + |
| 230 | + def func2(any_color: Color): |
| 231 | + reveal_type(any_color.name) # Revealed type is Literal["RED", "BLUE", "GREEN"] (or str) |
| 232 | + |
| 233 | + |
| 234 | +Member Values |
| 235 | +------------- |
| 236 | + |
| 237 | +All enum member objects have an attribute ``_value_`` that contains the member's |
| 238 | +value. They also have a property ``value`` that returns the same value. Type |
| 239 | +checkers may infer the type of a member's value:: |
| 240 | + |
| 241 | + class Color(Enum): |
| 242 | + RED = 1 |
| 243 | + GREEN = 2 |
| 244 | + BLUE = 3 |
| 245 | + |
| 246 | + reveal_type(Color.RED._value_) # Revealed type is Literal[1] (or int or object or Any) |
| 247 | + reveal_type(Color.RED.value) # Revealed type is Literal[1] (or int or object or Any) |
| 248 | + |
| 249 | + def func1(red_or_blue: Literal[Color.RED, Color.BLUE]): |
| 250 | + reveal_type(red_or_blue.value) # Revealed type is Literal[1, 2] (or int or object or Any) |
| 251 | + |
| 252 | + def func2(any_color: Color): |
| 253 | + reveal_type(any_color.value) # Revealed type is Literal[1, 2, 3] (or int or object or Any) |
| 254 | + |
| 255 | + |
| 256 | +The value of ``_value_`` can be assigned in a constructor method. This technique |
| 257 | +is sometimes used to initialize both the member value and non-member attributes. |
| 258 | +If the value assigned in the class body is a tuple, the unpacked tuple value is |
| 259 | +passed to the constructor. Type checkers may validate consistency between assigned |
| 260 | +tuple values and the constructor signature:: |
| 261 | + |
| 262 | + class Planet(Enum): |
| 263 | + def __init__(self, value: int, mass: float, radius: float): |
| 264 | + self._value_ = value |
| 265 | + self.mass = mass |
| 266 | + self.radius = radius |
| 267 | + |
| 268 | + MERCURY = (1, 3.303e+23, 2.4397e6) |
| 269 | + VENUS = (2, 4.869e+24, 6.0518e6) |
| 270 | + EARTH = (3, 5.976e+24, 6.37814e6) |
| 271 | + MARS = (6.421e+23, 3.3972e6) # Type checker error (optional) |
| 272 | + JUPITER = 5 # Type checker error (optional) |
| 273 | + |
| 274 | + reveal_type(Planet.MERCURY.value) # Revealed type is Literal[1] (or int or object or Any) |
| 275 | + |
| 276 | + |
| 277 | +The class ``enum.auto`` and method ``_generate_next_value_`` can be used within |
| 278 | +an enum class to automatically generate values for enum members. Type checkers |
| 279 | +may support these to infer literal types for member values:: |
| 280 | + |
| 281 | + class Color(Enum): |
| 282 | + RED = auto() |
| 283 | + GREEN = auto() |
| 284 | + BLUE = auto() |
| 285 | + |
| 286 | + reveal_type(Color.RED.value) # Revealed type is Literal[1] (or int or object or Any) |
| 287 | + |
| 288 | + |
| 289 | +If an enum class provides an explicit type annotation for ``_value_``, type |
| 290 | +checkers should enforce this declared type when values are assigned to |
| 291 | +``_value_``:: |
| 292 | + |
| 293 | + class Color(Enum): |
| 294 | + _value_: int |
| 295 | + RED = 1 # OK |
| 296 | + GREEN = "green" # Type error |
| 297 | + |
| 298 | + class Planet(Enum): |
| 299 | + _value_: str |
| 300 | + |
| 301 | + def __init__(self, value: int, mass: float, radius: float): |
| 302 | + self._value_ = value # Type error |
| 303 | + |
| 304 | + MERCURY = (1, 3.303e+23, 2.4397e6) |
| 305 | + |
| 306 | +If the literal values for enum members are not supplied, as they sometimes |
| 307 | +are not within a type stub file, a type checker can use the type of the |
| 308 | +``_value_`` attribute:: |
| 309 | + |
| 310 | + class ColumnType(Enum): |
| 311 | + _value_: int |
| 312 | + DORIC = ... |
| 313 | + IONIC = ... |
| 314 | + CORINTHIAN = ... |
| 315 | + |
| 316 | + reveal_type(ColumnType.DORIC.value) # Revealed type is int (or object or Any) |
| 317 | + |
| 318 | + |
| 319 | +Enum Literal Expansion |
| 320 | +---------------------- |
| 321 | + |
| 322 | +From the perspective of the type system, most enum classes are equivalent |
| 323 | +to the union of the literal members within that enum. (This rule |
| 324 | +does not apply to classes that derive from ``enum.Flag`` because these enums |
| 325 | +allow flags to be combined in arbitrary ways.) Because of the equivalency |
| 326 | +between an enum class and the union of literal members within that enum, the |
| 327 | +two types may be used interchangeably. Type checkers may therefore expand |
| 328 | +an enum type (that does not derive from ``enum.Flag``) into a union of |
| 329 | +literal values during type narrowing and exhaustion detection:: |
| 330 | + |
| 331 | + class Color(Enum): |
| 332 | + RED = 1 |
| 333 | + GREEN = 2 |
| 334 | + BLUE = 3 |
| 335 | + |
| 336 | + def print_color1(c: Color): |
| 337 | + if c is Color.RED or c is Color.BLUE: |
| 338 | + print("red or blue") |
| 339 | + else: |
| 340 | + reveal_type(c) # Revealed type is Literal[Color.GREEN] |
| 341 | + |
| 342 | + def print_color2(c: Color): |
| 343 | + match c: |
| 344 | + case Color.RED | Color.BLUE: |
| 345 | + print("red or blue") |
| 346 | + case Color.GREEN: |
| 347 | + print("green") |
| 348 | + case _: |
| 349 | + reveal_type(c) # Revealed type is Never |
| 350 | + |
| 351 | + |
| 352 | +Likewise, a type checker should treat a complete union of all literal members |
| 353 | +as compatible with the enum type:: |
| 354 | + |
| 355 | + class Answer(Enum): |
| 356 | + Yes = 1 |
| 357 | + No = 2 |
| 358 | + |
| 359 | + def func(val: object) -> Answer: |
| 360 | + if val is not Answer.Yes and val is not Answer.No: |
| 361 | + raise ValueError("Invalid value") |
| 362 | + reveal_type(val) # Revealed type is Answer (or Literal[Answer.Yes, Answer.No]) |
| 363 | + return val # OK |
0 commit comments