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:
Mark Mendoza 2020-07-13 13:11:38 -07:00 committed by GitHub
parent 5b81f54bd3
commit d872215d94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 67 additions and 59 deletions

View File

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