File: Decorators.rst

package info (click to toggle)
python-pytooling 8.6.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,564 kB
  • sloc: python: 23,883; makefile: 13
file content (217 lines) | stat: -rw-r--r-- 9,347 bytes parent folder | download
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