Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions pep-0484.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.


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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also discuss Type[Any]. type should likely be equivalent to Type[Any], similar to list / List[Any]. It's not entirely obvious how Type[Any] should behave. How about this:

  • It can be called with arbitrary arguments, and the return type is Any.
  • It only provides attributes and methods defined in type. Arbitrary class methods and attributes aren't supported.

@ilevkivskyi ilevkivskyi May 16, 2016 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JukkaL I agree, but I would add a third option of having both behaviours that you proposed: Type[Any] behaves like described in the first option, while type and Type[object] behaves like described in the second option. Note also that if we want to discuss Type[Any] we probably need to change wording in the first paragraph to "where C is a class or Any".

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
-----------------------------

Expand Down