Замыкание и декораторы
- Функция — это объект
- Её можно изготовить внутри другой функции и вернуть
- …причём в зависимости от параметров этой другой функции!
- …в процессе чего некоторые объекты из ПИ создающей функции «залипают» в ПИ создаваемой
- только они там навсегда должны залипнуть, а не только на время вызова
⇒ .__closure__
- Это и есть замыкание!
Пример:
и
Also: nonlocal name — явное указание брать имя name из внешнего, но не глобального пространства имён
В частности, позднее связывание:
Поскольку i для сгенерированных функций нелокальное, оно попадает в замыканий, и это один и тот же объект во всех adder-ах:
>>> c = create_adders() >>> c[1] <function create_adders.<locals>.adder at 0x7f272d2f93b0> >>> c[1].__closure__ (<cell at 0x7f272d1c1510: int object at 0x7f272db36660>,) >>> c[2].__closure__ (<cell at 0x7f272d1c1510: int object at 0x7f272db36660>,) >>> c[2].__closure__[0].cell_contents 9 >>> c[1].__closure__[0].cell_contents 9
Если мы хотели не этого, надо сделать так, чтобы при создании очередного adder-а его i именовало новый объект:
При этом никакого замыкания не произойдёт, у каждого adder-а будет своё локальное j, инициализированное соответствующим значением i. (Если бы нам нужно было сильнее запутаться, мы могли бы написать i=i вместо j=i ☺ ).
Декораторы
Что, если мы хотим «обмазать» все вызовы некоторой функции отладочной информацией?
Неудобно! Поиск с заменой fun(a,b) на dfun(fun,a,b).
Создадим обёрнутую функцию вместо старой:
Всё равно поиск с заменой, хотя и попроще. Тогда просто перебьём имя fun!
Вот это и есть декоратор, записывается так:
Закомментировали @genf — убрали декоратор!
BTW, Запись вида
означает то, что вы подумали: функцию функция(), обмазанную сначала декоратором декоратор1(), а затем — декоратор2().
Параметрические декораторы
Конструкторы декораторов!
- вместо объекта-функции мы пишем вызов этого объекта, значит, возвращать он должен функцию!
вторая часть статьи (+декораторы методов) примеры
∃ декораторы методов в классах. Но это потом.
Параметрические генераторы
В генератор можно затолкать значение на каждом обороте (оно прочтётся yield-ом).
1 >>> def biased(init):
2 ... bias = yield init
3 ... while bias:
4 ... init += bias*2+1
5 ... bias = yield init
6 ...
7 >>> g = biased(10)
8 >>> next(g) # или, что то же самое, g.send(None)
9 10
10 >>> g.send(5)
11 21
12 >>> g.send(5)
13 32
14 >>> g.send(-1)
15 31
16 >>> g.send(100500)
17 201032
18 >>> g.send(0)
19 Traceback (most recent call last):
20 File "<stdin>", line 1, in <module>
21 StopIteration
Первый вызов — только next() (ещё ничего не передали), остальные — .send()
при этом next() означает .send(None)
Зачем это может быть нужно??
Д/З
- Прочитать про все эти удивительные вещи по ссылкам и прощёлкать примеры оттуда
Замыкания: Gabor Laszlo Hajba и Dmitry Soshnikov
Декораторы хабр и learnpython
Параметрические декораторы хабр
TODO
EJudge: SimpleDecorator 'Простой декоратор'
Написать функцию-декоратор nonify(func), которая заменяет возвращаемое значение функции func на None, если оно было пустое (и не меняет в противном случае).
@nonify def aNb(a, n, b): return a*n+b print(aNb(1,2,3), aNb("QWE",0,""))
5 None
EJudge: FixFloat 'Фиксированная точность'
Написать функцию-параметрический декоратор fix(n), с помощью которой все вещественные (как позиционные, так и именные) параметры произвольной декорируемой функции, а также её возвращаемое значение, округляются до n-го знака после запятой. Если какие-то параметры функции оказались не вещественными, или не вещественно возвращаемое значение, эти объекты не меняются.
@fix(4) def aver(*args, sign=1): return sum(args)*sign print(aver(2.45675901, 3.22656321, 3.432654345, 4.075463224, sign=-1))
-13.1916
EJudge: VirtualTurtle 'Примитивная черепашка'
Написать параметрический генератор turtle(coord, direction), описывающий движение «черепахи» по координатной плоскости. coord — это кортеж из двух целочисленных начальных координат, direction описывает первоначальное направление (0 — восток, 1 — север, 2 — запад, 3 — юг). Координаты увеличиваются на северо-восток. Генератор принимает три команды — "f" (переход на 1 шаг вперёд), "l" (поворот против часовой стрелки на 90°) и "r" (поворот по часовой стрелке на 90°) и возвращает текущие координаты черепахи.
1 0 1 0 1 1 1 1 2 1 3 1 3 1 3 0 3 -1 3 -1
Исследовательская задача.
EJudge: StatCounter 'Статистика вызовов'
Написать, держитесь крепче, генератор-декоратор statcounter(), который конструирует объекты (назовём один из них stat) со следующим поведением. Первый вызов next(stat) (он же stat.send(None)) возвращает словарь, в котором stat будет хранить информацию вида функция: количество вызовов, где функция — это исходный (не обёрнутый) объект-функция (да, так тоже можно!). Все последующие вызовы stat.send(function) оборачивают вызов произвольной функции function увеличением на 1 соответствующего элемента словаря. Глобальными именами пользоваться нельзя. В примере видны уникальные id объектов, в тестах их не будет (я воспользуюсь function.__name__ или просто не буду их учитывать).
stat = statcounter() stats = next(stat) @stat.send def f1(a): return a+1 @stat.send def f2(a, b): return f1(a)+f1(b) print(f1(f2(2,3)+f2(5,6))) print(stats)
21 {<function f2 at 0x7fc3151ebb90>: 2, <function f1 at 0x7fc315283e60>: 5}