Skip to content

Commit 330f412

Browse files
committed
Add functions and decorators python
1 parent ae2122d commit 330f412

File tree

3 files changed

+741
-1
lines changed

3 files changed

+741
-1
lines changed

src/SUMMARY.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
- [Объекты и память](./dev/python/objects.md)
3838
- [Коллекции](./dev/python/collections.md)
3939
- [Асимптотическая сложность](./dev/python/o-notation.md)
40-
40+
- [Функции](./dev/python/functions.md)
41+
- [Декораторы и functools](./dev/python/decorators.md)
4142

4243
# Учимся у других
4344

src/dev/python/decorators.md

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
# Декораторы и модуль `functools`
2+
3+
Декораторы в Python — это мощный инструмент для модификации поведения функций и методов, позволяющий писать чистый, повторно используемый и легко читаемый код. В этой главе мы глубоко погрузимся в мир декораторов, изучим их синтаксис, создание, применение, а также познакомимся с полезными инструментами из модуля `functools`.
4+
5+
## Синтаксис декораторов
6+
7+
Декоратор — это функция, которая принимает другую функцию и возвращает новую функцию (или любой другой объект). Синтаксис декоратора — это «синтаксический сахар», который делает код более понятным.
8+
9+
```python
10+
@decorator
11+
def foo(x):
12+
return 42
13+
```
14+
15+
Эквивалентно:
16+
17+
```python
18+
def foo(x):
19+
return 42
20+
21+
foo = decorator(foo)
22+
```
23+
24+
Таким образом, после применения декоратора имя `foo` будет ссылаться на результат вызова `decorator(foo)`.
25+
26+
## «Теория» декораторов: создание простого декоратора
27+
28+
Рассмотрим пример декоратора `trace`, который логирует вызовы функции:
29+
30+
```python
31+
def trace(func):
32+
def inner(*args, **kwargs):
33+
print(func.__name__, args, kwargs)
34+
return func(*args, **kwargs)
35+
return inner
36+
```
37+
38+
Применим его:
39+
40+
```python
41+
@trace
42+
def identity(x):
43+
"I do nothing useful."
44+
return x
45+
46+
identity(42) # Вывод: identity (42,) {} → 42
47+
```
48+
49+
### Проблема атрибутов функции
50+
51+
После применения декоратора исходные атрибуты функции (такие как `__name__`, `__doc__`) теряются:
52+
53+
```python
54+
identity.__name__ # 'inner'
55+
help(identity) # Справка о inner, а не identity
56+
```
57+
58+
### Решение: `functools.wraps`
59+
60+
Модуль `functools` предоставляет декоратор `wraps`, который копирует метаданные исходной функции в декорированную:
61+
62+
```python
63+
import functools
64+
65+
def trace(func):
66+
@functools.wraps(func)
67+
def inner(*args, **kwargs):
68+
print(func.__name__, args, kwargs)
69+
return func(*args, **kwargs)
70+
return inner
71+
```
72+
73+
Теперь `identity.__name__` и `help(identity)` работают корректно.
74+
75+
## Декораторы с аргументами
76+
77+
Иногда нужно параметризовать декоратор. Например, указать файл для вывода в `trace`:
78+
79+
```python
80+
def trace(handle):
81+
def decorator(func):
82+
@functools.wraps(func)
83+
def inner(*args, **kwargs):
84+
print(func.__name__, args, kwargs, file=handle)
85+
return func(*args, **kwargs)
86+
return inner
87+
return decorator
88+
89+
@trace(sys.stderr)
90+
def identity(x):
91+
return x
92+
```
93+
94+
Эквивалентно:
95+
96+
```python
97+
decorator = trace(sys.stderr)
98+
identity = decorator(identity)
99+
```
100+
101+
## Декораторы с опциональными аргументами
102+
103+
Чтобы декоратор можно было использовать как с аргументами, так и без них, применяют следующий паттерн:
104+
105+
```python
106+
def trace(func=None, *, handle=sys.stdout):
107+
if func is None:
108+
return lambda func: trace(func, handle=handle)
109+
110+
@functools.wraps(func)
111+
def inner(*args, **kwargs):
112+
print(func.__name__, args, kwargs, file=handle)
113+
return func(*args, **kwargs)
114+
return inner
115+
```
116+
117+
Использование:
118+
119+
```python
120+
@trace
121+
def foo(): ...
122+
123+
@trace(handle=sys.stderr)
124+
def bar(): ...
125+
```
126+
127+
**Зачем только ключевые аргументы?** Это предотвращает неоднозначность при вызове и делает код чище.
128+
129+
## Практика: полезные декораторы
130+
131+
### @timethis — замер времени выполнения
132+
133+
```python
134+
import time
135+
136+
def timethis(func=None, *, n_iter=100):
137+
if func is None:
138+
return lambda func: timethis(func, n_iter=n_iter)
139+
140+
@functools.wraps(func)
141+
def inner(*args, **kwargs):
142+
print(func.__name__, end=" ... ")
143+
acc = float("inf")
144+
for i in range(n_iter):
145+
tick = time.perf_counter()
146+
result = func(*args, **kwargs)
147+
acc = min(acc, time.perf_counter() - tick)
148+
print(acc)
149+
return result
150+
return inner
151+
```
152+
153+
### @once — выполнить не более одного раза
154+
155+
```python
156+
def once(func):
157+
@functools.wraps(func)
158+
def inner(*args, **kwargs):
159+
if not inner.called:
160+
inner.result = func(*args, **kwargs)
161+
inner.called = True
162+
return inner.result
163+
inner.called = False
164+
return inner
165+
```
166+
167+
### @memoized — мемоизация
168+
169+
```python
170+
def memoized(func):
171+
cache = {}
172+
@functools.wraps(func)
173+
def inner(*args, **kwargs):
174+
key = args + tuple(sorted(kwargs.items()))
175+
if key not in cache:
176+
cache[key] = func(*args, **kwargs)
177+
return cache[key]
178+
return inner
179+
```
180+
181+
**Проблема:** Словари нехэшируемы. Для универсального решения можно сериализовать аргументы в строку (например, через `pickle`).
182+
183+
### @deprecated — пометить функцию как устаревшую
184+
185+
```python
186+
import warnings
187+
188+
def deprecated(func):
189+
code = func.__code__
190+
warnings.warn_explicit(
191+
f"{func.__name__} is deprecated.",
192+
category=DeprecationWarning,
193+
filename=code.co_filename,
194+
lineno=code.co_firstlineno + 1
195+
)
196+
return func
197+
```
198+
199+
## Контрактное программирование с декораторами
200+
201+
Реализуем простые контракты `@pre` и `@post`:
202+
203+
```python
204+
def pre(cond, message):
205+
def decorator(func):
206+
@functools.wraps(func)
207+
def inner(*args, **kwargs):
208+
assert cond(*args, **kwargs), message
209+
return func(*args, **kwargs)
210+
return inner
211+
return decorator
212+
213+
def post(cond, message):
214+
def decorator(func):
215+
@functools.wraps(func)
216+
def inner(*args, **kwargs):
217+
result = func(*args, **kwargs)
218+
assert cond(result), message
219+
return result
220+
return inner
221+
return decorator
222+
```
223+
224+
Использование:
225+
226+
```python
227+
@pre(lambda x: x >= 0, "negative argument")
228+
@post(lambda r: not math.isnan(r), "result is NaN")
229+
def log_positive(x):
230+
return math.log(x)
231+
```
232+
233+
## Цепочки декораторов
234+
235+
Порядок применения декораторов имеет значение:
236+
237+
```python
238+
@deco1
239+
@deco2
240+
def foo(): ...
241+
```
242+
243+
Эквивалентно:
244+
245+
```python
246+
foo = deco1(deco2(foo))
247+
```
248+
249+
## Модуль `functools`
250+
251+
### `lru_cache` — мемоизация с ограничением
252+
253+
```python
254+
@functools.lru_cache(maxsize=128)
255+
def expensive_func(x):
256+
return x * x
257+
```
258+
259+
- `maxsize=None` — неограниченный кэш (опасно при большом количестве вызовов!).
260+
- `cache_info()` — статистика использования кэша.
261+
262+
### `partial` — частичное применение
263+
264+
```python
265+
f = functools.partial(sorted, key=lambda x: x[1])
266+
f([('a', 4), ('b', 2)]) # [('b', 2), ('a', 4)]
267+
```
268+
269+
### `singledispatch` — обобщённые функции
270+
271+
Позволяет определять разные реализации функции для разных типов:
272+
273+
```python
274+
@functools.singledispatch
275+
def pack(obj):
276+
raise TypeError(f"Unsupported type: {type(obj)}")
277+
278+
@pack.register(int)
279+
def _(obj):
280+
return b"I" + hex(obj).encode("ascii")
281+
282+
@pack.register(list)
283+
def _(obj):
284+
return b"L" + b",".join(map(pack, obj))
285+
```
286+
287+
### `reduce` — свёртка последовательности
288+
289+
```python
290+
functools.reduce(lambda acc, x: acc * x, [1, 2, 3, 4]) # 24
291+
```
292+
293+
Хотя `reduce` популярен в функциональных языках, в Python его используют редко, предпочитая явные циклы для ясности.
294+
295+
## Заключение
296+
297+
Декораторы — это не просто «синтаксический сахар», а фундаментальный инструмент метапрограммирования в Python. Они позволяют:
298+
299+
- Модифицировать поведение функций без изменения их кода.
300+
- Реализовывать аспектно-ориентированное программирование.
301+
- Создавать выразительные и легко читаемые API.
302+
303+
Модуль `functools` предоставляет готовые решения для многих задач, связанных с функциональным программированием и декорированием.
304+
305+
**Дополнительные материалы:**
306+
- [Python Decorator Library](https://wiki.python.org/moin/PythonDecoratorLibrary)
307+
- [PEP 443 – Single-dispatch generic functions](https://peps.python.org/pep-0443/)
308+
- [PEP 318 – Decorators for Functions and Methods](https://peps.python.org/pep-0318/)

0 commit comments

Comments
 (0)