Skip to content

Commit f1babd8

Browse files
intgrMarti Raudsepp
authored and
Marti Raudsepp
committed
Improve decorators documentation (add decorator factories)
* Previously only bare decorators were explained, but decorator factories are also fairly common in the real world. * Added decorator examples to cheat sheet page. * Explained difference in behavior for class decorators. * Shortened (IMO) excessive example for bare decorators.
1 parent ea3c65c commit f1babd8

File tree

3 files changed

+99
-12
lines changed

3 files changed

+99
-12
lines changed

docs/source/cheat_sheet.rst

+19-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ Functions
113113
# type: (...) -> bool
114114
<code>
115115
116-
117116
When you're puzzled or when things are complicated
118117
**************************************************
119118

@@ -256,3 +255,22 @@ Miscellaneous
256255
return sys.stdin
257256
else:
258257
return sys.stdout
258+
259+
260+
Decorators
261+
**********
262+
263+
Decorator functions can be expressed via generics. See
264+
:ref:`declaring-decorators` for the more details.
265+
266+
.. code-block:: python
267+
268+
from typing import Any, Callable, TypeVar
269+
270+
F = TypeVar('F', bound=Callable[..., Any])
271+
272+
def bare_decorator(func): # type: (F) -> F
273+
...
274+
275+
def decorator_args(url): # type: (str) -> Callable[[F], F]
276+
...

docs/source/cheat_sheet_py3.rst

+19-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ Python 3 supports an annotation syntax for function declarations.
127127
quux(3) # Fine
128128
quux(__x=3) # Error
129129
130-
131130
When you're puzzled or when things are complicated
132131
**************************************************
133132

@@ -311,3 +310,22 @@ Miscellaneous
311310
# class of that name later on in the file
312311
def f(foo: 'A') -> int: # Ok
313312
...
313+
314+
315+
Decorators
316+
**********
317+
318+
Decorator functions can be expressed via generics. See
319+
:ref:`declaring-decorators` for the more details.
320+
321+
.. code-block:: python
322+
323+
from typing import Any, Callable, TypeVar
324+
325+
F = TypeVar('F', bound=Callable[..., Any])
326+
327+
def bare_decorator(func: F) -> F:
328+
...
329+
330+
def decorator_args(url: str) -> Callable[[F], F]:
331+
...

docs/source/generics.rst

+61-10
Original file line numberDiff line numberDiff line change
@@ -522,14 +522,19 @@ Declaring decorators
522522

523523
One common application of type variable upper bounds is in declaring a
524524
decorator that preserves the signature of the function it decorates,
525-
regardless of that signature. Here's a complete example:
525+
regardless of that signature.
526+
527+
Note that class decorators are handled differently than function decorators in
528+
mypy: decorating a class does not erase its type, even if the decorator has
529+
incomplete type annotations.
530+
531+
Here's a complete example of a function decorator:
526532

527533
.. code-block:: python
528534
529535
from typing import Any, Callable, TypeVar, Tuple, cast
530536
531-
FuncType = Callable[..., Any]
532-
F = TypeVar('F', bound=FuncType)
537+
F = TypeVar('F', bound=Callable[..., Any])
533538
534539
# A decorator that preserves the signature.
535540
def my_decorator(func: F) -> F:
@@ -543,15 +548,8 @@ regardless of that signature. Here's a complete example:
543548
def foo(a: int) -> str:
544549
return str(a)
545550
546-
# Another.
547-
@my_decorator
548-
def bar(x: float, y: float) -> Tuple[float, float, bool]:
549-
return (x, y, x > y)
550-
551551
a = foo(12)
552552
reveal_type(a) # str
553-
b = bar(3.14, 0)
554-
reveal_type(b) # Tuple[float, float, bool]
555553
foo('x') # Type check error: incompatible type "str"; expected "int"
556554
557555
From the final block we see that the signatures of the decorated
@@ -566,6 +564,59 @@ functions are typically small enough that this is not a big
566564
problem. This is also the reason for the :py:func:`~typing.cast` call in the
567565
``return`` statement in ``my_decorator()``. See :ref:`casts`.
568566

567+
.. _decorator-factories:
568+
569+
Decorator factories
570+
-------------------
571+
572+
Functions that take arguments and return a decorator (also called second-order decorators), are
573+
similarly supported via generics:
574+
575+
.. code-block:: python
576+
577+
from typing import Any, Callable, TypeVar
578+
579+
F = TypeVar('F', bound=Callable[..., Any])
580+
581+
def route(url: str) -> Callable[[F], F]:
582+
...
583+
584+
@route(url='/')
585+
def index(request: Any) -> str:
586+
return 'Hello world'
587+
588+
Sometimes the same decorator supports both bare calls and calls with arguments. This can be
589+
achieved by combining with :py:func:`@overload <typing.overload>`:
590+
591+
.. code-block:: python
592+
593+
from typing import Any, Callable, TypeVar, overload
594+
595+
F = TypeVar('F', bound=Callable[..., Any])
596+
597+
# Bare decorator usage
598+
@overload
599+
def atomic(__func: F) -> F: ...
600+
# Decorator with arguments
601+
@overload
602+
def atomic(*, savepoint: bool = True) -> Callable[[F], F]: ...
603+
604+
# Implementation
605+
def atomic(__func: Callable[..., Any] = None, *, savepoint: bool = True):
606+
def decorator(func: Callable[..., Any]):
607+
... # Code goes here
608+
if __func is not None:
609+
return decorator(__func)
610+
else:
611+
return decorator
612+
613+
# Usage
614+
@atomic
615+
def func1() -> None: ...
616+
617+
@atomic(savepoint=False)
618+
def func2() -> None: ...
619+
569620
Generic protocols
570621
*****************
571622

0 commit comments

Comments
 (0)