PEP 586: Rewrite sections regarding enum (#997)
This commit adjusts two sections of this PEP that are related to enums. First, it removes the sections regarding the interaction between enums, imports, and Any. I wasn't aware that the import behavior described in that section was mypy-only and isn't codified in PEP 484. So, I decided to just remove that section entirely -- it didn't feel there was much I could salvage there. Instead, I opted to adjust the "invalid parameters" section to explain in a little more detail why `Literal[Any]` is not allowed. Second, I split up the section about type narrowing into two. The first new section is a reminder that PEP 484 requires type checkers to support certain kinds of exhaustibility checks when working with enums. To make this more clear, I adjusted the example to be more closer to what is used in the spec and removed any mention of reachability -- it felt like a distraction. The second section focuses back on some neat tricks using Literals that type checkers may optionally implement. I also tweaked some of the examples here as suggested in https://github.com/python/peps/pull/993.
This commit is contained in:
parent
0e90d579b3
commit
2cb7fa99ce
80
pep-0586.rst
80
pep-0586.rst
|
@ -249,13 +249,15 @@ The following parameters are intentionally disallowed by design:
|
|||
only types, never over values.
|
||||
|
||||
The following are provisionally disallowed for simplicity. We can consider
|
||||
allowing them on a case-by-case basis based on demand.
|
||||
allowing them in future extensions of this PEP.
|
||||
|
||||
- Floats: e.g. ``Literal[3.14]``. Note: if we do decide to allow
|
||||
floats, we should likely disallow literal infinity and literal NaN.
|
||||
|
||||
- Any: e.g. ``Literal[Any]`` Note: the semantics of what exactly
|
||||
``Literal[Any]`` means would need to be clarified first.
|
||||
- Floats: e.g. ``Literal[3.14]``. Representing Literals of infinity or NaN
|
||||
in a clean way is tricky; real-world APIs are unlikely to vary their
|
||||
behavior based on a float parameter.
|
||||
|
||||
- Any: e.g. ``Literal[Any]``. ``Any`` is a type, and ``Literal[...]`` is
|
||||
meant to contain values only. It is also unclear what ``Literal[Any]``
|
||||
would actually semantically mean.
|
||||
|
||||
Parameters at runtime
|
||||
---------------------
|
||||
|
@ -293,26 +295,6 @@ In cases like these, we always assume the user meant to construct a
|
|||
literal string. If the user wants a forward reference, they must wrap
|
||||
the entire literal type in a string -- e.g. ``"Literal[Color.RED]"``.
|
||||
|
||||
Literals, enums, and Any
|
||||
------------------------
|
||||
|
||||
Another ambiguity is when the user attempts to use some expression that
|
||||
is meant to be an enum but is actually of type ``Any``. For example,
|
||||
suppose a user attempts to import an enum from a package with no type hints::
|
||||
|
||||
from typing import Literal
|
||||
from lib_with_no_types import SomeEnum # SomeEnum has type 'Any'!
|
||||
|
||||
# x has type `Literal[Any]` due to the bad import
|
||||
x: Literal[SomeEnum.FOO]
|
||||
|
||||
Because ``Literal`` may not be parameterized by ``Any``, this program
|
||||
is *illegal*: the type checker should report an error with the last line.
|
||||
|
||||
In short, while ``Any`` may effectively be used as a placeholder for any
|
||||
arbitrary *type*, it is currently **not** allowed to serve as a placeholder
|
||||
for any arbitrary *value*.
|
||||
|
||||
Type inference
|
||||
==============
|
||||
|
||||
|
@ -517,20 +499,21 @@ We considered several different proposals for fixing this, but ultimately
|
|||
decided to defer the problem of integer generics to a later date. See
|
||||
`Rejected or out-of-scope ideas`_ for more details.
|
||||
|
||||
Interactions with type narrowing
|
||||
--------------------------------
|
||||
Interactions with enums and exhaustiveness checks
|
||||
-------------------------------------------------
|
||||
|
||||
Type checkers should be capable of performing exhaustiveness checks when
|
||||
working Literal types that have a closed number of variants, such as
|
||||
enums. For example, the type checker should be capable of inferring that the
|
||||
final ``else`` statement in the following function is unreachable::
|
||||
enums. For example, the type checker should be capable of inferring that
|
||||
the final ``else`` statement must be of type ``str``, since all three
|
||||
values of the ``Status`` enum have already been exhausted::
|
||||
|
||||
class Status(Enum):
|
||||
SUCCESS = 0
|
||||
INVALID_DATA = 1
|
||||
FATAL_ERROR = 2
|
||||
|
||||
def parse_status(s: Status) -> None:
|
||||
def parse_status(s: Union[str, Status]) -> None:
|
||||
if s is Status.SUCCESS:
|
||||
print("Success!")
|
||||
elif s is Status.INVALID_DATA:
|
||||
|
@ -538,24 +521,29 @@ final ``else`` statement in the following function is unreachable::
|
|||
elif s is Status.FATAL_ERROR:
|
||||
print("Unexpected fatal error...")
|
||||
else:
|
||||
# Error should not be reported by type checkers that
|
||||
# ignore errors in unreachable blocks
|
||||
print("Nonsense" + 100)
|
||||
# 's' must be of type 'str' since all other options are exhausted
|
||||
print("Got custom status: " + s)
|
||||
|
||||
This behavior is technically not new: this behavior is
|
||||
The interaction described above is not new: it's already
|
||||
`already codified within PEP 484 <pep-484-enums_>`_. However, many type
|
||||
checkers (such as mypy) do not yet implement this behavior. Once Literal
|
||||
types are introduced, it will become easier to do so: we can model
|
||||
enums as being approximately equal to the union of their values and
|
||||
take advantage of any existing logic regarding unions, exhaustibility,
|
||||
and type narrowing.
|
||||
checkers (such as mypy) do not yet implement this due to the expected
|
||||
complexity of the implementation work.
|
||||
|
||||
So here, ``Status`` could be treated as being approximately equal to
|
||||
``Literal[Status.SUCCESS, Status.INVALID_DATA, Status.FATAL_ERROR]``
|
||||
Some of this complexity will be alleviated once Literal types are introduced:
|
||||
rather than entirely special-casing enums, we can instead treat them as being
|
||||
approximately equivalent to the union of their values and take advantage of any
|
||||
existing logic regarding unions, exhaustibility, type narrowing, reachability,
|
||||
and so forth the type checker might have already implemented.
|
||||
|
||||
So here, the ``Status`` enum could be treated as being approximately equivalent
|
||||
to ``Literal[Status.SUCCESS, Status.INVALID_DATA, Status.FATAL_ERROR]``
|
||||
and the type of ``s`` narrowed accordingly.
|
||||
|
||||
Type checkers may optionally perform additional analysis and narrowing
|
||||
beyond what is described above.
|
||||
Interactions with narrowing
|
||||
---------------------------
|
||||
|
||||
Type checkers may optionally perform additional analysis for both enum and
|
||||
non-enum Literal types beyond what is described in the section above.
|
||||
|
||||
For example, it may be useful to perform narrowing based on things like
|
||||
containment or equality checks::
|
||||
|
@ -566,7 +554,7 @@ containment or equality checks::
|
|||
# Literal["MALFORMED", "ABORTED"] here.
|
||||
return expects_bad_status(status)
|
||||
|
||||
# Similarly, type checker could narrow 'x' to Literal["PENDING"]
|
||||
# Similarly, type checker could narrow 'status' to Literal["PENDING"]
|
||||
if status == "PENDING":
|
||||
expects_pending_status(status)
|
||||
|
||||
|
@ -577,7 +565,7 @@ involving Literal bools. For example, we can combine ``Literal[True]``,
|
|||
@overload
|
||||
def is_int_like(x: Union[int, List[int]]) -> Literal[True]: ...
|
||||
@overload
|
||||
def is_int_like(x: Union[str, List[str]]) -> Literal[False]: ...
|
||||
def is_int_like(x: object) -> bool: ...
|
||||
def is_int_like(x): ...
|
||||
|
||||
vector: List[int] = [1, 2, 3]
|
||||
|
|
Loading…
Reference in New Issue