0.99M

decorators

1.

Замыкания и
Декораторы
Докладчик: Евграфов Михаил

2.

Замыкания
2

3.

Область видимости функций
var_global = 42
def print_vars(
var_local: int
) -> None:
print(
f"{var_local = }",
f"{var_global = }",
sep="\n",
)
глобальные
переменные
локальные
переменные
3

4.

Чтение глобальных переменных
def print_vars(var_local: int) -> None:
print(f"{var_local = }")
print(f"{var_global = }")
var_global = 42
print_vars(var_local=5)
# var_local = 5
# var_global = 42
4

5.

Изменение глобальных переменных
def modify_global_list() -> None:
lst.append(42)
lst = list(range(3))
print(f"lst: {lst}")
# lst: [0, 1, 2]
modify_global_list()
print(f"lst: {lst}")
# lst: [0, 1, 2, 42]
5

6.

Перепривязка
def print_object_info(obj: object) -> None:
print(f"{obj = }", f"obj ID: {id(obj)}", sep="\n")
def try_to_rebound_global_list() -> None:
lst = [0 - 1j, 2.72, 3.14]
print_object_info(lst)
lst = list(range(3))
print_object_info(lst)
try_to_rebound_global_list()
print_object_info(lst)
6

7.

Результаты перепривязки
# obj = [0, 1, 2]
# obj ID: 2827461041664
# obj = [-1j, 2.72, 3.14]
# obj ID: 2827461397440
# obj = [0, 1, 2]
# obj ID: 2827461041664
7

8.

Когда переменная глобальная
def print_vars(var_local: int) -> None:
print(f"{var_local = }")
print(f"{var_global = }")
var_global = 42
print_vars(var_local=1337)
# var_local = 1337
# var_global = 42
8

9.

Когда переменная локальная
def print_vars(var_local: int) -> None:
var_global = 3.14
print(f"{var_local = }")
print(f"{var_global = }")
var_global = 42
print_vars(var_local=1337)
# var_local = 1337
# var_global = 3.14
9

10.

И так, и так не бывает
def print_vars(var_local: int) -> None:
print(f"{var_local = }")
print(f"{var_global = }")
var_global = 3.14
print(f"{var_global = }")
var_global = 42
print_vars(var_local=1337)
...
UnboundLocalError: ...
10

11.

global
def print_vars(var_local: int) -> None:
global var_global
print(f"{var_local = }")
print(f"{var_global = }")
var_global = 3.14
var_global = 42
print_vars(var_local=1337)
print(f"{var_global = }")
# var_local = 1337
# var_global = 42
# var_global = 3.14
11

12.

Вложенные функции
def get_printer() -> Callable[[int], None]:
var_nonloc = 42
def print_vars(var_local: int) -> None:
print(f"{var_local = }")
print(f"{var_nonloc = }")
нелокальные
переменные
локальные
переменные
return print_vars
12

13.

Когда переменная нелокальная
def get_printer() -> Callable[[int], None]:
var_nonloc = 42
def print_vars(var_local: int) -> None:
print(f"{var_local = }")
print(f"{var_nonloc = }")
return print_vars
print_vars = get_printer()
print_vars(var_local=5)
# var_local = 5
# var_nonloc = 42
13

14.

Когда переменная локальная
def get_printer() -> Callable[[int], None]:
var_nonloc = 42
def print_vars(var_local: int) -> None:
var_nonloc = 3.14
print(f"{var_local = }")
print(f"{var_nonloc = }")
return print_vars
print_vars = get_printer()
print_vars(var_local=5)
# var_local = 5
# var_nonloc = 3.14
14

15.

И так, и так не бывает
def get_printer() -> Callable[[int], None]:
var_nonloc = 42
def print_vars(var_local: int) -> None:
print(f"{var_local = }")
print(f"{var_nonloc = }")
var_nonloc = 3.14
return print_vars
print_vars = get_printer()
print_vars(var_local=5)
...
UnboundLocalError: ...
15

16.

nonlocal
def get_printer() -> Callable[[int], None]:
var_nonloc = 42
def print_vars(var_local: int) -> None:
nonlocal var_nonloc
print(f"{var_local = }")
print(f"{var_nonloc = }")
var_nonloc = 3.14
return print_vars
16

17.

nonlocal: результат
print_vars = get_printer()
print_vars(var_local=5)
# var_local = 5
# var_nonloc = 42
print_vars(var_local=5)
# var_local = 5
# var_nonloc = 3.14
17

18.

Когда использовать global
def get_counter_curr() -> int:
global counter
counter += 1
return counter
counter = 0
for _ in range(3):
print(f"counter: {get_counter_curr()}")
# counter: 1
# counter: 2
# counter: 3
18

19.

Когда global не работает
def get_counter_curr() -> int:
global counter
counter += 1
return counter
counter = 0
get_counter1 = get_counter_curr
get_counter2 = get_counter_curr
print(get_counter1(), get_counter2(), sep="\n")
# 1
# 2
19

20.

Замыкания
фабричная функция
def get_counter_func() -> Callable[[], int]:
counter = 0
def get_counter_curr() -> int:
nonlocal counter
counter += 1
return counter
контекст
замыкания
замыкание
return get_counter_curr
20

21.

Замыкания: результат
get_counter1 = get_counter_func()
get_counter2 = get_counter_func()
for _ in range(3):
get_counter1()
for _ in range(5):
get_counter2()
print(get_counter1(), get_counter2(), sep="\n")
# 4
# 6
21

22.

Где хранится контекст замыкания
print(f"locals: {get_counter1.__code__.co_varnames}")
# locals: ()
print(f"free vars: {get_counter1.__code__.co_freevars}")
# free vars: ('counter',)
print(f"context: {get_counter1.__closure__}")
# context: (<cell at 0x00000292518E9BA0: ...)
for i, cell in enumerate(get_counter1.__closure__):
print(f"cell_{i} content: {cell.cell_contents}")
# cell_0 content: 4
22

23.

Декораторы
23

24.

Описание проблемы
import time
def do_something() -> None:
print("start doing
something")
time.sleep(1)
print("stop doing something")
хотим измерять
время работы
do_something()
# start doing something
# stop doing something
24

25.

Возможное решение
import time
def do_something() -> None:
print("start doing something")
time.sleep(1)
print("stop doing something")
time_start = time.time()
do_something()
print(f"time taken: {time.time() - time_start:.2f}")
# start doing something
# stop doing something
# time taken: 1.00
25

26.

Минусы: дублирование логики
import time
time_start = time.time()
do_something()
print(f"time taken: {time.time() - time_start:.2f}")
time_start = time.time()
do_another_thing()
print(f"time taken: {time.time() - time_start:.2f}")
26

27.

Декоратор
import time
from typing import Callable, ParamSpec, TypeVar
P = ParamSpec("P")
R = TypeVar("R")
def timeit(func: Callable[P, R]) -> Callable[P, R]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
time_start = time.time()
result = func(*args, **kwargs)
print(f"time taken: {time.time() - time_start:.2f}")
return result
return wrapper
27

28.

Декоратор подробнее
декоратор
декорируемая функция
def timeit(func: Callable[P, R]) -> Callable[P, R]:
def wrapper(*args: ..., **kwargs: ...) -> R:
time_start = time.time()
result = func(*args, **kwargs)
print(time.time() - time_start)
return result
декорирующая
функция
return wrapper
декорирующая функция дополняет поведение
декорируемой функции
28

29.

Декоратор как замыкание
контекст замыкания
def timeit(func: Callable[P, R]) -> Callable[P, R]:
def wrapper(*args: ..., **kwargs: ...) -> R:
time_start = time.time()
result = func(*args, **kwargs)
print(time.time() - time_start)
return result
замыкание
return wrapper
декорирующая функция замыкается на декорируемую
функцию
29

30.

Использование декоратора
@timeit
def do_something() -> None:
print("start doing something")
time.sleep(1)
print("stop doing something")
do_something()
# start doing something
# stop doing something
# time taken: 1.00
30

31.

@Callable
@timeit
def do_something() -> None:
print("start doing something")
time.sleep(1)
print("stop doing something")
def do_something() -> None:
print("start doing something")
time.sleep(1)
print("stop doing something")
do_something = timeit(do_something)
31

32.

@Callable
@timeit
def do_something() -> None:
print("start doing something")
time.sleep(1)
print("stop doing something")
def do_something() -> None:
print("start doing something")
time.sleep(1)
print("stop doing something")
do_something = timeit(do_something)
32

33.

Подмена
@timeit
def do_something() -> None:
print("start doing something")
time.sleep(1)
print("stop doing something")
print(do_something.__name__)
# wrapper
33

34.

wraps
from functools import wraps
def timeit(func: Callable[P, R]) -> Callable[P, R]:
@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
time_start = time.time()
result = func(*args, **kwargs)
print(f"time taken: {time.time() - time_start:.2f}")
return result
return wrapper
34

35.

wraps: результат
@timeit
def do_something() -> None:
print("start doing something")
time.sleep(1)
print("stop doing something")
print(do_something.__name__)
# do_something
35

36.

Момент выполнения декораторов
def decorate(func: Callable[P, R]) -> Callable[P, R]:
print("decorate was applied")
return func
@decorate
def do_something() -> None:
print("do something")
# decorate was applied
@decorate
def do_another_thing() -> None:
print("do another thing")
# decorate was applied
36

37.

Композиция декораторов
def inner(func: Callable[P, R]) -> Callable[P, R]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
print("call inner")
return func(*args, **kwargs)
return wrapper
def outer(func: Callable[P, R]) -> Callable[P, R]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
print("call outer")
return func(*args, **kwargs)
return wrapper
37

38.

Композиция декораторов
@outer
@inner
def do_something() -> None:
print("do something")
do_something()
# call outer
# call inner
# do something
38

39.

Логика композиции
@outer
@inner
def do_something() -> None:
print("do something")
def do_something() -> None:
print("do something")
do_something = outer(inner(do_something))
39

40.

Логика композиции
@outer
@inner
def do_something() -> None:
print("do something")
def do_something() -> None:
print("do something")
do_something = outer(inner(do_something))
40

41.

Параметризация декораторов
def register(
name: str,
registry: dict[str, Callable[P, R]],
) -> Callable[[Callable[P, R]], Callable[P, R]]:
def _register(func: Callable[P, R]):
registry[name] = func
return func
return _register
41

42.

Использование
registry = dict[str, Callable[P, R]]()
@register("do_something", registry)
def do_something() -> None:
print("do something")
print(registry)
# {'do_something': <function do_something ...>}
42

43.

Логика использования
registry = dict[str, Callable[P, R]]()
@register("do_something", registry)
def do_something() -> None:
print("do something")
def do_something() -> None:
print("do something")
register_decorator = register("do_something", registry)
do_something = register_decorator(do_something)
43

44.

Логика использования
registry = dict[str, Callable[P, R]]()
@register("do_something", registry)
def do_something() -> None:
print("do something")
def do_something() -> None:
print("do something")
register_decorator = register("do_something", registry)
do_something = register_decorator(do_something)
44

45.

Семинар
45
English     Русский Rules