I have some concerns about the draft chapter as it relates to stub files.
The draft chapter says:
All enum member objects have an attribute _value_
that contains the member’s
value. They also have a property value
that returns the same value. Type
checkers may infer the type of a member’s value::
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
reveal_type(Color.RED._value_) # Revealed type is Literal[1] (or int or object or Any)
reveal_type(Color.RED.value) # Revealed type is Literal[1] (or int or object or Any)
With regards to stub files, the draft chapter states:
If the literal values for enum members are not supplied, as they sometimes
are not within a type stub file, a type checker can use the type of the
_value_
attribute::
class ColumnType(Enum):
_value_: int
DORIC = ...
IONIC = ...
CORINTHIAN = ...
reveal_type(ColumnType.DORIC.value) # Revealed type is int (or object or Any)
This doesn’t appear to account for the fact that it’s perfectly legal at runtime for enums to be heterogenous in the types of their member values. While it’s less common to have heterogenous enums than homogenous enums, this isn’t a hypothetical concern: here’s an enum in the stdlib uuid
module where different members have values of different types:
@_simple_enum(Enum)
class SafeUUID:
safe = 0
unsafe = -1
unknown = None
For cases where enums have simple enum values, like this one, we can simply include the enum member values directly in a stub file, so type checkers should be able to figure out that the type of SafeUUID.unsafe.value
is of type Literal[-1]
, and the type of SafeUUID.unknown.value
is of type None
. But it’s not always possible to include enum values in stub files: in some cases, the value of the enum may be constructed using a more complex expression, and complex expressions have traditionally been banned in stub files (with a few very small exceptions), on the grounds that stub files are essentially declarative “data files” for the type checker.
You might argue that creating heterogenous enums like SafeUUID
is an antipattern, and that enums should generally have values with homogenous types. I would tend to agree with you. However, I think it’s important that we should still have a way to express heterogenous enums in stub files if that’s what the runtime is doing. The most important thing in a stub is for us to be accurate w.r.t. to the runtime, even if the runtime is making use of an antipattern that we wouldn’t necessarily endorse when we were writing our own code.
I would also prefer it if the spec explicitly banned using = ...
for enum members in a stub file if the enum in the stub file does not include an explicit _value_
annotation. In the absence of a _value_
annotation, it will be impossible for a type checker to infer the type of a member declared using = ...
in a stub file. I would prefer for these cases to be explicitly rejected by the type checker, rather than the type checker inferring a type of Any
or Unknown
for the type of the value of that member.