Skip to content
This repository was archived by the owner on Jul 2, 2024. It is now read-only.

Commit cd3ef0c

Browse files
committed
Added "decorators" document - draft
Signed-off-by: Serhii Horodilov <sgorodil@gmail.com>
1 parent 0d26c05 commit cd3ef0c

File tree

3 files changed

+216
-0
lines changed

3 files changed

+216
-0
lines changed

src/basics/decorators.txt

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
.. _first-class objects:
2+
https://dbader.org/blog/python-first-class-functions
3+
4+
*******************************************************************************
5+
Decorators
6+
*******************************************************************************
7+
8+
Decorators provide a simple syntax for calling higher-order functions
9+
:cite:`realpython:decorators`.
10+
11+
.. important::
12+
13+
There is some kind of misunderstanding in definitions.
14+
15+
**Decorator** is a function returning another function, usually applied
16+
as a function transformation using the ``@wrapper`` syntax
17+
:cite:`docs-python:term-decorator`.
18+
19+
However, that's no quit enough to describe it. The more complete
20+
definition is:
21+
22+
**Decorator** is a structural design pattern that lets you attach new
23+
behaviors to objects by placing these objects inside special wrapper
24+
objects that contain the behaviors :cite:`refactoring.guru:decorator`.
25+
26+
Before you understand decorators, you must first understand how functions
27+
work.
28+
29+
First-class objects
30+
===================
31+
32+
In Python functions are `first-class objects`_. Everything in Python is an
33+
object. Functions are objects too.
34+
35+
Inner functions
36+
---------------
37+
38+
Functions can be nested. This means it is possible to define functions
39+
inside other functions.
40+
41+
.. code-block:: python
42+
:caption: Nested functions example
43+
44+
def heap_sort(origin: List[int]) -> List[int]:
45+
"""Return a sorted collection using the heap sort algorithm"""
46+
47+
def heapify(_ds: List[int], _size: int, _idx: int) -> List[int]:
48+
...
49+
50+
...
51+
for idx in range(size, -1, -1):
52+
heapify(result, size, idx)
53+
...
54+
55+
The order in which inner functions are defined no matters. The function
56+
definition does not execute the function body; this gets executed only when
57+
the function is called. Furthermore, the inner functions are not defined until
58+
the parent function is called. They are locally scoped to their parent. Trying
59+
to call ``heapify`` function outside of ``heap_sort`` will cause ``NameError``
60+
exception.
61+
62+
Functions are objects
63+
---------------------
64+
65+
This means functions can be passed around and used as arguments, just like any
66+
other object (e.g. *int*, *str* etc.).
67+
68+
.. code-block:: python
69+
70+
from typing import Callable
71+
72+
73+
def say_hello(name: str) -> str:
74+
return f"Hello, {name}!"
75+
76+
77+
def be_awesome(name: str) -> str:
78+
return f"Yo, {name}!"
79+
80+
81+
def greet_serhii(greeting_func: Callable) -> str:
82+
return greeting_func("Serhii")
83+
84+
85+
if __name__ == "__main__":
86+
print(f"{greet_serhii(say_hello) = }")
87+
print(f"{greet_serhii(be_awesome) = }")
88+
89+
Returning functions
90+
-------------------
91+
92+
Since function can be passed as an argument, it may be returned from another
93+
function.
94+
95+
.. code-block:: python
96+
97+
from typing import Callable
98+
99+
100+
def parent(idx: int) -> Callable:
101+
def first_child():
102+
return "this is the first child"
103+
104+
def second_child():
105+
return "this is the second child"
106+
107+
return second if not num % 2 else first
108+
109+
110+
first = parent(1)
111+
second = parent(2)
112+
113+
.. note::
114+
115+
``parent`` returns functions themselves, there are no parentheses.
116+
117+
After running the code snippet above, ``first`` refers the ``first_child``
118+
function from the inner ``parent`` scope. From now it can be used to call
119+
the target function it refers.
120+
121+
.. code-block::
122+
123+
>>> first()
124+
"this is the first child"
125+
>>> second()
126+
"this is the second child"
127+
128+
Simple decorators
129+
=================
130+
131+
Now you're ready to move on and see the magical beast that is the Python
132+
decorators. Let's start with a simple example:
133+
134+
.. code-block:: python
135+
136+
def decorator(func: Callable) -> Callable:
137+
def wrapper():
138+
print(f"before {func.__name__} call")
139+
func()
140+
print(f"after {func.__name__} call")
141+
142+
return wrapper # no wrapper call, return reference to wrapper function
143+
144+
def say_hello():
145+
print("Hello!")
146+
147+
say_hello_decorated = decorator(say_hello)
148+
149+
Running function:
150+
151+
.. code-block::
152+
153+
>>> say_hello()
154+
Hello!
155+
>>> say_hello_decorated()
156+
before say_hello call
157+
Hello!
158+
after say_hello call
159+
160+
The common way to use decorators is to replace the original function with
161+
a decorated one:
162+
163+
.. code-block::
164+
165+
>>> say_hello = decorator(say_hello)
166+
>>> say_hello()
167+
before say_hello call
168+
Hello!
169+
after say_hello call
170+
171+
``say_hello`` function is the reference to the ``decorator.<locals>.wrapper``,
172+
which itself is bound to the original ``say_hello`` function. There is a
173+
syntactic sugar to do this, called *pie-syntax*. The following example does
174+
exact the same things as the first decorator example:
175+
176+
.. code-block:: python
177+
178+
def decorator(func: Callable) -> Callable:
179+
def wrapper():
180+
print(f"before {func.__name__} call")
181+
func()
182+
print(f"after {func.__name__} call")
183+
184+
return wrapper # no wrapper call, return reference to wrapper function
185+
186+
187+
@decorator
188+
def say_hello():
189+
print("Hello!")
190+
191+
.. important::
192+
193+
There is no way to *undecorate* object in Python. Once something is bound
194+
to the decorator's wrapper - it is decorated forever.

src/basics/index.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@
1212
functions
1313
modules
1414
exceptions
15+
decorators
1516
pep8
1617
testing

src/refs.bib

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,24 @@ @misc{docs-python:private-variables
8181
title = "{Python Documentation: Private Variables}",
8282
url = {https://docs.python.org/3/tutorial/classes.html?highlight=private#private-variables},
8383
}
84+
85+
@misc{realpython:decorators,
86+
title = "{Primer on Python Decorators}",
87+
author = "{Geir Arne Hjelle }",
88+
url = {https://realpython.com/primer-on-python-decorators/},
89+
}
90+
91+
@misc{docs-python:term-decorator,
92+
title = "{Python Documentation}",
93+
url = {https://docs.python.org/glossary.html#term-decorator},
94+
}
95+
96+
@misc{docs-python:function-definition,
97+
title = "{Python Documentation}",
98+
url = {https://docs.python.org/3/reference/compound_stmts.html#function},
99+
}
100+
101+
@misc{refactoring.guru:decorator,
102+
title = "{Refactoring Guru: Decorator}",
103+
url = {https://refactoring.guru/design-patterns/decorator},
104+
}

0 commit comments

Comments
 (0)