[PYTHON] [Translation] PEP 0484 --Type Hints

This article is a translation of the type hint PEP 0484 introduced in Python 3.5.

We haven't had enough time to translate it, so if you have a mistranslation or a more appropriate translation, please let us know in an edit request.

PEP 0484 --Type hint

PEP: 484
title: Type hint
Author: Guido van Rossum <guido at python.org>, Jukka Lehtosalo <jukka.lehtosalo at iki.fi>, Łukasz Langa <lukasz at langa.pl>
BDFL-Delegation: Mark Shannon
Discussions-To: Python-Dev <python-dev at python.org>
status: Authorization
Type: Standardization process
Created date: September 29, 2014
Post history: January 16, 2015, March 20, 2015, April 17, 2015, May 20, 2015, May 22, 2015
resolution: https://mail.python.org/pipermail/python-dev/2015-May/140104.html

Overview

PEP 3107 introduced the syntax for function annotation, but its semantics were deliberately undefined. Today, there are many third-party uses for static type analysis, and the community can benefit from standardized vocabulary and basic tools in the standard library.

This PEP introduces conventions for situations where provisional modules and annotations are not available to provide standardized definitions and tools.

This PEP still does not explicitly block other uses of annotation. Furthermore, please note that conforming to this specification does not require (or prohibit) any special processing of annotations. It simply brings better coordination, as PEP 333 went to the web framework.

For example, the following simple function is annotated with its argument and return types.

def greeting(name: str) -> str:
    return 'Hello ' + name

While these annotations can be referenced as regular __annotations__ attributes at run time, they do not perform type checking at run time. Instead, this proposal assumes the existence of a separate offline checker. Users can voluntarily inspect the source code using such a type checker. Basically, these type checkers act as very powerful linters. (Of course, similar checkers could be used to enforce Design By Contract for individual users and JIT optimization at runtime, but those tools are still at a practical level. not.)

This suggestion is strongly inspired by mypy [mypy]. For example, the type of "integer sequence" is written as Sequence [int]. You don't have to add new syntax to your language by using square brackets. The example presented here uses the custom type Sequence imported from the pure-Python typing module. Notations such as Sequence [int] work at runtime by implementing __getitem__ () in the metaclass (but its signature is primarily for offline checkers).

The type system supports special types that are consistent (that is, mutually assignable) with all types, such as union types, generic types, and ʻAny`. This latter function is inherited from the idea of gradual typing. Gradual typing and the entire type system are described in PEP 483.

Other methods we have borrowed, or other methods to compare or contrast, are summarized in PEP 482.

Rationale and purpose

PEP 3107 has been added to support arbitrary annotations as part of the function definition. Annotations had no special meaning at the time, but they always had the potential purpose of being used for type hints [[gvr-artima]](https://www.python. org / dev / peps / pep-0484 / # gvr-artima). Type checking is mentioned as the first use case in PEP 3107.

This PEP aims to provide a standardized syntax for type annotations. It opens up the possibilities of Python code for easy static analysis and refactoring, potential runtime type checking and (possibly in some context) code generation using type information.

The most important of these purposes is static analysis. This includes support for offline checkers like mypy and the standard notation used by the IDE for code completion and refactoring.

Unintended

The proposed typing module contains components for run-time type checking, especially the get_type_hints () function, but to implement certain run-time type checking features (eg decorators and You will need to develop a third party package (using metaclasses). Using type hints to optimize performance remains a challenge for readers of this PEP.

Let's also emphasize the following. ** Python is still a dynamically typed language. Python authors don't want to require type hints (even as conventions). ** **

Annotation Semantics

Any unannotated function should be treated as having as generic a type as possible by all type checkers or should be ignored. Functions with the @no_type_check decorator or the comment # type: ignore should be treated as having no annotations.

It is recommended, but not required, for the function to be checked to have all arguments and return annotations. The default annotation for checked function arguments and return types is ʻAny. The exception is that the first arguments of the instance and class methods do not need to be annotated. It is assumed that the instance method has the type of the class to which the instance method belongs, and the class method has the type object type corresponding to the class object to which the class method belongs. For example, the first argument of an instance method of class ʻA is implicitly of type ʻA`. Class methods cannot represent the exact type of the first argument in the available type notation.

(Note that the return type of __init__ should be annotated as-> None. The reason is subtle. If __init__ is an annotation of the return type-> None Should an unannotated __init__ method with no arguments be type-checked if it is considered to have? Rather than leaving this ambiguity or introducing an exception to this exception, we simply init` says it should have a return annotation, which makes its default behavior similar to other methods.)

The type checker is expected to check if the contents of the function being checked are consistent with the specified annotation. Annotations are also used to check the validity of calls that appear in other checked functions.

Type checkers are expected to try to guess as much information as possible as needed. The minimum requirement is to handle the built-in decorators @property, @staticmethod and @classmethod.

Type definition syntax

The syntax leverages PEP 3107 style annotations along with many extensions described in the following sections. Its basic form is to use type hints by filling the function annotation slots with classes.

def greeting(name: str) -> str:
    return 'Hello ' + name

This declares that the expected type of the name argument is str. Similarly, the expected return type is str.

Expressions whose type is a subtype of the type of a particular argument are also acceptable as that argument.

Allowed type hints (https://www.python.org/dev/peps/pep-0484/#id14)

Type hints can be built-in classes (including those defined in standard libraries or third-party extension modules), abstract base classes, types defined in the types module, and user-defined classes (standard libraries or third-party modules). (Including those defined in).

Although annotations are generally the best format for type hints, it may be more appropriate to represent the type hints in special comments or in separate stub files. (See example below.)

The annotation must be a valid expression that evaluates without exception when the function is defined (but see below for forward references).

Annotations should be kept simple, otherwise static analysis tools may not be able to interpret their values. For example, dynamically calculated types are unlikely to be understandable. (This is intentionally a somewhat vague requirement. As a result of discussion, certain additional and exception specifications may be added to future versions of this PEP.)

In addition to the above, special constructors defined below are available: all abstract base classes exposed in None, ʻAny, ʻUnion, Tuple, Callable, typing And alternatives for concrete classes (eg Sequence and Dict), type variables, and type aliases.

All newly introduced names (such as ʻAny and ʻUnion) used to support the features described in the following sections are provided by the typing module.

Using None

When used in type hints, the expression None is considered equivalent totype (None).

Type alias

Type aliases are simply defined by variable assignment.

Url = str

def retry(url: Url, retry_count: int) -> None:
    ...

The caveat here is that it is recommended to capitalize the first letter of the alias. That's because aliases represent user-defined types, which are usually written that way (like user-defined classes).

Type aliases can be as complex as type hints in annotations. Anything that is acceptable as a type hint is acceptable as a type alias.

from typing import TypeVar, Iterable, Tuple

T = TypeVar('T', int, float, complex)
Vector = Iterable[Tuple[T, T]]

def inproduct(v: Vector) -> T:
    return sum(x*y for x, y in v)

This is equivalent to the following code.

from typing import TypeVar, Iterable, Tuple

T = TypeVar('T', int, float, complex)

def inproduct(v: Iterable[Tuple[T, T]]) -> T:
    return sum(x*y for x, y in v)

Callable Objects (https://www.python.org/dev/peps/pep-0484/#id17)

Frameworks that receive callback functions with a particular signature may use Callable [[Arg1Type, Arg2Type], ReturnType] to do type hints. Here is an example.

from typing import Callable

def feeder(get_next_item: Callable[[], str]) -> None:
    # Body

def async_query(on_success: Callable[[int], None],
                on_error: Callable[[int, Exception], None]) -> None:
    # Body

You can declare the return type of a callable object without specifying a call signature by replacing the argument list with a literal ellipsis (three dots).

def partial(func: Callable[..., str], *args) -> Callable[..., str]:
    # Body

Note that there are no brackets before or after the ellipsis. In this case there are no restrictions on the callback function arguments (and keyword arguments are acceptable).

Using keyword arguments for callback functions is not recognized as a common use case, so there is currently no mechanism for specifying keyword arguments for Callable. Similarly, there is no mechanism for specifying a particular type of variadic argument in a callback signature.

Since typing.Callable has two roles as a replacement for collections.abc.Callable, ʻisinstance (x, typing.Callable) replaces ʻisinstance (x, collections.abc.Callable). It is implemented by delaying. However, ʻis instance (x, typing.Callable [...])` is not supported.

Generics

Type information about objects in a container cannot be type inferred statically in the usual way. As a result, the abstract base class has been extended to support array subscripts to indicate the expected type of container element. Here is an example.

from typing import Mapping, Set

def notify_by_email(employees: Set[Employee], overrides: Mapping[str, str]) -> None:
    ...

Generics are parameterized using a new factory called TypeVar in the typing module. Here is an example.

from typing import Sequence, TypeVar

T = TypeVar('T')      # Declare type variable

def first(l: Sequence[T]) -> T:   # Generic function
    return l[0]

In this case, the contract is that the return value is consistent with the elements held in the collection.

The expression TypeVar () must always be assigned directly to a variable (should not be used as part of a larger expression). The argument passed to TypeVar () must be a string equivalent to the variable name to which it is assigned. Do not redefine type variables.

TypeVar supports parameterized types that constrain a particular set of possible types. For example, you can define a type variable that handles only str and bytes. By default, type variables handle all possible types. This is an example of a type variable constraint.

from typing import TypeVar

AnyStr = TypeVar('AnyStr', str, bytes)

def concat(x: AnyStr, y: AnyStr) -> AnyStr:
    return x + y

The function concat can be called with either two str type arguments or two bytes type arguments, but with a mixture of str type and bytes type arguments. It cannot be called.

There must be at least two constraints, if any. You cannot specify only one constraint.

The subtypes of a type constrained by a type variable should be treated as their respective explicitly represented base types in the context of the type variable. Consider this example.

class MyStr(str):
    ...

x = concat(MyStr('apple'), MyStr('pie'))

This call is valid, but its type variable ʻAnyStr will be set to strinstead ofMyStr. The type inferred as the return value assigned to x is actually str`.

In addition, ʻAny` is a valid value for all type variables. Consider the following:

def count_truthy(elements: List[Any]) -> int:
    return sum(1 for elem in elements if element)

This is equivalent to removing the generics notation and simply ʻelements: List`.

User-defined generic types (https://www.python.org/dev/peps/pep-0484/#id19)

You can use the Generic base class to define a user-defined class as a generic type. Here is an example.

from typing import TypeVar, Generic

T = TypeVar('T')

class LoggedVar(Generic[T]):
    def __init__(self, value: T, name: str, logger: Logger) -> None:
        self.name = name
        self.logger = logger
        self.value = value

    def set(self, new: T) -> None:
        self.log('Set ' + repr(self.value))
        self.value = new

    def get(self) -> T:
        self.log('Get ' + repr(self.value))
        return self.value

    def log(self, message: str) -> None:
        self.logger.info('{}: {}'.format(self.name message))

The base class Generic [T] defines a class LoggedVar that takes one type parameter T. It also enables T as a type inside the class.

The Generic base class uses a metaclass that defines __getitem__ so that LoggedVar [t] is valid as a type.

from typing import Iterable

def zero_all_vars(vars: Iterable[LoggedVar[int]]) -> None:
    for var in vars:
        var.set(0)

A generic type takes an arbitrary number of type variables and constrains them. The following code is valid.

from typing import TypeVar, Generic
...

T = TypeVar('T')
S = TypeVar('S')

class Pair(Generic[T, S]):
    ...

The individual type variables passed to Generic must be separate. This invalidates the following code.

from typing import TypeVar, Generic
...

T = TypeVar('T')

class Pair(Generic[T, T]):   # INVALID
    ...

You can use multiple inheritance for Generic.

from typing import TypeVar, Generic, Sized

T = TypeVar('T')

class LinkedList(Sized, Generic[T]):
    ...

If you subclass a generic class without specifying a type parameter, it is assumed that you specified ʻAnyfor each positional argument. In the following example,MyIterable is not a generic type, but implicitly inherits from ʻIterable [Any].

from typing import Iterable

class MyIterable(Iterable): # Same as Iterable[Any]
    ...

Generic type metaclasses are not supported.

Instantiate and type erasure of generic classes (https://www.python.org/dev/peps/pep-0484/#id20)

Generic types like List and Sequence cannot be instantiated. However, user-defined classes derived from those generic types can be instantiated. Consider a Node class that inherits fromGeneric [T].

from typing import TypeVar, Generic

T = TypeVar('T')

class Node(Generic[T]):
    ...

There are now two ways to instantiate this class. The type that the type checker infers depends on which format you use. The first way is to explicitly pass the value of the type parameter. This overrides any type inference performed by the type checker.

x = Node[T]() # The type inferred for x is Node[T].
y = Node[int]() # The type inferred for y is Node[int].

If the type is not explicitly specified, the type checker is free to infer. Consider the following code.

x = Node()

The inferred type will be Node [Any]. There is not enough context to infer a more accurate type. Alternatively, the type checker may not accept this line and require explicit annotation as follows:

x = Node() # type: Node[int] # Inferred type is Node[int].

Type checkers with stronger type inference look at how x is used in the file, and even if no explicit type annotation is found, type inference is more accurate, likeNode [int]. I will try. But perhaps it's impossible to get that kind of type inference to work in all cases. Python programs can be too dynamic.

This PEP does not provide details on how type inference should work. We allow different tools to try different approaches. Future versions may set clearer rules.

The type is not preserved at run time. The class of x is simply Node in all cases. This behavior is called "type erasure". This is a common practice for languages with generics (eg Java and TypeScript).

Any generic type as a base class

Generic [T] is valid only as a base class. This is not strictly a type. However, user-defined generic types in LinkedList [T] in the sample code above, built-in generic types, and abstract base classes such as List [T] and ʻIterable [T]can also be types. It is also valid as a base class. For example, you can define a subclass ofDict` with specialized type arguments.

from typing import Dict, List, Optional

class Node:
    ...

class SymbolTable(Dict[str, List[Node]]):
    def push(self, name: str, node: Node) -> None:
        self.setdefault(name, []).append(node)

    def pop(self, name: str) -> Node:
        return self[name].pop()

    def lookup(self, name: str) -> Optional[Node]:
        nodes = self.get(name)
        if nodes:
            return nodes[-1]
        return None

SymbolTable is a subclass of dict and a subtype of Dict [str, List [Node]].

If a generic base class has a type variable as a type argument, this makes the defined class generic. For example, you can define a generic LinkedList class that is a repeatable container object.

from typing import TypeVar, Iterable, Container

T = TypeVar('T')

class LinkedList(Iterable[T], Container[T]):
    ...

Now LinkedList [int] is a valid type. Note that you can use T multiple times in the list of base classes if you don't use T multiple times inside Generic [...].

Also consider the following example.

from typing import TypeVar, Mapping

T = TypeVar('T')

class MyDict(Mapping[str, T]):
    ...

In this case MyDict takes a single parameter T.

Abstract generic type

The metaclass used by Generic is a subclass of ʻabc.ABCMeta`. A generic class becomes an abstract base class by including abstract methods or properties. Also, a generic class can have multiple abstract base classes as base classes without metaclass conflicts.

Type variable with upper bound

Type variables can be specified as upper bound using bound = type. This means that the actual type that replaces the type variable (explicitly or implicitly) must be a subclass of the boundary type. A common example is a definition of a Comparable type that works well enough to catch most common errors.

from typing import TypeVar

class Comparable(metaclass=ABCMeta):
    @abstractmethod
    def __lt__(self, other: Any) -> bool:
        ...
    ... # __gt__ etc. as well

CT = TypeVar('CT', bound=Comparable)

def min(x: CT, y: CT) -> CT:
    if x < y:
        return x
    else:
        return y

min(1, 2) # ok, return type int
min('x', 'y') # ok, return type str

(Note that this is not always ideal. For example, min ('x', 1) will result in an error at runtime, but the type checker simply infers Comparable as the return type. Unfortunately, we need to introduce a more powerful and complex concept, F-bounded polymorphism, to address this. In the future we may rethink this concept.)

Upper bounds cannot be combined with type constraints (like ʻAnyStr` used in the previous example). Type constraints ensure that the inferred type is \ _exactly \ _ matched to any of the constrained types. The upper bound, on the other hand, only requires that the actual type be a subclass of the borderline type.

Covariance and Contravariance

Consider class ʻEmployeeand its subclassManager. Now suppose you have a function with arguments annotated with List [Employee]. Should it be allowed to call this function with a variable of type List [Manager] as an argument? Many would answer "yes, of course" without even thinking about its logical consequences. However, the type checker should reject such calls unless you know more about the function. The function may add a ʻEmpoyee instance to its list. It violates the variable type on the caller.

You can see that such arguments have \ _contravariantly) behavior. The intuitive answer (correct if this function doesn't change the arguments!) Requests arguments that behave \ _ covariantly \ _ (covariantly). A long introduction to these concepts can be found on Wikipedia [wiki-variance]. Here's a quick explanation of how to control the behavior of the type checker.

By default, type variables are assumed to be \ _invariant \ _ (invariant). That means that arguments to annotated arguments, such as the List [Employee] type, must exactly match the type annotation. Subclasses and superclasses of type parameters (ʻEmployee` in this example) are not allowed.

To declare a container type that accepts covariant type checks, use covariant = True to declare the type variable. Pass contravariant = True if contravariant behavior is required (rare). You can pass at most one of these.

A typical example is the definition of a non-rewritable (or read-only) container class.

from typing import TypeVar, Generic, Iterable, Iterator

T = TypeVar('T', covariant=True)

class ImmutableList(Generic[T]):
    def __init__(self, items: Iterable[T]) -> None:
        ...
    def __iter__(self) -> Iterator[T]:
        ...
    ...

class Employee:
    ...

class Manager(Employee):
    ...

def dump_employees(emps: ImmutableList[Employee]) -> None:
    for emp in emps:
        ...

mgrs = ImmutableList([Manager()])  # type: ImmutableList[Manager]
dump_employees(mgrs)  # OK

All read-only collection classes in the typing module (for example, Mapping and Sequence) are declared with covariant type variables. Variable collection classes (eg MutableMapping and MutableSequence) are defined using regular immutable type variables. One example of a contravariant type variable is the Generator type. The argument type of send () is contravariant (see below).

Note: Displacement variation affects generic type parameters. It does not affect normal parameters. For example, the following example is fine.

from typing import TypeVar

class Employee:
    ...

class Manager(Employee):
    ...

E = TypeVar('E', bound=Employee)  # Invariant

def dump_employee(e: E) -> None:
    ...

dump_employee(Manager())  # OK

Numeric Hierarchy

PEP 3141 defines a Python numeric hierarchy. The numbers module of the standard library implements the corresponding abstract base classes (Number, Complex, Real, Rational, ʻIntegral). These abstract base classes have some challenges, but the built-in concrete numeric classes complex, float and ʻint are used everywhere (especially the latter two :-).

Instead of requiring the user to write ʻimport numbers and use something like numbers.Float, this PEP suggests a simple shortcut that has almost the same effect: annotate if an argument has the float type. Arguments of type ʻint are also allowed when done. Similarly, arguments of type float or ʻint are allowed for arguments annotated with the type complex. It can't handle classes that implement the corresponding abstract base classes or the fractions.Fraction` class, but I think such use cases are very rare.

Byte type

There are three built-in classes for arrays of bytes: bytes, bytearray and memoryview (the classes provided by the ʻarraymodule are not counted). Of course,bytes and bytearrayshare a lot of behavior (but not all, for examplebytearray` can be changed).

A common base class called ByteString is defined in collections.abc, and the corresponding type also exists in typing, but because there are usually functions that accept (any) byte type. It would be tedious to have to write typing.ByteString everywhere. Therefore, as a shortcut similar to the built-in numeric class, a bytearray or memoryview type is also allowed when the argument is annotated as having a bytes type. (Repeat, there are situations where this doesn't work, but I think it's rare in practice.)

Refer to forward

When a type hint contains a name that has not yet been defined, its definition can be represented as a string literal. Names represented by string literals will be resolved later.

A common situation where this happens is when defining a container class. For container classes, the class you are defining appears in the signatures of some methods. For example, the following code (the first few lines of a simple binary tree implementation) doesn't work.

class Tree:
    def __init__(self, left: Tree, right: Tree):
        self.left = left
        self.right = right

To correspond to this, write as follows.

class Tree:
    def __init__(self, left: 'Tree', right: 'Tree'):
        self.left = left
        self.right = right

A string literal must contain a valid Python expression (that is, compile (lit,'','eval') becomes a valid code object) when the module is fully loaded. It must be able to evaluate without error. The local and global namespaces when evaluating it must be the same namespace in which the default arguments for the same function are evaluated.

In addition, the expression must be parseable as a valid type hint. For example, it is constrained by the rules mentioned above in the Allowed Type Hints (https://www.python.org/dev/peps/pep-0484/#acceptable-type-hints) section.

It is permissible to use string literals as _part _ of type hints. For example

class Tree:
    ...
    def leaves(self) -> List['Tree']:
        ...

A common use case for forward references is, for example, when the Django model is required for signatures. Each model is typically in a separate file, with methods that take arguments with types related to the other model. Due to the circular import solution in Python, it is often not possible to directly import all the required models.

# File models/a.py
from models.b import B
class A(Model):
    def foo(self, b: B):
        ...

# File models/b.py
from models.a import A
class B(Model):
    def bar(self, a: A):
        ...

# File main.py
from models.a import A
from models.b import B

Assuming that main is imported first, it fails with an ImportError on the line from models.a import A in models / b.py. This is because b.py was imported from models / a.py before class A was defined. The solution is to switch to importing only modules and reference the model in the \ _module \ _. \ _ Class \ _ format.

# File models/a.py
from models import b
class A(Model):
    def foo(self, b: 'b.B'):
        ...

# File models/b.py
from models import a
class B(Model):
    def bar(self, a: 'a.A'):
        ...

# File main.py
from models.a import A
from models.b import B

Union types

Since it is common to receive a limited set of expected types for a single argument, a special factory called ʻUnion` is provided. Here is an example.

from typing import Union

def handle_employees(e: Union[Employee, Sequence[Employee]]) -> None:
    if isinstance(e, Employee):
        e = [e]
    ...

The type represented by ʻUnion [T1, T2, ...] is the result of the ʻissubclass check for T1 and its arbitrary subtype, T2 and its arbitrary subtype, etc. It will be True.

A common use case for direct sum types is the optional type. By default, None is invalid for any type unless you specify the default value, None, when defining the function. Here is an example.

def handle_employee(e: Union[Employee, None]) -> None:
    ...

You can write ʻOptional [T1] as a simplified representation of ʻUnion [T1, None]. For example, the previous code is equivalent to the following code.

from typing import Optional

def handle_employee(e: Optional[Employee]) -> None:
    ...

Also, when the default value is None, it is automatically regarded as an optional type. For example

def handle_employee(e: Employee = None):
    ...

This is equivalent to the following code.

def handle_employee(e: Optional[Employee] = None) -> None:
    ...

ʻAny` type

ʻAny is a special type. All types are subtypes of ʻAny. The same is true for the built-in ʻobject`. However, for static checkers, these are completely different.

When the type of a value is ʻobject, the type checker will deny almost any operation on that value. And assigning that value to a variable of a more specialized type (or using that value as the return value) will result in a type error. On the other hand, when a value is of type ʻAny, the type checker now allows all operations on that value, assigning a value of type ʻAny` to a variable of more constrained type (or You can use that value as the return value).

Check version and platform

The type checker is required to be able to perform simple version and platform checks. Here is an example.

import sys

if sys.version_info[0] >= 3:
    # Python 3 specific definitions
else:
    # Python 2 specific definitions

if sys.platform == 'win32':
    # Windows specific definitions
else:
    # Posix specific definitions

Don't expect the checker to handle obfuscated code like " ". Join (reversed (sys.platform) ==" xunil ".

Default argument value

In stubs, it may be useful to be able to declare arguments that have defaults without specifying actual default values. Here is an example.

def foo(x: AnyStr, y: AnyStr = ...) -> AnyStr:
    ...

What should the default value be? Either " ", b" " or None fails to satisfy the type constraint (in fact None _ changes its type to ʻOptional [AnyStr]` _ ).

In these cases, you can specify the default value as a literal ellipsis. In other words, the example above represents exactly what you want to write.

Compatibility with other function annotation usage

There are many existing or potential function annotation use cases, which are incompatible with type hints. Such things can confuse static checkers. However, type hint annotation does nothing at run time (it does nothing but evaluate the annotation expression and keep the annotation in the __annotations__ attribute of the function object). This does not make the program malicious. The type checker will just print the wrong warning or error.

To avoid applying type hints to parts of your program, use some of the methods described below.

See later sections for more details.

Ultimately, it might be a good idea to switch the annotation-dependent interface to another mechanism (for example, a decorator) for maximum compatibility with offline checking. That said, there is no such pressure in Python 3.5. See also the lengthy discussion of Rejected Alternatives (https://www.python.org/dev/peps/pep-0484/#rejected-alternatives) below.

Type comment

This PEP does not add first-class syntax for explicitly marking a variable as a particular type. Comments in the following formats can be used to assist type inference in complex cases.

x = []   # type: List[Employee]
x, y, z = [], [], []  # type: List[int], List[int], List[str]
x, y, z = [], [], []  # type: (List[int], List[int], List[str])
x = [
   1,
   2,
]  # type: List[int]

The type comment must be on the last line of the statement containing the variable declaration. Type comments can also be placed immediately after the colon in with and for statements.

Examples of type comments for with and for statements.

with frobnicate() as foo:  # type: int
    # Here foo is an int
    ...

for x, y in points:  # type: float, float
    # Here x and y are floats
    ...

In stubs, it may be useful to declare the existence of a variable without specifying an initial value. This can be done using literal abbreviations.

from typing import IO

stream = ...  # type: IO[str]

For non-stub code, there are similar special cases.

from typing import IO

stream = None # type: IO[str]

The type checker should not consider this code as an error (even though the value None does not match the specified type) and should change the inferred type to ʻOptional [...]No (despite the rule doing this for the annotated argument and the default valueNone`). The assumption here is that other code will be responsible for assigning values of the appropriate type to the variable. And you can assume that everywhere you use that variable will have the specified type.

The # type: ignore comment must be placed on the line that references the error.

import http.client
errors = {
    'not_found': http.client.NOT_FOUND  # type: ignore
}

# type: ignore If a comment exists on a single line, disables all type checking from the line with the comment to the rest of the file.

If type hints are found to be useful in general, type variable syntax may be provided in future Python versions.

Cast

In some cases, the type checker may need different types of hints: The programmer may know that an expression is a more constrained type than the type checker can infer. For example

from typing import List, cast

def find_first_str(a: List[object]) -> str:
    index = next(i for i, x in enumerate(a) if isinstance(x, str))
    # We only get here if there's at least one string in a
    return cast(str, a[index])

Some type checkers may not be able to infer that the type of ʻa [index] is str, but infer it as ʻobject or ʻAny. However, the programmer knows that the string is correct (if the code gets there). cast (t, x)tells the type checker that you are confident that the type ofx is t`. At run time, the cast always returns the expression unchanged. It doesn't check its type, and it doesn't convert to that value.

Casts are different from type comments (see previous section). When using type comments, the type checker still needs to verify that the inferred type is consistent with its stated type. When using cast, the type checker should trust the programmer indiscriminately. Also, casts are used in expressions, while type comments are applied in assignments.

Stub file

Stub files are files that contain type hints and are only used by the type checker, not at runtime. There are various use cases for stub files.

Stub files use the same syntax as regular Python modules. There is one feature of the typing module that can only be used with stub files. It's the @overload decorator described in the sections that follow.

The type checker should only check the function signature in the stub file. It is recommended that the function body of the stub file be only literal ellipsis (...).

The type checker must be able to set the search path for the stub file. If the stub file is found, the type checker should not load the corresponding "real" module.

Although the stub file is a syntactically valid Python module, it uses the .pyi extension so that the stub file can be maintained in the same directory as the corresponding real module. Having a separate stub file enhances the impression of the expected behavior of a stub file that does nothing at runtime.

Other notes about stub files.

Function Overload

The @overload decorator allows you to write functions that support different combinations of argument types. This pattern is frequently used in embedded modules and embedded types. For example, the __getitem__ () method of type bytes would be written as:

from typing import overload

class bytes:
    ...
    @overload
    def __getitem__(self, i: int) -> int:
        ...
    @overload
    def __getitem__(self, s: slice) -> bytes:
        ...

This code is more rigorous than the ones achieved using direct sum types (which cannot represent the relationship between arguments and return types).

from typing import Union

class bytes:
    ...
    def __getitem__(self, a: Union[int, slice]) -> Union[int, bytes]:
        ...

Another example where @overload is useful is the type of the built-inmap ()function. The map () function takes different arguments depending on the type of callable object.

from typing import Callable, Iterable, Iterator, Tuple, TypeVar, overload

T1 = TypeVar('T1')
T2 = TypeVar('T2')
S = TypeVar('S')

@overload
def map(func: Callable[[T1], S], iter1: Iterable[T1]) -> Iterator[S]:
    ...
@overload
def map(func: Callable[[T1, T2], S],
        iter1: Iterable[T1], iter2: Iterable[T2]) -> Iterator[S]:
    ...
# ... and we could add more items to support more than two iterables

Also note that you can easily add elements to support map (None, ...).

@overload
def map(func: None, iter1: Iterable[T1]) -> Iterable[T1]:
    ...

@overload
def map(func: None,
        iter1: Iterable[T1],
        iter2: Iterable[T2]) -> Iterable[Tuple[T1, T2]]:
    ...

The @overload decorator can only be used with stub files. You could use this syntax to provide an implementation of multiple dispatch, but that implementation would require sys._getframe () to buy everyone's work. It is difficult to design and implement a more efficient multiple dispatch mechanism. That's why past attempts were abandoned and functools.singledispatch () was favored. (See PEP 443, especially in the "Alternative approaches" section.) Designing multiple dispatch to meet future requirements You might come up with, but I don't want that design to be constrained by the overloaded syntax defined for stub file type hints. For the time being, using the @overload decorator or calling ʻoverload ()directly will result in aRuntimeError`.

Instead of using the @overload decorator, the TypeVar type with constraints is usually used. For example, the definitions of concat1 and concat2 in this stub file are equivalent.

from typing import TypeVar

AnyStr = TypeVar('AnyStr', str, bytes)

def concat1(x: AnyStr, y: AnyStr) -> AnyStr:
    ...

@overload
def concat2(x: str, y: str) -> str:
    ...

@overload
def concat2(x: bytes, y: bytes) -> bytes:
    ...

Functions like map and bytes.__getitem__ mentioned above cannot be accurately represented using type variables. However, unlike @overload, type variables can also be used outside the stub file. @overload is a special condition that can only be used for stubs, so it is recommended to use it only when the type variable is not enough.

Another important difference between using type variables like ʻAnyStr and @overloadis that the former can also be used to define type parameter constraints for generic classes. For example, it defines a constraint for the generic class type parametertyping.IO (only ʻIO [str], ʻIO [bytes] and ʻIO [Any] are valid).

class IO(Generic[AnyStr]):
    ...

Stub file storage location and distribution

The easiest way to store and distribute stub files is to put them in the same directory with the Python module. This makes it easier for both programmers and tools to find. However, package maintainers are also free to not add type hints to their packages. Therefore, third-party stubs that can be installed from PyPI with the pip command are also supported. In this case, there are three issues to consider: naming, version, and installation path.

This PEP does not provide recommendations for naming schemes for third-party stub file packages. Findability will hopefully be based on the popularity of the package. For example, the Django package.

Third-party stubs must be versioned using the lowest version of a compatible source package. Example: FooPackage has versions 1.0, 1.1, 1.2, 1.3, 2.0, 2.1, 2.2. There are API changes in versions 1.1, 2.0 and 2.2. Stub file package maintainers are free to release stubs for all versions, but end users need at least 1.0, 1.1, 2.0 and 2.2 to do type checking on all versions. The user knows that the version with the closest _low or same_stub to the package version is compatible. In the previous example, the user would choose stub version 1.1 for FooPackage 1.3.

If the user decides to use the "latest" of the available source package, and if the stub is updated frequently, then in general, using the "latest" stub file will also work. be careful.

Third-party stub packages can use any location for stub storage. The type checker should use PYTHONPATH to search for stub files. The default fallback directory that is always checked is shared / typehints / python3.5 / (or 3.6, etc.). Since only one package is installed for a specific Python version for each environment, no other version settings are made under that directory. The author of the stub file package can use the following code in setup.py.

...
data_files=[
    (
        'shared/typehints/python{}.{}'.format(*sys.version_info[:2]),
        pathlib.Path(SRC_PATH).glob('**/*.pyi'),
    ),
],
...

Typeshed repository

There is a shared repository called [typeshed] with a collection of useful stubs. Please note that stubs for some packages cannot be included here without the explicit consent of the package owner. Further policies regarding the stubs collected here will be determined after discussion in python-dev and will be reported in the README of the typeshed repository.

Exception

We do not suggest a syntax that explicitly indicates the exception that will occur. So far, the only known use cases for this feature are for documentation. And in that case, it is recommended to put this information in the docstring.

typing module

A unified namespace is needed to extend the use of static typing to older versions as well as Python 3.5. To this end, we will introduce a new module called typing in the standard library.

This module contains the basic building blocks for building types (eg ʻAny), types that represent generic variants of built-in collections (eg List), and types that represent the abstract base classes of generic collections. Define (eg Sequence`) and include other useful definitions.

Basic components:

Generic version of the built-in collection:

Note: Dict, List, Set and FrozenSet are mainly useful for annotating the return value. The abstract collection type defined below is more appropriate for the argument. For example, Mapping, Sequence or ʻAbstractSet`.

Generic version of the container abstract base class (and anything other than a container):

Several one-time types are defined to inspect a single special method (similar to Hashable and Sized):

Convenient definition:

Types available in the typing.io submodule:

Types available in the typing.re submodule:

Rejected alternative

Various counterarguments were made during the discussion of this early draft of the PEP, and a number of alternatives were proposed. We'll discuss them here and explain why they were rejected.

I have picked up some of the major counterarguments.

Which parentheses do you use for generic type parameters?

Most people are familiar with angle brackets (eg List <int>) that represent generic type parameters in languages such as C ++, Java, C # and Swift. The problem is that it's really hard to parse angle brackets. This is especially true for naive parsers like Python. In most languages, it is common to deal with the ambiguity by allowing only angle brackets in special syntactic positions and not allowing arbitrary expressions. (It also uses powerful parsing technology that allows you to backtrack any part of the chord.)

But in Python I want the type expression and the other expressions to be (syntactically) the same. This allows you to assign to a variable, for example to create a type alias. Consider this simple type expression.

List<int>

From a Python parser's point of view, this expression starts with the same four tokens (NAME, LESS, NAME, GREATER) as the chained comparison.

a < b > c  # I.e., (a < b) and (b > c)

You can also create an example that can be parsed in both ways.

a < b > [ c ]

Assuming the language has angle brackets, this code can be interpreted in one of two ways:

(a<b>)[c]      # I.e., (a<b>).__getitem__(c)
a < b > ([c])  # I.e., (a < b) and (b > [c])

Certainly rules can be considered to disambiguate such cases. But for many users, the rules are arbitrary and complex. This also requires dramatic changes to the CPython parser (and all other parsers for Python). It should be noted that Python's current parser is intentionally "stupid". This means that a concise grammar is easy for the user to guess.

For all these reasons, square brackets (eg List [int]) were chosen (and have always been a favorite) for the syntax of generics type parameters. This can be implemented by defining the __getitem__ () method in the metaclass and does not require any new syntax. This option works with all recent Python versions (starting with Python 2.2). And Python isn't the only one choosing this syntax. Scala also uses square brackets for generic classes.

What about the existing uses of annotations?

One of the arguments points out that PEP 3107 explicitly supports the use of arbitrary expressions in function annotations. The new proposal is believed to be incompatible with PEP 3107.

The answer to this is that, first of all, the current proposal does not cause a direct incompatibility. Therefore, programs that use annotations in Python 3.4 will work fine in Python 3.5 without any disadvantages.

Ultimately, we expect type hints to be the sole use of annotations. However, doing so requires additional discussion and decommissioning after publishing the early typing module with Python 3.5. The current PEP will be in interim status until Python 3.6 is released (see [PEP 411](see https://www.python.org/dev/peps/pep-0411)). The fastest possible plan is to introduce an initial deprecation period for annotations of untyped hints in Python 3.6, a complete deprecation period in 3.7, and allow only type hint declarations for annotation purposes in 3.8. You need to give the authors of packages that use annotations enough time to devise other approaches. Even if type hints are rapidly successful.

Another possible result is that the type hint ultimately becomes the default meaning of the annotation, but always retains the option to disable the type hint. To this end, the current proposal defines a decorator called @no_type_check that overrides the default interpretation of annotations as type hints in a class or function. We also define a meta-decorator called @no_type_check_decorator, which decorates the decorator (!). Annotations of any function or class decorated with the decorator created by this meta-decorator will be ignored by the type checker.

There is also a comment # type: ignore. And the static type checker should support a configuration option that disables type checking for the selected package.

Despite all these options, there are endless suggestions for allowing type hints and other annotation formats to co-exist for individual arguments. One suggestion was that if the annotation for an argument was a dictionary literal, each key would represent a different form of annotation, and if that key was 'type', it would be used for a type hint. The problem with this idea and its variants is that the notation is very noisy and hard to read. Also, in most cases of existing libraries that use annotations, the need to combine type hints with those libraries will be small. Therefore, a concise technique that selectively disables type hints seems sufficient.

Forward reference problem

If the type hint needs to include a forward reference, the current suggestion is clearly a workaround. Python needs to have all the names defined when they are used. Aside from circular imports, this is rarely an issue: "used" here means "found at runtime". Most "forward" references are fine as long as you ensure that the name is defined before the function that uses that name is called.

The problem with type hints is that the annotation is evaluated when the function is defined (through PEP 3107, as well as the default arguments). Any name used in the annotation must be predefined when the function is defined. A common scenario is a class definition that has a method that requires a reference to the class itself to annotate that class. (More generally, it also happens in mutual recursion classes.) This is natural for container types. For example

class Node:
    """Binary tree node."""

    def __init__(self, left: Node, right: Node):
        self.left = left
        self.right = right

This doesn't work as I wrote before. That's due to the Python property that class names are only defined when the entire body of the class is executed. Our solution is to allow string literals to be used in annotations (which isn't particularly sophisticated, but it does a good job). However, in most cases you don't need to use this. Most use of type hints assume that they refer to a built-in type or a type defined in another module.

One counter-proposal attempted to change the semantics of a type hint so that it was never evaluated at runtime (after all, type checking is done offline, so the type hint needs to be evaluated at runtime. I wonder if there is). This, of course, conflicts with backwards compatibility issues. The Python interpreter doesn't really know if a particular annotation can be a type hint or something else.

A compromise is possible, such as allowing the __future__ import to convert all _ annotations of a given module to string literals as follows:

from __future__ import annotations

class ImSet:
    def add(self, a: ImSet) -> List[ImSet]:
        ...

assert ImSet.add.__annotations__ == {'a': 'ImSet', 'return': 'List[ImSet]'}

Such a __future__ import statement may be proposed in another PEP.

Double colon

Several people have been creative and challenged to invent a solution to this problem. For example, it has been suggested to use a double colon (::) for type hints to solve two problems at the same time: to disambiguate between other annotations and type hints, and at runtime. It's about changing the semantics so that evaluation doesn't happen. However, there were some things that were inappropriate for this idea.

Other forms of new syntax

Several other alternative syntaxes have been proposed. For example, the introduction of the reserved word where [roberge] and the requirements clause inspired by Cobra. However, it has the same problem as a double colon that doesn't work in earlier versions of Python 3. The same applies to new __future__ imports.

Other backward compatibility rules

Includes proposed ideas.

Or it is suggested to simply wait for the next release. But what problem does it solve? It's just a postponement.

PEP Development Process

A raw draft of this PEP can be found on [github]. There is also an issue tracker [issues], where many technical discussions take place.

The draft on GitHub is updated on a regular basis. The official PEPS repository [peps] is (usually) updated only when a new draft is posted to python-dev.

Acknowledgments

This document could not have been completed without the valuable feedback, encouragement and advice of Jim Baker, Jeremy Siek, Michael Matson Vitousek, Andrey Vlasovskikh, Radomir Dopieralski, Peter Ludemann and BDFL Commissioner Mark Shannon.

It is influenced by the libraries, frameworks and existing languages mentioned in PEP 482. Thanks to the authors in alphabetical order: Stefan Behnel, William Edwards, Greg Ewing, Larry Hastings, Anders Hejlsberg, Alok Menghrajani, Travis E. Oliphant, Joe Pamer, Raoul-Gabriel Urma and Julien Verlaguet

References

- -
[mypy] http://mypy-lang.org
[gvr-artima] (1,2) http://www.artima.com/weblogs/viewpost.jsp?thread=85551
[wiki-variance] http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)
[typeshed] https://github.com/JukkaL/typeshed/
[pyflakes] https://github.com/pyflakes/pyflakes/
[pylint] http://www.pylint.org
[roberge] http://aroberge.blogspot.com/2015/01/type-hinting-in-python-focus-on.html
[github] https://github.com/ambv/typehinting
[issues] https://github.com/ambv/typehinting/issues
[peps] https://hg.python.org/peps/file/tip/pep-0484.txt

Copyright

This document is in the public domain.

Source: https://hg.python.org/peps/file/tip/pep-0484.txt

Recommended Posts

[Translation] PEP 0484 --Type Hints
Try type hints
[Translation] Python static type, amazing mypy!
Practice! !! Introduction to Python (Type Hints)
I read PEP 613 (Explicit Type Aliases)
Increase source visibility with type hints
Japanese translation: PEP 20 --The Zen of Python