Updated types in documentation to use PEP 604 syntax.

This commit is contained in:
Eric Traut 2022-10-24 20:06:21 -07:00
parent b8f6c1c275
commit d5d27505ef
7 changed files with 122 additions and 122 deletions

View File

@ -6,9 +6,9 @@ Some behaviors of pyright can be controlled through the use of comments within t
Versions of Python prior to 3.6 did not support type annotations for variables. Pyright honors type annotations found within a comment at the end of the same line where a variable is assigned.
```python
offsets = [] # type: List[int]
offsets = [] # type: list[int]
self._target = 3 # type: Union[int, str]
self._target = 3 # type: int | str
```
Future versions of Python will likely deprecate support for type annotation comments. The “reportTypeCommentUsage” diagnostic will report usage of such comments so they can be replaced with inline type annotations.

View File

@ -44,11 +44,11 @@ Relative paths specified within the config file are relative to the config file
## Type Check Diagnostics Settings
The following settings control pyrights diagnostic output (warnings or errors). Unless otherwise specified, each diagnostic setting can specify a boolean value (`false` indicating that no error is generated and `true` indicating that an error is generated). Alternatively, a string value of `"none"`, `"warning"`, `"information"`, or `"error"` can be used to specify the diagnostic level.
**strictListInference** [boolean]: When inferring the type of a list, use strict type assumptions. For example, the expression `[1, 'a', 3.4]` could be inferred to be of type `List[Any]` or `List[Union[int, str, float]]`. If this setting is true, it will use the latter (stricter) type. The default value for this setting is 'false'.
**strictListInference** [boolean]: When inferring the type of a list, use strict type assumptions. For example, the expression `[1, 'a', 3.4]` could be inferred to be of type `list[Any]` or `list[int | str | float]`. If this setting is true, it will use the latter (stricter) type. The default value for this setting is 'false'.
**strictDictionaryInference** [boolean]: When inferring the type of a dictionarys keys and values, use strict type assumptions. For example, the expression `{'a': 1, 'b': 'a'}` could be inferred to be of type `Dict[str, Any]` or `Dict[str, Union[int, str]]`. If this setting is true, it will use the latter (stricter) type. The default value for this setting is 'false'.
**strictDictionaryInference** [boolean]: When inferring the type of a dictionarys keys and values, use strict type assumptions. For example, the expression `{'a': 1, 'b': 'a'}` could be inferred to be of type `dict[str, Any]` or `dict[str, int | str]`. If this setting is true, it will use the latter (stricter) type. The default value for this setting is 'false'.
**strictSetInference** [boolean]: When inferring the type of a set, use strict type assumptions. For example, the expression `{1, 'a', 3.4}` could be inferred to be of type `Set[Any]` or `Set[Union[int, str, float]]`. If this setting is true, it will use the latter (stricter) type. The default value for this setting is 'false'.
**strictSetInference** [boolean]: When inferring the type of a set, use strict type assumptions. For example, the expression `{1, 'a', 3.4}` could be inferred to be of type `set[Any]` or `set[int | str | float]`. If this setting is true, it will use the latter (stricter) type. The default value for this setting is 'false'.
**strictParameterNoneValue** [boolean]: PEP 484 indicates that when a function parameter is assigned a default value of None, its type should implicitly be Optional even if the explicit type is not. When enabled, this rule requires that parameter type annotations use Optional explicitly in this case. The default value for this setting is 'true'.

View File

@ -43,7 +43,7 @@ Pyright attempts to infer the types of global (module-level) variables, class va
Pyright supports type narrowing to track assumptions that apply within certain code flow paths. For example, consider the following code:
```python
def func(a: Optional[Union[str, List[str]]):
def func(a: str | list[str] | None):
if isinstance(a, str):
log(a)
elif isinstance(a, list):
@ -52,22 +52,22 @@ def func(a: Optional[Union[str, List[str]]):
log(a)
```
In this example, the type evaluator knows that parameter a is either None, str, or List[str]. Within the first `if` clause, a is constrained to be a str. Within the `elif` clause, it is constrained to be a List[str], and within the `else` clause, it has to be None (by process of elimination). The type checker would therefore flag the final line as an error if the log method could not accept None as a parameter.
In this example, the type evaluator knows that parameter a is either None, str, or list[str]. Within the first `if` clause, a is constrained to be a str. Within the `elif` clause, it is constrained to be a list[str], and within the `else` clause, it has to be None (by process of elimination). The type checker would therefore flag the final line as an error if the log method could not accept None as a parameter.
Narrowing is also applied when values are assigned to a variable.
```python
def func(b: Optional[Union[str, List[str]]]):
def func(b: str | list[str] | None):
# The declared type of “a” is a union of three types
# (str, List[str] and None).
a: Optional[Union[str, List[str]]] = b
reveal_type(a) # Type is `Optional[Union[str, List[str]]]`
# (str, list[str] and None).
a: str | list[str] | None = b
reveal_type(a) # Type is `str | list[str] | None`
a = "hi"
reveal_type(a) # Type is `str`
a = ["a", "b", "c"]
reveal_type(a) # Type is `List[str]`
reveal_type(a) # Type is `list[str]`
a = None
reveal_type(a) # Type is `None`
@ -76,7 +76,7 @@ def func(b: Optional[Union[str, List[str]]]):
If the type narrowing logic exhausts all possible subtypes, it can be assumed that a code path will never be taken. For example, consider the following:
```python
def func(a: Union[Foo, Bar]):
def func(a: Foo | Bar):
if isinstance(a, Foo):
# “a” must be type Foo
a.do_something_1()
@ -103,7 +103,7 @@ class Bar:
def do_something_2(self):
pass
def func(a: Union[Foo, Bar]):
def func(a: Foo | Bar):
if a.kind == "Foo":
a.do_something_1()
else:

View File

@ -46,7 +46,7 @@ b: int = 3.4 # Error
This example introduces the notion of a _Union type_, which specifies that a value can be one of several distinct types.
```
c: Union[int, float] = 3.4
c: int | float = 3.4
c = 5
c = a
c = b
@ -57,53 +57,53 @@ c = "" # Error
This example introduces the _Optional_ type, which is the same as a union with `None`.
```
d: Optional[int] = 4
d: int | None = 4
d = b
d = None
d = "" # Error
```
Those examples are straightforward. Lets look at one that is less intuitive. In this example, the declared type of `f` is `List[Optional[int]]`. A value of type `List[int]` is being assigned to `f`. As we saw above, `int` is assignable to `Optional[int]`. You might therefore assume that `List[int]` is assignable to `List[Optional[int]]`, but this is an incorrect assumption. To understand why, we need to understand generic types and type arguments.
Those examples are straightforward. Lets look at one that is less intuitive. In this example, the declared type of `f` is `list[int | None]`. A value of type `list[int]` is being assigned to `f`. As we saw above, `int` is assignable to `int | None`. You might therefore assume that `list[int]` is assignable to `list[int | None]`, but this is an incorrect assumption. To understand why, we need to understand generic types and type arguments.
```
e: List[int] = [3, 4]
f: List[Optional[int]] = e # Error
e: list[int] = [3, 4]
f: list[int | None] = e # Error
```
### Generic Types
A _generic type_ is a class that is able to handle different types of inputs. For example, the `List` class is generic because it is able to operate on different types of elements. The type `List` by itself does not specify what is contained within the list. Its element type must be specified as a _type argument_ using the indexing (square bracket) syntax in Python. For example, `List[int]` denotes a list that contains only `int` elements whereas `List[Union[int, float]]` denotes a list that contains a mixture of int and float elements.
A _generic type_ is a class that is able to handle different types of inputs. For example, the `list` class is generic because it is able to operate on different types of elements. The type `list` by itself does not specify what is contained within the list. Its element type must be specified as a _type argument_ using the indexing (square bracket) syntax in Python. For example, lList[int]` denotes a list that contains only `int` elements whereas `list[int | float]` denotes a list that contains a mixture of int and float elements.
We noted above that `List[int]` is not assignable to `List[Optional[int]]`. Why is this the case? Consider the following example.
We noted above that `list[int]` is not assignable to `list[int | None]`. Why is this the case? Consider the following example.
```
my_list_1: List[int] = [1, 2, 3]
my_list_2: List[Optional[int]] = my_list_1 # Error
my_list_1: list[int] = [1, 2, 3]
my_list_2: list[int | None] = my_list_1 # Error
my_list_2.append(None)
for elem in my_list_1:
print(elem + 1) # Runtime exception
```
The code is appending the value `None` to the list `my_list_2`, but `my_list_2` refers to the same object as `my_list_1`, which has a declared type of `List[int]`. The code has violated the type of `my_list_1` because it no longer contains only `int` elements. This broken assumption results in a runtime exception. The type checker detects this broken assumption when the code attempts to assign `my_list_1` to `my_list_2`.
The code is appending the value `None` to the list `my_list_2`, but `my_list_2` refers to the same object as `my_list_1`, which has a declared type of `list[int]`. The code has violated the type of `my_list_1` because it no longer contains only `int` elements. This broken assumption results in a runtime exception. The type checker detects this broken assumption when the code attempts to assign `my_list_1` to `my_list_2`.
`List` is an example of a _mutable container type_. It is mutable in that code is allowed to modify its contents — for example, add or remove items. The type parameters for mutable container types are typically marked as _invariant_, which means that an exact type match is enforced. This is why the type checker reports an error when attempting to assign a `List[int]` to a variable of type `List[Optional[int]]`.
`list` is an example of a _mutable container type_. It is mutable in that code is allowed to modify its contents — for example, add or remove items. The type parameters for mutable container types are typically marked as _invariant_, which means that an exact type match is enforced. This is why the type checker reports an error when attempting to assign a `list[int]` to a variable of type `list[int | None]`.
Most mutable container types also have immutable counterparts.
| Mutable Type | Immutable Type |
| ----------------- | -------------- |
| List | Sequence |
| Dict | Mapping |
| Set | AbstractSet |
| n/a | Tuple |
| list | Sequence |
| dict | Mapping |
| set | AbstractSet |
| n/a | tuple |
Switching from a mutable container type to a corresponding immutable container type is often an effective way to resolve type errors relating to assignability. Lets modify the example above by changing the type annotation for `my_list_2`.
```
my_list_1: List[int] = [1, 2, 3]
my_list_2: Sequence[Optional[int]] = my_list_1 # No longer an error
my_list_1: list[int] = [1, 2, 3]
my_list_2: Sequence[int | None] = my_list_1 # No longer an error
```
The type error on the second line has now gone away.
@ -119,8 +119,8 @@ Pyright uses a technique called “type narrowing” to track the type of an exp
val_str: str = "hi"
val_int: int = 3
def func(val: Union[float, str, complex], test: bool):
reveal_type(val) # Union[int, str, complex]
def func(val: float | str | complex, test: bool):
reveal_type(val) # int | str | complex
val = val_int # Type is narrowed to int
reveal_type(val) # int
@ -129,7 +129,7 @@ def func(val: Union[float, str, complex], test: bool):
val = val_str # Type is narrowed to str
reveal_type(val) # str
reveal_type(val) # Union[int, str]
reveal_type(val) # int | str
if isinstance(val, int):
reveal_type(val) # int
@ -139,11 +139,11 @@ def func(val: Union[float, str, complex], test: bool):
print(val)
```
At the start of this function, the type checker knows nothing about `val` other than that its declared type is `Union[float, str, complex]`. Then it is assigned a value that has a known type of `int`. This is a legal assignment because `int` is considered a subclass of `float`. At the point in the code immediately after the assignment, the type checker knows that the type of `val` is an `int`. This is a “narrower” (more specific) type than `Union[float, str, complex]`. Type narrowing is applied when ever a symbol is assigned a new value.
At the start of this function, the type checker knows nothing about `val` other than that its declared type is `float | str | complex`. Then it is assigned a value that has a known type of `int`. This is a legal assignment because `int` is considered a subclass of `float`. At the point in the code immediately after the assignment, the type checker knows that the type of `val` is an `int`. This is a “narrower” (more specific) type than `float | str | complex`. Type narrowing is applied when ever a symbol is assigned a new value.
Another assignment occurs several lines further down, this time within a conditional block. The symbol `val` is assigned a value known to be of type `str`, so the narrowed type of `val` is now `str`. Once the code flow of the conditional block merges with the main body of the function, the narrowed type of `val` becomes `Union[int, str]` because the type checker cannot statically predict whether the conditional block will be executed at runtime.
Another assignment occurs several lines further down, this time within a conditional block. The symbol `val` is assigned a value known to be of type `str`, so the narrowed type of `val` is now `str`. Once the code flow of the conditional block merges with the main body of the function, the narrowed type of `val` becomes `int | str` because the type checker cannot statically predict whether the conditional block will be executed at runtime.
Another way that types can be narrowed is through the use of conditional code flow statements like `if`, `while`, and `assert`. Type narrowing applies to the block of code that is “guarded” by that condition, so type narrowing in this context is sometimes referred to as a “type guard”. For example, if you see the conditional statement `if x is None:`, the code within that `if` statement can assume that `x` contains `None`. Within the code sample above, we see an example of a type guard involving a call to `isinstance`. The type checker knows that `isinstance(val, int)` will return True only in the case where `val` contains a value of type `int`, not type `str`. So the code within the `if` block can assume that `val` contains a value of type `int`, and the code within the `else` block can assume that `val` contains a value of type `str`. This demonstrates how a type (in this case `Union[int, str]`) can be narrowed in both a positive (`if`) and negative (`else`) test.
Another way that types can be narrowed is through the use of conditional code flow statements like `if`, `while`, and `assert`. Type narrowing applies to the block of code that is “guarded” by that condition, so type narrowing in this context is sometimes referred to as a “type guard”. For example, if you see the conditional statement `if x is None:`, the code within that `if` statement can assume that `x` contains `None`. Within the code sample above, we see an example of a type guard involving a call to `isinstance`. The type checker knows that `isinstance(val, int)` will return True only in the case where `val` contains a value of type `int`, not type `str`. So the code within the `if` block can assume that `val` contains a value of type `int`, and the code within the `else` block can assume that `val` contains a value of type `str`. This demonstrates how a type (in this case `int | str`) can be narrowed in both a positive (`if`) and negative (`else`) test.
The following expression forms support type narrowing:
@ -195,17 +195,17 @@ Some type guards are able to narrow in both the positive and negative cases. Pos
class Foo: pass
class Bar: pass
def func1(val: Union[Foo, Bar]):
def func1(val: Foo | Bar):
if isinstance(val, Bar):
reveal_type(val) # Bar
else:
reveal_type(val) # Foo
def func2(val: Optional[int]):
def func2(val: int | None):
if val:
reveal_type(val) # int
else:
reveal_type(val) # Optional[int]
reveal_type(val) # int | None
```
In the example of `func1`, the type was narrowed in both the positive and negative cases. In the example of `func2`, the type was narrowed only the positive case because the type of `val` might be either `int` (specifically, a value of 0) or `None` in the negative case.
@ -237,7 +237,7 @@ def func2(val: str | bytes):
```
```python
def func3(x: List[str | None]) -> str:
def func3(x: list[str | None]) -> str:
is_str = x[0] is not None
if is_str:
@ -321,11 +321,11 @@ The same applies to `Any` when it is used as a type argument.
```python
b: Iterable[Any] = [1, 2, 3]
reveal_type(b) # List[Any]
reveal_type(b) # list[Any]
c: Iterable[str] = [""]
b = c
reveal_type(b) # List[Any]
reveal_type(b) # list[Any]
```
### Constrained Type Variables and Conditional Types

View File

@ -31,7 +31,7 @@ def func1(p1: float, p2: str, p3, **p4) -> None:
Symbol | Symbol Category | Scope | Declared Type
----------|-----------------|-----------|----------------------------------------------------
func1 | Function | Module | (float, str, Any, Dict[str, Any]) -> None
func1 | Function | Module | (float, str, Any, dict[str, Any]) -> None
p1 | Parameter | func1 | float
p2 | Parameter | func1 | str
p3 | Parameter | func1 | <none>
@ -60,10 +60,10 @@ The simplest form of type inference is one that involves a single assignment to
```python
var1 = 3 # Inferred type is int
var2 = "hi" # Inferred type is str
var3 = list() # Inferred type is List[Unknown]
var4 = [3, 4] # Inferred type is List[int]
var3 = list() # Inferred type is list[Unknown]
var4 = [3, 4] # Inferred type is list[int]
for var5 in [3, 4]: ... # Inferred type is int
var6 = [p for p in [1, 2, 3]] # Inferred type is List[int]
var6 = [p for p in [1, 2, 3]] # Inferred type is list[int]
```
### Multi-Assignment Type Inference
@ -71,7 +71,7 @@ var6 = [p for p in [1, 2, 3]] # Inferred type is List[int]
When a symbol is assigned values in multiple places within the code, those values may have different types. The inferred type of the variable is the union of all such types.
```python
# In this example, symbol var1 has an inferred type of Union[str, int].
# In this example, symbol var1 has an inferred type of `str | int`]`.
class Foo:
def __init__(self):
self.var1 = ""
@ -79,7 +79,7 @@ class Foo:
def do_something(self, val: int):
self.var1 = val
# In this example, symbol var2 has an inferred type of Optional[Foo].
# In this example, symbol var2 has an inferred type of `Foo | None`.
if __debug__:
var2 = None
else:
@ -88,13 +88,13 @@ else:
### Ambiguous Type Inference
In some cases, an expressions type is ambiguous. For example, what is the type of the expression `[]`? Is it `List[None]`, `List[int]`, `List[Any]`, `Sequence[Any]`, `Iterable[Any]`? These ambiguities can lead to unintended type violations. Pyright uses several techniques for reducing these ambiguities based on contextual information. In the absence of contextual information, heuristics are used.
In some cases, an expressions type is ambiguous. For example, what is the type of the expression `[]`? Is it `list[None]`, `list[int]`, `list[Any]`, `Sequence[Any]`, `Iterable[Any]`? These ambiguities can lead to unintended type violations. Pyright uses several techniques for reducing these ambiguities based on contextual information. In the absence of contextual information, heuristics are used.
### Bidirectional Type Inference (Expected Types)
One powerful technique Pyright uses to eliminate type inference ambiguities is _bidirectional inference_. This technique makes use of an “expected type”.
As we saw above, the type of the expression `[]` is ambiguous, but if this expression is passed as an argument to a function, and the corresponding parameter is annotated with the type `List[int]`, Pyright can now assume that the type of `[]` in this context must be `List[int]`. Ambiguity eliminated!
As we saw above, the type of the expression `[]` is ambiguous, but if this expression is passed as an argument to a function, and the corresponding parameter is annotated with the type `list[int]`, Pyright can now assume that the type of `[]` in this context must be `list[int]`. Ambiguity eliminated!
This technique is called “bidirectional inference” because type inference for an assignment normally proceeds by first determining the type of the right-hand side (RHS) of the assignment, which then informs the type of the left-hand side (LHS) of the assignment. With bidirectional inference, if the LHS of an assignment has a declared type, it can influence the inferred type of the RHS.
@ -102,14 +102,14 @@ Lets look at a few examples:
```python
var1 = [] # Type of RHS is ambiguous
var2: List[int] = [] # Type of LHS now makes type of RHS unambiguous
var3 = [4] # Type is assumed to be List[int]
var4: List[float] = [4] # Type of RHS is now List[float]
var5 = (3,) # Type is assumed to be Tuple[Literal[3]]
var6: Tuple[float, ...] = (3,) # Type of RHS is now Tuple[float, ...]
var2: list[int] = [] # Type of LHS now makes type of RHS unambiguous
var3 = [4] # Type is assumed to be list[int]
var4: list[float] = [4] # Type of RHS is now list[float]
var5 = (3,) # Type is assumed to be tuple[Literal[3]]
var6: tuple[float, ...] = (3,) # Type of RHS is now tuple[float, ...]
```
### Empty List and Dictionary Type Inference
### Empty list and Dictionary Type Inference
It is common to initialize a local variable or instance variable to an empty list (`[]`) or empty dictionary (`{}`) on one code path but initialize it to a non-empty list or dictionary on other code paths. In such cases, Pyright will infer the type based on the non-empty list or dictionary and suppress errors about a “partially unknown type”.
@ -131,7 +131,7 @@ As with variable assignments, function return types can be inferred from the `re
# This function has two explicit return statements and one implicit
# return (at the end). It does not have a declared return type,
# so Pyright infers its return type based on the return expressions.
# In this case, the inferred return type is Union[str, bool, None].
# In this case, the inferred return type is `str | bool | None`.
def func1(val: int):
if val > 3:
@ -168,7 +168,7 @@ It is common for input parameters to be unannotated. This can make it difficult
# The return type of this function cannot be fully inferred based
# on the information provided because the types of parameters
# a and b are unknown. In this case, the inferred return
# type is Union[Unknown, None].
# type is `Unknown | None`.
def func1(a, b, c):
if c:
@ -183,11 +183,11 @@ In cases where all parameters are unannotated, Pyright uses a technique called _
```python
def func2(p_int: int, p_str: str, p_flt: float):
# The type of var1 is inferred to be Union[int, None] based
# The type of var1 is inferred to be `int | None` based
# on call-site return type inference.
var1 = func1(p_int, p_int, p_int)
# The type of var2 is inferred to be Union[str, float, None].
# The type of var2 is inferred to be `str | float | None`.
var2 = func1(p_str, p_flt, p_int)
```
@ -242,10 +242,10 @@ def func2(mode: Literal["r", "w", "rw"]) -> None:
When Pyright is performing type inference, it generally does not infer literal types. Consider the following example:
```python
# If Pyright inferred the type of var1 to be List[Literal[4]],
# If Pyright inferred the type of var1 to be list[Literal[4]],
# any attempt to append a value other than 4 to this list would
# generate an error. Pyright therefore infers the broader
# type List[int].
# type list[int].
var1 = [4]
```
@ -254,42 +254,42 @@ var1 = [4]
When inferring the type of a tuple expression (in the absence of bidirectional inference hints), Pyright assumes that the tuple has a fixed length, and each tuple element is typed as specifically as possible.
```python
# The inferred type is Tuple[Literal[1], Literal["a"], Literal[True]]
# The inferred type is tuple[Literal[1], Literal["a"], Literal[True]]
var1 = (1, "a", True)
def func1(a: int):
# The inferred type is Tuple[int, int]
# The inferred type is tuple[int, int]
var2 = (a, a)
# If you want the type to be Tuple[int, ...]
# If you want the type to be tuple[int, ...]
# (i.e. a homogenous tuple of indeterminate length),
# use a type annotation.
var3: Tuple[int, ...] = (a, a)
var3: tuple[int, ...] = (a, a)
```
### List Expressions
When inferring the type of a list expression (in the absence of bidirectional inference hints), Pyright uses the following heuristics:
1. If the list is empty (`[]`), assume `List[Unknown]` (unless a known list type is assigned to the same variable along another code path).
2. If the list contains at least one element and all elements are the same type T, infer the type `List[T]`.
1. If the list is empty (`[]`), assume `list[Unknown]` (unless a known list type is assigned to the same variable along another code path).
2. If the list contains at least one element and all elements are the same type T, infer the type `list[T]`.
3. If the list contains multiple elements that are of different types, the behavior depends on the `strictListInference` configuration setting. By default this setting is off.
* If `strictListInference` is off, infer `List[Unknown]`.
* Otherwise use the union of all element types and infer `List[Union[(elements)]]`.
* If `strictListInference` is off, infer `list[Unknown]`.
* Otherwise use the union of all element types and infer `list[Union[(elements)]]`.
These heuristics can be overridden through the use of bidirectional inference hints (e.g. by providing a declared type for the target of the assignment expression).
```python
var1 = [] # Infer List[Unknown]
var1 = [] # Infer list[Unknown]
var2 = [1, 2] # Infer List[int]
var2 = [1, 2] # Infer list[int]
# Type depends on strictListInference config setting
var3 = [1, 3.4] # Infer List[Unknown] (off)
var3 = [1, 3.4] # Infer List[Union[int, float]] (on)
var3 = [1, 3.4] # Infer list[Unknown] (off)
var3 = [1, 3.4] # Infer list[int | float] (on)
var4: List[float] = [1, 3.4] # Infer List[float]
var4: list[float] = [1, 3.4] # Infer list[float]
```
@ -297,22 +297,22 @@ var4: List[float] = [1, 3.4] # Infer List[float]
When inferring the type of a set expression (in the absence of bidirectional inference hints), Pyright uses the following heuristics:
1. If the set contains at least one element and all elements are the same type T, infer the type `Set[T]`.
1. If the set contains at least one element and all elements are the same type T, infer the type `set[T]`.
2. If the set contains multiple elements that are of different types, the behavior depends on the `strictSetInference` configuration setting. By default this setting is off.
* If `strictSetInference` is off, infer `Set[Unknown]`.
* Otherwise use the union of all element types and infer `Set[Union[(elements)]]`.
* If `strictSetInference` is off, infer `set[Unknown]`.
* Otherwise use the union of all element types and infer `set[Union[(elements)]]`.
These heuristics can be overridden through the use of bidirectional inference hints (e.g. by providing a declared type for the target of the assignment expression).
```python
var1 = {1, 2} # Infer Set[int]
var1 = {1, 2} # Infer set[int]
# Type depends on strictSetInference config setting
var2 = {1, 3.4} # Infer Set[Unknown] (off)
var2 = {1, 3.4} # Infer Set[Union[int, float]] (on)
var2 = {1, 3.4} # Infer set[Unknown] (off)
var2 = {1, 3.4} # Infer set[int | float] (on)
var3: Set[float] = {1, 3.4} # Infer Set[float]
var3: set[float] = {1, 3.4} # Infer set[float]
```
@ -320,24 +320,24 @@ var3: Set[float] = {1, 3.4} # Infer Set[float]
When inferring the type of a dictionary expression (in the absence of bidirectional inference hints), Pyright uses the following heuristics:
1. If the dict is empty (`{}`), assume `Dict[Unknown, Unknown]`.
2. If the dict contains at least one element and all keys are the same type K and all values are the same type V, infer the type `Dict[K, V]`.
1. If the dict is empty (`{}`), assume `dict[Unknown, Unknown]`.
2. If the dict contains at least one element and all keys are the same type K and all values are the same type V, infer the type `dict[K, V]`.
3. If the dict contains multiple elements where the keys or values differ in type, the behavior depends on the `strictDictionaryInference` configuration setting. By default this setting is off.
* If `strictDictionaryInference` is off, infer `Dict[Unknown, Unknown]`.
* Otherwise use the union of all key and value types `Dict[Union[(keys), Union[(values)]]]`.
* If `strictDictionaryInference` is off, infer `dict[Unknown, Unknown]`.
* Otherwise use the union of all key and value types `dict[Union[(keys), Union[(values)]]]`.
```python
var1 = {} # Infer Dict[Unknown, Unknown]
var1 = {} # Infer dict[Unknown, Unknown]
var2 = {1: ""} # Infer Dict[int, str]
var2 = {1: ""} # Infer dict[int, str]
# Type depends on strictDictionaryInference config setting
var3 = {"a": 3, "b": 3.4} # Infer Dict[str, Unknown] (off)
var3 = {"a": 3, "b": 3.4} # Infer Dict[str, Union[int, float]] (on)
var3 = {"a": 3, "b": 3.4} # Infer dict[str, Unknown] (off)
var3 = {"a": 3, "b": 3.4} # Infer dict[str, int | float] (on)
var4: Dict[str, float] = {"a": 3, "b": 3.4}
var4: dict[str, float] = {"a": 3, "b": 3.4}
```
### Lambdas
@ -349,7 +349,7 @@ Lambdas present a particular challenge for a Python type checker because there i
var1 = lambda a, b: a + b
# This function takes a comparison function callback.
def float_sort(list: List[float], comp: Callable[[float, float], bool]): ...
def float_sort(list: list[float], comp: Callable[[float, float], bool]): ...
# In this example, the types of the lambdas input parameters
# a and b can be inferred to be float because the float_sort

View File

@ -76,9 +76,9 @@ Variables:
Type annotations can be omitted in a few specific cases where the type is obvious from the context:
* Constants that are assigned simple literal values (e.g. `RED = '#F00'` or `MAX_TIMEOUT = 50` or `room_temperature: Final = 20`). A constant is a symbol that is assigned only once and is either annotated with `Final` or is named in all-caps. A constant that is not assigned a simple literal value requires explicit annotations, preferably with a `Final` annotation (e.g. `WOODWINDS: Final[List[str]] = ['Oboe', 'Bassoon']`).
* Constants that are assigned simple literal values (e.g. `RED = '#F00'` or `MAX_TIMEOUT = 50` or `room_temperature: Final = 20`). A constant is a symbol that is assigned only once and is either annotated with `Final` or is named in all-caps. A constant that is not assigned a simple literal value requires explicit annotations, preferably with a `Final` annotation (e.g. `WOODWINDS: Final[list[str]] = ['Oboe', 'Bassoon']`).
* Enum values within an Enum class do not require annotations because they take on the type of the Enum class.
* Type aliases do not require annotations. A type alias is a symbol that is defined at a module level with a single assignment where the assigned value is an instantiable type, as opposed to a class instance (e.g. `Foo = Callable[[Literal["a", "b"]], Union[int, str]]` or `Bar = Optional[MyGenericClass[int]]`).
* Type aliases do not require annotations. A type alias is a symbol that is defined at a module level with a single assignment where the assigned value is an instantiable type, as opposed to a class instance (e.g. `Foo = Callable[[Literal["a", "b"]], int | str]` or `Bar = MyGenericClass[int] | None`).
* The “self” parameter in an instance method and the “cls” parameter in a class method do not require an explicit annotation.
* The return type for an `__init__` method does not need to be specified, since it is always `None`.
* The following module-level symbols do not require type annotations: `__all__`,`__author__`, `__copyright__`, `__email__`, `__license__`, `__title__`, `__uri__`, `__version__`.
@ -103,21 +103,21 @@ a = 3
a = [3, 4, 5]
# Variable with known (declared) type
a: List[int] = [3, 4, 5]
a: list[int] = [3, 4, 5]
# Type alias with partially unknown type (because type
# arguments are missing for list and dict)
DictOrList = Union[list, dict]
DictOrList = list | dict
# Type alias with known type
DictOrList = Union[List[Any], Dict[str, Any]]
DictOrList = list[Any] | dict[str, Any]
# Generic type alias with known type
_T = TypeVar("_T")
DictOrList = Union[List[_T], Dict[str, _T]]
DictOrList = list[_T] | dict[str, _T]
# Function with known type
def func(a: Optional[int], b: Dict[str, float] = {}) -> None:
def func(a: int | None, b: dict[str, float] = {}) -> None:
pass
# Function with partially unknown type (because type annotations
@ -126,13 +126,13 @@ def func(a, b):
pass
# Function with partially unknown type (because of missing
# type args on Dict)
def func(a: int, b: Dict) -> None:
# type args on dict)
def func(a: int, b: dict) -> None:
pass
# Function with partially unknown type (because return type
# annotation is missing)
def func(a: int, b: Dict[str, float]):
def func(a: int, b: dict[str, float]):
pass
# Decorator with partially unknown type (because type annotations
@ -235,11 +235,11 @@ Here are some tips for increasing the type completeness score for your library:
## Best Practices for Inlined Types
### Wide vs. Narrow Types
In type theory, when comparing two types that are related to each other, the “wider” type is the one that is more general, and the “narrower” type is more specific. For example, `Sequence[str]` is a wider type than `List[str]` because all `List` objects are also `Sequence` objects, but the converse is not true. A subclass is narrower than a class it derives from. A union of types is wider than the individual types that comprise the union.
In type theory, when comparing two types that are related to each other, the “wider” type is the one that is more general, and the “narrower” type is more specific. For example, `Sequence[str]` is a wider type than `list[str]` because all `list` objects are also `Sequence` objects, but the converse is not true. A subclass is narrower than a class it derives from. A union of types is wider than the individual types that comprise the union.
In general, a function input parameter should be annotated with the widest possible type supported by the implementation. For example, if the implementation requires the caller to provide an iterable collection of strings, the parameter should be annotated as `Iterable[str]`, not as `List[str]`. The latter type is narrower than necessary, so if a user attempts to pass a tuple of strings (which is supported by the implementation), a type checker will complain about a type incompatibility.
In general, a function input parameter should be annotated with the widest possible type supported by the implementation. For example, if the implementation requires the caller to provide an iterable collection of strings, the parameter should be annotated as `Iterable[str]`, not as `list[str]`. The latter type is narrower than necessary, so if a user attempts to pass a tuple of strings (which is supported by the implementation), a type checker will complain about a type incompatibility.
As a specific application of the “use the widest type possible” rule, libraries should generally use immutable forms of container types instead of mutable forms (unless the function needs to modify the container). Use `Sequence` rather than `List`, `Mapping` rather than `Dict`, etc. Immutable containers allow for more flexibility because their type parameters are covariant rather than invariant. A parameter that is typed as `Sequence[Union[str, int]]` can accept a `List[Union[str, int]]`, `List[int]`, `Sequence[str]`, and a `Sequence[int]`. But a parameter typed as `List[Union[str, int]]` is much more restrictive and accepts only a `List[Union[str, int]]`.
As a specific application of the “use the widest type possible” rule, libraries should generally use immutable forms of container types instead of mutable forms (unless the function needs to modify the container). Use `Sequence` rather than `list`, `Mapping` rather than `dict`, etc. Immutable containers allow for more flexibility because their type parameters are covariant rather than invariant. A parameter that is typed as `Sequence[str | int]` can accept a `list[str | int]`, `list[int]`, `Sequence[str]`, and a `Sequence[int]`. But a parameter typed as `list[str | int]` is much more restrictive and accepts only a `list[str | int]`.
### Overloads
If a function or method can return multiple different types and those types can be determined based on the presence or types of certain parameters, use the `@overload` mechanism defined in [PEP 484](https://www.python.org/dev/peps/pep-0484/#id45). When overloads are used within a “.py” file, they must appear prior to the function implementation, which should not have an `@overload` decorator.
@ -248,7 +248,7 @@ If a function or method can return multiple different types and those types can
If a function or method is intended to take parameters that are specified only by name, use the keyword-only separator ("*").
```python
def create_user(age: int, *, dob: Optional[date] = None):
def create_user(age: int, *, dob: date | None = None):
...
```
@ -298,16 +298,16 @@ Type aliases are symbols that refer to other types. Generic type aliases (those
```python
# Simple type alias
FamilyPet = Union[Cat, Dog, GoldFish]
FamilyPet = Cat | Dog | GoldFish
# Generic type alias
ListOrTuple = Union[List[_T], Tuple[_T, ...]]
ListOrTuple = list[_T] | tuple[_T, ...]
# Recursive type alias
TreeNode = Union[LeafNode, List["TreeNode"]]
TreeNode = LeafNode | list["TreeNode"]
# Explicit type alias using PEP 613 syntax
StrOrInt: TypeAlias = Union[str, int]
StrOrInt: TypeAlias = str | int
```
### Abstract Classes and Methods
@ -348,14 +348,14 @@ COLOR_FORMAT_RGB = "rgb"
# All-caps constant with explicit type
COLOR_FORMAT_RGB: Literal["rgb"] = "rgb"
LATEST_VERSION: Tuple[int, int] = (4, 5)
LATEST_VERSION: tuple[int, int] = (4, 5)
# Final variable with inferred type
ColorFormatRgb: Final = "rgb"
# Final variable with explicit type
ColorFormatRgb: Final[Literal["rgb"]] = "rgb"
LATEST_VERSION: Final[Tuple[int, int]] = (4, 5)
LATEST_VERSION: Final[tuple[int, int]] = (4, 5)
```
### Typed Dictionaries, Data Classes, and Named Tuples
@ -391,7 +391,7 @@ If you need to support older versions of Python, type annotations can still be p
class Foo:
# Variable type comments go at the end of the line
# where the variable is assigned.
timeout = None # type: Optional[int]
timeout = None # type: int | None
# Function type comments can be specified on the
# line after the function signature.

View File

@ -162,7 +162,7 @@ def dataclass_transform(
eq_default: bool = True,
order_default: bool = False,
kw_only_default: bool = False,
field_specifiers: Tuple[type, ...] = (()),
field_specifiers: tuple[type, ...] = (()),
) -> Callable[[_T], _T]: ...
```
@ -260,7 +260,7 @@ class Employee:
name: str
# Field that uses field descriptor class instance
age: Optional[int] = field(default=None, init=False)
age: int | None = field(default=None, init=False)
# Field with type annotation and simple initializer to
# describe default value
@ -306,7 +306,7 @@ This example demonstrates
@overload
def model_field(
*,
default: Optional[Any] = ...,
default: Any | None = ...,
resolver: Callable[[], Any],
init: Literal[False] = False,
) -> Any: ...
@ -314,7 +314,7 @@ def model_field(
@overload
def model_field(
*,
default: Optional[Any] = ...,
default: Any | None = ...,
resolver: None = None,
init: bool = True,
) -> Any: ...
@ -349,7 +349,7 @@ def dataclass_transform(
eq_default: bool = True,
order_default: bool = False,
kw_only_default: bool = False,
field_specifiers: Tuple[Union[type, Callable[..., Any]], ...] = (()),
field_specifiers: tuple[type | Callable[..., Any], ...] = (()),
) -> Callable[[_T], _T]:
return lambda a: a
```
@ -422,7 +422,7 @@ def __dataclass_transform__(
eq_default: bool = True,
order_default: bool = False,
kw_only_default: bool = False,
field_specifiers: Tuple[Union[type, Callable[..., Any]], ...] = (()),
field_specifiers: tuple[type | Callable[..., Any], ...] = (()),
) -> Callable[[_T], _T]:
# If used within a stub file, the following implementation can be
# replaced with "...".
@ -516,7 +516,7 @@ def __dataclass_transform__(
eq_default: bool = True,
order_default: bool = False,
kw_only_default: bool = False,
field_specifiers: Tuple[Union[type, Callable[..., Any]], ...] = (()),
field_specifiers: tuple[type | Callable[..., Any], ...] = (()),
) -> Callable[[_T], _T]: ...
```
@ -554,7 +554,7 @@ def __dataclass_transform__(
eq_default: bool = True,
order_default: bool = False,
kw_only_default: bool = False,
field_specifiers: Tuple[Union[type, Callable[..., Any]], ...] = (()),
field_specifiers: tuple[type | Callable[..., Any], ...] = (()),
) -> Callable[[_T], _T]:
return lambda a: a
```