1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
|
Decorators
##########
.. #contents:: Table of Contents
:local:
:depth: 2
Decorators can be applied to classes or functions/methods. A decorator is a callable, so a function or a class
implementing ``__call__``. Decorator can accept parameters, when a decorator factory returns a specific decorator.
The decorator syntax of Python is syntactic sugar for a function call.
See also :ref:`decorators offered by pyTooling <DECO>`.
.. hint::
The predefined :func:`~functools.wraps` decorator should be used when creating wrapping or replacing decorators, so
the name and doc-string of the callable is preserved and decorators can be chained.
+-------------------------------------+---------------------------------------------------+-----------------------------------------------+
| Function-based without Parameter | Function-based with Parameter(s) | Class-based with Parameter(s) |
+=====================================+===================================================+===============================================+
| .. code-block:: Python | .. code-block:: Python | .. code-block:: Python |
| | | |
| from functools import wraps | from functools import wraps | from functools import wraps |
| | | |
| F = TypeVar("F", Callable) | F = TypeVar("F", Callable) | F = TypeVar("F", Callable) |
| | | |
| def decorator(func: F) -> F: | def decorator_factory(param: int) -> Callable: | class decoratorclass: |
| @wraps(func) | def specific_decorator(func: F) -> F: | _param: int |
| def wrapper(*args, **kwargs): | @wraps(func) | |
| return func(*args, **kwargs) | def wrapper(*args, **kwargs): | def __init__(self, param: int) -> None: |
| | kwargs["param"] = param | self._param = param |
| return wrapper | return func(*args, **kwargs) | |
| | | def __call__(self, func: F) -> F: |
| | return wrapper | @wraps(func) |
| | return specific_Decorator | def wrapper(*args, **kwargs): |
| | | kwargs["param"] = self._param |
| | | return func(*args, **kwargs) |
| | | |
| # | # | return wrapper |
| | | |
+-------------------------------------+---------------------------------------------------+-----------------------------------------------+
| .. code-block:: Python | .. code-block:: Python | .. code-block:: Python |
| | | |
| @decorator | @decorator_factory(10) | @decoratorclass(10) |
| def foo(param: int) -> bool: | def foo(param: int) -> bool: | def foo(param: int) -> bool: |
| pass | pass | pass |
+-------------------------------------+---------------------------------------------------+-----------------------------------------------+
| .. code-block:: Python | .. code-block:: Python | .. code-block:: Python |
| | | |
| def foo(param: int) -> bool: | def foo(param: int) -> bool: | def foo(param: int) -> bool: |
| pass | pass | pass |
| | | |
| foo = decorator(foo) | foo = decorator(10)(foo) | foo = decoratorclass(10)(foo) |
+-------------------------------------+---------------------------------------------------+-----------------------------------------------+
Usecase
*******
Modifying Decorator
===================
A modifying decorator returns the original, but modified language item. Existing fields might be modified or new fields
might be added to the language item. It supports classes, functions and methods.
.. code-block:: Python
F = TypeVar("F", Callable)
def decorator(func: F) -> F:
func.__field__ = ...
return func
@decorator
def function(param: int) -> bool:
pass
class C:
@decorator
def method(self, param: int) -> bool:
pass
.. seealso::
The predefined :func:`~functools.wraps` decorator is a modifying decorator because it copies the ``__name__`` and
``__doc__`` fields from the original callable to the decorated callable.
Replacing Decorator
===================
A replacing decorator replaces the original language item by a new language item. The new item might have a similar or
completely different behavior as the original item. It supports classes, functions and methods.
.. code-block:: Python
F = TypeVar("F", Callable)
def decorator(func: F) -> F:
def replacement(*args, **kwargs):
pass
return replacement
@decorator
def function(param: int) -> bool:
pass
class C:
@decorator
def method(self, param: int) -> bool:
pass
.. seealso::
The predefined :func:`property` decorator is a replacing decorator because it replaces the method with a descriptor
implementing *getter* for a read-only property. It's a special cases, because it's also a wrapping decorator as the
behavior of the original method is the behavior of the getter.
Wrapping Decorator
==================
.. todo:: TUTORIAL::Wrapping decorator
.. code-block:: Python
F = TypeVar("F", Callable)
def decorator(func: F) -> F:
def wrapper(*args, **kwargs):
# ...
return func(*args, **kwargs)
return replacement
@decorator
def function(param: int) -> bool:
pass
class C:
@decorator
def method(self, param: int) -> bool:
pass
Without Parameters
******************
Function-based without Parameters
=================================
.. todo:: TUTORIAL::Function-based without parameters - write a tutorial
.. code-block:: Python
F = TypeVar("F", Callable)
def decorator(func: F) -> F:
def wrapper(*args, **kwargs):
# ...
return func(*args, **kwargs)
return replacement
With Parameters
***************
Function-based with Parameters
==============================
.. todo:: TUTORIAL::Function-based with parameters - write a tutorial
.. code-block:: Python
F = TypeVar("F", Callable)
def decorator_factory(param: int) -> Callable:
def decorator(func: F) -> F:
def wrapper(*args, **kwargs):
# ...
return func(*args, **kwargs)
return replacement
return decorator
Class-based with Parameters
===========================
A decorator accepting parameters can also be implemented with a class providing ``__call__``, so it's a callable.
.. todo:: TUTORIAL::Class-based - write a tutorial
.. code-block:: Python
from functools import wraps
F = TypeVar("F", Callable)
class decoratorclass:
_param: int
def __init__(self, param: int) -> None:
self._param = param
def __call__(self, func: F) -> F:
@wraps(func)
def wrapper(*args, **kwargs):
kwargs["param"] = self._param
return func(*args, **kwargs)
return wrapper
|