PEP 612: Polishing up (#1521)
* making indentation consistent * small alteration to section structure * providing a bit more context for the 'User Defined Generic Classes' section * missing 'and'
This commit is contained in:
parent
5b81f54bd3
commit
d872215d94
126
pep-0612.rst
126
pep-0612.rst
|
@ -38,14 +38,14 @@ tools to annotate the following common decorator pattern satisfactorily:
|
|||
R = TypeVar("R")
|
||||
|
||||
def add_logging(f: Callable[..., R]) -> Callable[..., Awaitable[R]]:
|
||||
async def inner(*args: object, **kwargs: object) -> R:
|
||||
await log_to_database()
|
||||
return f(*args, **kwargs)
|
||||
return inner
|
||||
async def inner(*args: object, **kwargs: object) -> R:
|
||||
await log_to_database()
|
||||
return f(*args, **kwargs)
|
||||
return inner
|
||||
|
||||
@add_logging
|
||||
def takes_int_str(x: int, y: str) -> int:
|
||||
return x + 7
|
||||
return x + 7
|
||||
|
||||
await takes_int_str(1, "A")
|
||||
await takes_int_str("B", 2) # fails at runtime
|
||||
|
@ -89,14 +89,14 @@ the decorator and the parameter enforcement of the decorated function.
|
|||
R = TypeVar("R")
|
||||
|
||||
def add_logging(f: Callable[P, R]) -> Callable[P, Awaitable[R]]:
|
||||
async def inner(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||
await log_to_database()
|
||||
return f(*args, **kwargs)
|
||||
return inner
|
||||
async def inner(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||
await log_to_database()
|
||||
return f(*args, **kwargs)
|
||||
return inner
|
||||
|
||||
@add_logging
|
||||
def takes_int_str(x: int, y: str) -> int:
|
||||
return x + 7
|
||||
return x + 7
|
||||
|
||||
await takes_int_str(1, "A") # Accepted
|
||||
await takes_int_str("B", 2) # Correctly rejected by the type checker
|
||||
|
@ -108,17 +108,17 @@ example:
|
|||
.. code-block::
|
||||
|
||||
class Request:
|
||||
...
|
||||
...
|
||||
|
||||
def with_request(f: Callable[..., R]) -> Callable[..., R]:
|
||||
def inner(*args: object, **kwargs: object) -> R:
|
||||
return f(Request(), *args, **kwargs)
|
||||
return inner
|
||||
def inner(*args: object, **kwargs: object) -> R:
|
||||
return f(Request(), *args, **kwargs)
|
||||
return inner
|
||||
|
||||
@with_request
|
||||
def takes_int_str(request: Request, x: int, y: str) -> int:
|
||||
# use request
|
||||
return x + 7
|
||||
# use request
|
||||
return x + 7
|
||||
|
||||
takes_int_str(1, "A")
|
||||
takes_int_str("B", 2) # fails at runtime
|
||||
|
@ -132,14 +132,14 @@ type this more complex decorator.
|
|||
from typing.type_variable_operators import Concatenate
|
||||
|
||||
def with_request(f: Callable[Concatenate[Request, P], R]) -> Callable[P, R]:
|
||||
def inner(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||
return f(Request(), *args, **kwargs)
|
||||
return inner
|
||||
def inner(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||
return f(Request(), *args, **kwargs)
|
||||
return inner
|
||||
|
||||
@with_request
|
||||
def takes_int_str(request: Request, x: int, y: str) -> int:
|
||||
# use request
|
||||
return x + 7
|
||||
# use request
|
||||
return x + 7
|
||||
|
||||
takes_int_str(1, "A") # Accepted
|
||||
takes_int_str("B", 2) # Correctly rejected by the type checker
|
||||
|
@ -148,8 +148,11 @@ type this more complex decorator.
|
|||
Specification
|
||||
-------------
|
||||
|
||||
ParamSpec Declarations
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
``ParamSpec`` Variables
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Declaration
|
||||
````````````
|
||||
|
||||
A parameter specification variable is defined in a similar manner to how a
|
||||
normal type variable is defined with ``typing.TypeVar``.
|
||||
|
@ -165,7 +168,7 @@ arguments in the declaration just as ``typing.TypeVar`` does, but for now we
|
|||
will defer the standardization of the semantics of those options to a later PEP.
|
||||
|
||||
Valid use locations
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
```````````````````
|
||||
|
||||
Previously only a list of parameter arguments (``[A, B, C]``) or an ellipsis
|
||||
(signifying "undefined parameters") were acceptable as the first "argument" to
|
||||
|
@ -179,10 +182,10 @@ parameter specification variable (``Callable[Concatenate[int, P], int]``\ ).
|
|||
|
||||
parameters_expression ::=
|
||||
| "..."
|
||||
| "[" [ type_expression ("," type_expression)\* ] "]"
|
||||
| "[" [ type_expression ("," type_expression)* ] "]"
|
||||
| parameter_specification_variable
|
||||
| concatenate "["
|
||||
type_expression ("," type_expression)\* ","
|
||||
type_expression ("," type_expression)* ","
|
||||
parameter_specification_variable
|
||||
"]"
|
||||
|
||||
|
@ -216,7 +219,8 @@ inheriting from ``Generic[P]`` makes a class generic on
|
|||
P_2 = ParamSpec("P_2")
|
||||
|
||||
class X(Generic[T, P]):
|
||||
...
|
||||
f: Callable[P, int]
|
||||
x: T
|
||||
|
||||
def f(x: X[int, P_2]) -> str: ... # Accepted
|
||||
def f(x: X[int, Concatenate[int, P_2]]) -> str: ... # Accepted
|
||||
|
@ -231,13 +235,17 @@ brackets. For aesthetic purposes we allow these to be omitted.
|
|||
.. code-block::
|
||||
|
||||
class Z(Generic[P]):
|
||||
...
|
||||
f: Callable[P, int]
|
||||
|
||||
def f(x: Z[[int, str, bool]]) -> str: ... # Accepted
|
||||
def f(x: Z[int, str, bool]) -> str: ... # Equivalent
|
||||
|
||||
# Both Z[[int, str, bool]] and Z[int, str, bool] express this:
|
||||
class Z_instantiated:
|
||||
f: Callable[[int, str, bool], int]
|
||||
|
||||
Semantics
|
||||
^^^^^^^^^
|
||||
`````````
|
||||
|
||||
The inference rules for the return type of a function invocation whose signature
|
||||
contains a ``ParamSpec`` variable are analogous to those around
|
||||
|
@ -371,7 +379,7 @@ These "properties" can only be used as the annotated types for
|
|||
pass
|
||||
|
||||
def out_of_scope(*args: P.args, **kwargs: P.kwargs) -> None: # Rejected
|
||||
pass
|
||||
pass
|
||||
|
||||
|
||||
Furthermore, because the default kind of parameter in Python (\ ``(x: int)``\ )
|
||||
|
@ -510,7 +518,7 @@ of that ``ParamSpec``. That allows us to spell things like this:
|
|||
.. code-block::
|
||||
|
||||
def twice(f: Callable[P, int], *args: P.args, **kwargs: P.kwargs) -> int:
|
||||
return f(*args, **kwargs) + f(*args, **kwargs)
|
||||
return f(*args, **kwargs) + f(*args, **kwargs)
|
||||
|
||||
The type of ``twice`` in the above example is
|
||||
``Callable[Concatenate[Callable[P, int], P], int]``, where ``P`` is bound by the
|
||||
|
@ -584,14 +592,14 @@ convention to prefer.
|
|||
def decorator(
|
||||
f: BetterCallable[[Ts], [Tmap], int],
|
||||
) -> BetterCallable[[Ts], [Tmap], str]:
|
||||
def decorated(*args: Ts, **kwargs: Tmap) -> str:
|
||||
x = f(*args, **kwargs)
|
||||
return int_to_str(x)
|
||||
return decorated
|
||||
def decorated(*args: Ts, **kwargs: Tmap) -> str:
|
||||
x = f(*args, **kwargs)
|
||||
return int_to_str(x)
|
||||
return decorated
|
||||
|
||||
@decorator
|
||||
def foo(x: int) -> int:
|
||||
return x
|
||||
return x
|
||||
|
||||
reveal_type(foo) # Option A: BetterCallable[[int], {}, str]
|
||||
# Option B: BetterCallable[[], {x: int}, str]
|
||||
|
@ -623,30 +631,30 @@ everything that we can express with ``ParamSpecs``.
|
|||
F = TypeVar("F", bound=Function)
|
||||
|
||||
def no_change(f: F) -> F:
|
||||
def inner(
|
||||
*args: ParametersOf[F].args,
|
||||
**kwargs: ParametersOf[F].kwargs
|
||||
) -> ReturnType[F]:
|
||||
return f(*args, **kwargs)
|
||||
return inner
|
||||
def inner(
|
||||
*args: ParametersOf[F].args,
|
||||
**kwargs: ParametersOf[F].kwargs
|
||||
) -> ReturnType[F]:
|
||||
return f(*args, **kwargs)
|
||||
return inner
|
||||
|
||||
def wrapping(f: F) -> Callable[ParametersOf[F], List[ReturnType[F]]]:
|
||||
def inner(
|
||||
*args: ParametersOf[F].args,
|
||||
**kwargs: ParametersOf[F].kwargs
|
||||
) -> List[ReturnType[F]]:
|
||||
return [f(*args, **kwargs)]
|
||||
return inner
|
||||
def inner(
|
||||
*args: ParametersOf[F].args,
|
||||
**kwargs: ParametersOf[F].kwargs
|
||||
) -> List[ReturnType[F]]:
|
||||
return [f(*args, **kwargs)]
|
||||
return inner
|
||||
|
||||
def unwrapping(
|
||||
f: Callable[ParametersOf[F], List[R]]
|
||||
f: Callable[ParametersOf[F], List[R]]
|
||||
) -> Callable[ParametersOf[F], R]:
|
||||
def inner(
|
||||
*args: ParametersOf[F].args,
|
||||
**kwargs: ParametersOf[F].kwargs
|
||||
) -> R:
|
||||
return f(*args, **kwargs)[0]
|
||||
return inner
|
||||
def inner(
|
||||
*args: ParametersOf[F].args,
|
||||
**kwargs: ParametersOf[F].kwargs
|
||||
) -> R:
|
||||
return f(*args, **kwargs)[0]
|
||||
return inner
|
||||
|
||||
We decided to go with ``ParamSpec``\ s over this approach for several reasons:
|
||||
|
||||
|
@ -679,10 +687,10 @@ positional parameters could be expanded to include keyword parameters.
|
|||
.. code-block::
|
||||
|
||||
def add_n(f: Callable[P, R]) -> Callable[Concatenate[("n", int), P], R]:
|
||||
def inner(*args: P.args, n: int, **kwargs: P.kwargs) -> R:
|
||||
# use n
|
||||
return f(*args, **kwargs)
|
||||
return inner
|
||||
def inner(*args: P.args, n: int, **kwargs: P.kwargs) -> R:
|
||||
# use n
|
||||
return f(*args, **kwargs)
|
||||
return inner
|
||||
|
||||
However, the key distinction is that while prepending positional-only parameters
|
||||
to a valid callable type always yields another valid callable type, the same
|
||||
|
|
Loading…
Reference in New Issue