11Decorators
22==========
33
4- Functions can also be passed as arguments to other functions and return the
5- results of other functions. For example, it is possible to write a Python
6- function that takes another function as a :term: ` parameter ` , embeds it in
7- another function that does something similar, and then returns the new function.
8- This new combination can then be used instead of the original function:
4+ Functions can also be passed as arguments to other functions, and the results of
5+ other functions can be returned . For example, it is possible to write a Python
6+ function that takes another function as a parameter, embeds it within another
7+ function that does something similar, and then returns the new function. This
8+ new combination can then be used in place of the original function:
99
1010.. code-block :: pycon
1111 :linenos:
@@ -26,23 +26,23 @@ This new combination can then be used instead of the original function:
2626 Execute function my_func with the argument(s)
2727 ('Hello', 'Pythonistas!')
2828
29- Line 2
30- The ``inf `` function outputs the name of the function it wraps.
31- Line 6
32- When finished, the ``inf `` function returns the wrapped function.
29+ Line 1
30+ The ``inf `` function prints the name of the function it wraps.
31+ Line 12
32+ When it has finished, the ``inf `` function returns the wrapped function.
3333
3434A decorator is `syntactic sugar
3535<https://en.wikipedia.org/wiki/Syntactic_sugar> `_ for this process and allows
36- you to wrap one function inside another with a one- line addition . You still get
37- exactly the same effect as with the previous code, but the resulting code is
36+ you to wrap one function around another with a single line of code . You still
37+ get exactly the same effect as with the previous code, but the resulting code is
3838much cleaner and easier to read. Using a decorator simply consists of two parts:
3939
40- #. the definition of the function to wrap or * decorate * other functions, and
41- #. the use of an ``@ `` followed by the decorator just before the wrapped
40+ #. defining the function that is to wrap or decorate other functions, and
41+ #. using an ``@ ``, followed by the decorator, immediately before the wrapped
4242 function is defined.
4343
44- The decorator function should take a function as a :term: ` parameter ` and return
45- a function, as follows:
44+ The decorator function should take a function as a parameter and return a
45+ function, as follows:
4646
4747.. code-block :: pycon
4848 :linenos:
@@ -58,81 +58,218 @@ a function, as follows:
5858
5959 Line 1
6060 The function ``my_func `` is decorated with ``@inf ``.
61- Line 7
62- The wrapped function is called after the decorator function is finished.
61+ Line 8
62+ The wrapped function is called once the decorator function has finished.
6363
6464``functools ``
6565-------------
6666
67- The Python :mod: `functools ` module is intended for higher-order functions, for
68- example functions that act on or return other functions. Mostly you can use them
69- as decorators, such as :
67+ The Python :mod: `functools ` module is designed for higher-order functions, that
68+ are functions which act on or return other functions. You can usually use them
69+ as decorators, for example :
7070
7171:func: `functools.cache `
72- Simple, lightweight, function cache as of Python ≥ 3.9, sometimes called
73- *memoize *. It returns the same as :func: `functools.lru_cache ` with the
74- :term: `parameter ` ``maxsize=None ``, additionally creating a
75- :doc: `/types/dicts ` with the function arguments. Since old values never
76- need to be deleted, this function is then also smaller and faster. Example:
72+ A simple, lightweight cache for functions in Python 3.9 and later, sometimes
73+ also referred to as *memoize *. It returns the same result as
74+ :func: `functools.lru_cache ` with the parameter ``maxsize=None ``, whilst
75+ additionally creating a :doc: `/types/dicts ` containing the function
76+ arguments. As old values never need to be deleted, this function is
77+ therefore smaller and faster. An example:
7778
7879 .. code-block :: pycon
7980 :linenos:
8081
8182 >>> from timeit import timeit
82- ... from functools import cache
83- ... @cache
83+ >>> from functools import cache
84+ >>> @cache
8485 ... def factorial(n):
8586 ... return n * factorial(n - 1) if n else 1
86- ... timeit("factorial(8)", globals=globals())
87- 0.02631620899774134
87+ ...
88+ >>> timeit("factorial(10)", number=1, globals=globals())
89+ 8.74977558851242e-06
90+ >>> timeit("factorial(12)", number=1, globals=globals())
91+ 4.041939973831177e-06
92+ >>> timeit("factorial(12)", number=1, globals=globals())
93+ 1.8328428268432617e-06
8894
8995 Line 1
90- imports the :mod: `timeit ` module for measuring execution time.
96+ imports the :mod: `timeit ` module to measure execution time.
9197 Line 2
9298 imports :func: `functools.cache `.
93- Line 5
99+ Line 3
94100 The ``@cache `` decorator is used to store intermediate results, which
95- can then be reused. In our case, the execution speed is increased
96- approximately tenfold.
97- Line 10
98- :func: `timeit.timeit ` measures the time of a call. Unless otherwise
99- specified, the call is made one million times.
101+ can then be reused. In our case, this increases the execution speed by a
102+ factor of approximately ten.
103+ Line 7
104+ :func: `timeit.timeit ` measures the time taken for a call.
105+ Line 9
106+ Only two further recursive calls need to be made, as ``factorial(10) ``
107+ is already cached.
100108
101- :func: `functools.wraps `
102- This decorator makes the wrapper function look like the original function
103- with its name and properties.
109+ :func: `functools.singledispatch `
110+ converts a function into a generic function. To define a generic function,
111+ it is decorated with the ``@singledispatch `` decorator:
112+
113+ .. code-block :: pycon
114+
115+ >>> from functools import singledispatch
116+ >>>
117+ >>> @singledispatch
118+ ... def multiply(a, b):
119+ ... raise NotImplementedError("Unsupported type")
120+ ...
121+
122+ To add overloaded implementations to the function, you can use
123+ :func: `register ` on the generic function as a decorator:
104124
105125 .. code-block :: pycon
106126
107- >>> from functools import wraps
108- >>> def my_decorator(f):
109- ... @wraps(f)
110- ... def wrapper(*args, **kwargs):
111- ... """Wrapper docstring"""
112- ... print("Call decorated function")
113- ... return f(*args, **kwargs)
114- ... return wrapper
127+ >>> @multiply.register(float)
128+ ... def _(a, b):
129+ ... print(a * b)
115130 ...
116- >>> @my_decorator
117- ... def example():
118- ... """Example docstring"""
119- ... print("Call example function")
131+ >>> @multiply.register(str)
132+ ... def _(a, b):
133+ ... print(float(a) * float(b))
120134 ...
121- >>> example.__name__
122- 'example'
123- >>> example.__doc__
124- 'Example docstring'
135+ >>> multiply(7.0, 0.6)
136+ 4.2
137+ >>> multiply("7.0", "0.6")
138+ 4.2
139+
140+ For functions annotated with types, the decorator automatically infers the
141+ type of the first argument.
142+
143+ :func: `functools.wraps `
144+ This decorator ensures that the wrapped function looks exactly like the
145+ original function, with its name and attributes intact.
146+
147+ .. code-block :: pycon
125148
126- Without ``@wraps `` decorator, the name and docstring of the wrapper method
127- would have been returned instead:
149+ >>> from functools import wraps
150+ >>> def my_decorator(f):
151+ ... @wraps(f)
152+ ... def wrapper(*args, **kwargs):
153+ ... """Wrapper docstring"""
154+ ... print("Call decorated function")
155+ ... return f(*args, **kwargs)
156+ ... return wrapper
157+ ...
158+ >>> @my_decorator
159+ ... def example():
160+ ... """Example docstring"""
161+ ... print("Call example function")
162+ ...
163+ >>> example.__name__
164+ 'example'
165+ >>> example.__doc__
166+ 'Example docstring'
167+
168+ Without the ``@wraps `` decorator, the name and docstring of the ``wrapper ``
169+ method would have been returned instead:
128170
129171 .. code-block :: pycon
130172
131- >>> example.__name__
132- 'wrapper'
133- >>> example.__doc__
134- 'Wrapper docstring'
173+ >>> example.__name__
174+ 'wrapper'
175+ >>> example.__doc__
176+ 'Wrapper docstring'
177+
178+ Other typical uses for Python decorators
179+ ----------------------------------------
180+
181+ Other Python compilers
182+ ~~~~~~~~~~~~~~~~~~~~~~
183+
184+ Python compilers such as `Numba <https://numba.pydata.org/ >`_ can be used with a
185+ decorator:
186+
187+ .. code-block :: python
188+
189+ @numba.jit (nopython = True )
190+ def dist (x , y ):
191+ """ Calculate the distance"""
192+ dist = 0
193+ for i in range (len (x)):
194+ dist += (x[i] - y[i]) ** 2
195+ return dist
196+
197+ .. seealso ::
198+ * :ref: `/performance/index.rst#numba `
199+
200+ Parallelisation
201+ ~~~~~~~~~~~~~~~
202+
203+ The sequential execution of independent pipeline steps does not make optimal use
204+ of the processing power of processors. The `@dask.delayed
205+ <https://docs.dask.org/en/stable/delayed.html#decorator> `_ decorator creates a
206+ directed acyclic graph (DAG) to execute the tasks in parallel, which helps to
207+ reduce the overall execution time:
208+
209+ .. code-block :: pycon
210+
211+ >>> import dask
212+ >>> @dask.delayed
213+ ... def inc(x):
214+ ... return x + 1
215+ ...
216+ >>> @dask.delayed
217+ ... def double(x):
218+ ... return x * 2
219+ ...
220+ >>> @dask.delayed
221+ ... def add(x, y):
222+ ... return x + y
223+ ...
224+ >>> data = range(1, 6)
225+ >>> output = []
226+ >>> for x in data:
227+ ... a = inc(x)
228+ ... b = double(x)
229+ ... c = add(a, b)
230+ ... output.append(c)
231+ ...
232+ >>> total = dask.delayed(sum)(output)
233+ >>> total.compute()
234+ 50
235+ >>> total.visualize()
236+ <IPython.core.display.Image object>
237+
238+ .. figure :: mydask.png
239+
240+ Memory profiling
241+ ~~~~~~~~~~~~~~~~
242+
243+ The ``@memory_profiler.profile `` decorator is used to measure memory usage. It
244+ monitors the enclosed function step by step, tracking RAM usage or the amount of
245+ memory released at each individual step:
246+
247+ .. code-block :: python
248+ :linenos:
249+
250+ from memory_profiler import profile
251+
252+
253+ @profile
254+ def my_func ():
255+ a = [1 ] * (10 ** 6 )
256+ b = [2 ] * (2 * 10 ** 7 )
257+ del b
258+ return a
259+
260+ The output might look like this:
261+
262+ .. code-block :: console
263+
264+ Line # Mem usage Increment Line Contents
265+ ================================================
266+ 4 67.3 MiB 67.3 MiB @profile
267+ 5 def my_func():
268+ 6 74.8 MiB 7.5 MiB a = [1] * (10 ** 6)
269+ 7 227.4 MiB 152.6 MiB b = [2] * (2 * 10 ** 7)
270+ 8 74.9 MiB 0.0 MiB del b
271+ 9 74.9 MiB 0.0 MiB return a
135272
136- .. tip ::
137- ` cusy seminar: Advanced Python
138- <https://cusy.io/en/our-training-courses/advanced-python .html> `_
273+ .. seealso ::
274+ * ` memory-profiler
275+ <https://www.python4data.science/de/latest/performance/ipython-profiler .html#Speicherprofil-erstellen:-%memit-und-%mprun > `_
0 commit comments