Use enum trick for dataclasses.MISSING (#7127)

The goal of this change is to fix the behavior of
`dataclasses.Field`. Several attributes of a `Field` may have a value
of `MISSING` (a sentinel value). As a result, attributes of Field may
be checked against `MISSING` as in

     [f for f in fields(obj) if f.default is MISSING]

`default`, `default_factory`, and `kw_only` are the attributes
which may have a value of `MISSING`.

This workaround of defining `_MISSING_TYPE` as an enum, and `MISSING`
as its only member, allows `... | Literal[_MISSING_TYPE.MISSING]` to
be used in type annotations for these attributes. This is very
slightly inaccurate -- primarily in that `_MISSING_TYPE` isn't really
an enum -- but this allows for use in `Literal`.
After PEP 661 (Sentinel Values), there may be a more accurate way of
writing this, but for now this works.

This adjustment is only applied to the attributes of Field, not the
arguments to functions and methods.
This commit is contained in:
Stephen Rosen
2022-02-05 00:21:12 -05:00
committed by GitHub
parent 819f8dfc10
commit 38d32a916c

View File

@@ -1,7 +1,9 @@
import enum
import sys
import types
from builtins import type as Type # alias to avoid name clashes with fields named "type"
from typing import Any, Callable, Generic, Iterable, Mapping, Protocol, TypeVar, overload
from typing_extensions import Literal
if sys.version_info >= (3, 9):
from types import GenericAlias
@@ -9,9 +11,15 @@ if sys.version_info >= (3, 9):
_T = TypeVar("_T")
_T_co = TypeVar("_T_co", covariant=True)
class _MISSING_TYPE: ...
# define _MISSING_TYPE as an enum within the type stubs,
# even though that is not really its type at runtime
# this allows us to use Literal[_MISSING_TYPE.MISSING]
# for background, see:
# https://github.com/python/typeshed/pull/5900#issuecomment-895513797
class _MISSING_TYPE(enum.Enum):
MISSING = enum.auto()
MISSING: _MISSING_TYPE
MISSING = _MISSING_TYPE.MISSING
if sys.version_info >= (3, 10):
class KW_ONLY: ...
@@ -72,15 +80,15 @@ class _DefaultFactory(Protocol[_T_co]):
class Field(Generic[_T]):
name: str
type: Type[_T]
default: _T
default_factory: _DefaultFactory[_T]
default: _T | Literal[_MISSING_TYPE.MISSING]
default_factory: _DefaultFactory[_T] | Literal[_MISSING_TYPE.MISSING]
repr: bool
hash: bool | None
init: bool
compare: bool
metadata: types.MappingProxyType[Any, Any]
if sys.version_info >= (3, 10):
kw_only: bool
kw_only: bool | Literal[_MISSING_TYPE.MISSING]
def __init__(
self,
default: _T,