Add section to PEP 484 on new feature Type[C].
This commit is contained in:
parent
2c4f0ac43b
commit
92eb68bca0
95
pep-0484.txt
95
pep-0484.txt
|
@ -861,6 +861,101 @@ allow all operations on it, and a value of type ``Any`` can be assigned
|
||||||
to a variable (or used as a return value) of a more constrained type.
|
to a variable (or used as a return value) of a more constrained type.
|
||||||
|
|
||||||
|
|
||||||
|
The type of class objects
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Sometimes you want to talk about class objects, in particular class
|
||||||
|
objects that inherit from a given class. This can be spelled as
|
||||||
|
``Type[C]`` where ``C`` is a class. To clarify: while ``C`` (when
|
||||||
|
used as an annotation) refers to instances of class ``C``, ``Type[C]``
|
||||||
|
refers to *subclasses* of ``C``. (This is a similar distinction as
|
||||||
|
between ``object`` and ``type``.)
|
||||||
|
|
||||||
|
For example, suppose we have the following classes::
|
||||||
|
|
||||||
|
class User: ... # Abstract base for User classes
|
||||||
|
class BasicUser(User): ...
|
||||||
|
class ProUser(User): ...
|
||||||
|
class TeamUser(User): ...
|
||||||
|
|
||||||
|
And suppose we have a function that creates an instance of one of
|
||||||
|
these classes if you pass it a class object::
|
||||||
|
|
||||||
|
def new_user(user_class):
|
||||||
|
user = user_class()
|
||||||
|
# (Here we could write the user object to a database)
|
||||||
|
return user
|
||||||
|
|
||||||
|
Without ``Type[]`` the best we could do to annotate ``new_user()``
|
||||||
|
would be::
|
||||||
|
|
||||||
|
def new_user(user_class: type) -> User:
|
||||||
|
...
|
||||||
|
|
||||||
|
However using ``Type[]`` and a type variable with an upper bound we
|
||||||
|
can do much better::
|
||||||
|
|
||||||
|
U = TypeVar('U', bound=User)
|
||||||
|
def new_user(user_class: Type[U]) -> U:
|
||||||
|
...
|
||||||
|
|
||||||
|
Now when we call ``new_user()`` with a specific subclass of ``User`` a
|
||||||
|
type checker will infer the correct type of the result::
|
||||||
|
|
||||||
|
joe = new_user(BasicUser) # Inferred type is BasicUser
|
||||||
|
|
||||||
|
The value corresponding to ``Type[C]`` must be an actual class object
|
||||||
|
that's a subtype of ``C``, not a special form. IOW, in the above
|
||||||
|
example calling e.g. ``new_user(Union[BasicUser, ProUser])`` is
|
||||||
|
rejected by the type checker (in addition to failing at runtime
|
||||||
|
because you can't instantiate a union).
|
||||||
|
|
||||||
|
Note that it is legal to use a union of classes as the parameter for
|
||||||
|
``Type[]``, as in::
|
||||||
|
|
||||||
|
def new_non_team_user(user_class: Type[Union[BasicUser, ProUser]]):
|
||||||
|
...
|
||||||
|
|
||||||
|
However the actual argument passed in at runtime must still be a
|
||||||
|
concrete class object, e.g. in the above example::
|
||||||
|
|
||||||
|
new_non_team_user(ProUser) # OK
|
||||||
|
new_non_team_user(TeamUser) # Disallowed by type checker
|
||||||
|
|
||||||
|
``Type[Any]`` is also supported (see below for its meaning). However,
|
||||||
|
other special constructs like ``Tuple`` or ``Callable`` are not
|
||||||
|
allowed.
|
||||||
|
|
||||||
|
There are some concerns with this feature: for example when
|
||||||
|
``new_user()`` calls ``user_class()`` this implies that all subclasses
|
||||||
|
of ``User`` must support this in their constructor signature. However
|
||||||
|
this is not unique to ``Type[]``: class methods have similar concerns.
|
||||||
|
A type checker ought to flag violations of such assumptions, but by
|
||||||
|
default constructor calls that match the constructor signature in the
|
||||||
|
indicated base class (``User`` in the example above) should be
|
||||||
|
allowed. A program containing a complex or extensible class hierarchy
|
||||||
|
might also handle this by using a factory class method. A future
|
||||||
|
revision of this PEP may introduce better ways of dealing with these
|
||||||
|
concerns.
|
||||||
|
|
||||||
|
When ``Type`` is parameterized it requires exactly one parameter.
|
||||||
|
Plain ``Type`` without brackets is equivalent to ``Type[Any]`` and
|
||||||
|
this in turn is equivalent to ``type`` (the root of Python's metaclass
|
||||||
|
hierarchy). This equivalence also motivates the name, ``Type``, as
|
||||||
|
opposed to alternatives like ``Class`` or ``SubType``, which were
|
||||||
|
proposed while this feature was under discussion; this is similar to
|
||||||
|
the relationship between e.g. ``List`` and ``list``.
|
||||||
|
|
||||||
|
Regarding the behavior of ``Type[Any]`` (or ``Type`` or ``type``),
|
||||||
|
accessing attributes of a variable with this type only provides
|
||||||
|
attributes and methods defined by ``type`` (for example,
|
||||||
|
``__repr__()`` and ``__mro__``). Such a variable can be called with
|
||||||
|
arbitrary arguments, and the return type is ``Any``.
|
||||||
|
|
||||||
|
``Type[T]`` should be considered covariant, since for a concrete class
|
||||||
|
``C``, ``Type[C]`` matches ``C`` and any of its subclasses.
|
||||||
|
|
||||||
|
|
||||||
Version and platform checking
|
Version and platform checking
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue