Замыкание и декораторы
С этими понятиями вы, скорее всего, не столкнетесь на первом кругу обучения языка программирования, а вероятнее всего и на втором. Мы разберемся с этими понятиями, не углубляясь в теоретическую часть, а на практике.
Замыкания
Замыкания представляю собой базовую конструкцию, но они являются сложными для понимания.
Давайте определим функцию, которая определяет объект, а у объекта есть поле, у поля есть значение.
>>> def fun(): pass >>> fun <function fun at 0x0396EF18> >>> dir(fun) ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] >>> fun.__sizeof__ <built-in method __sizeof__ of function object at 0x0396EF18> >>> fun.__sizeof__() 56 >>>
Есть отличие: наличие метода __call__, который говорит о том, что функцию можно вызвать.
Передадим функцию в качестве параметра другим функциям.
>>> callable(fun) True >>> fun() >>> def ffun(t): return t(),t() >>> ffun(fun) (None, None) >>>
Можно написать функцию, которая конструирует функцию.
>>> def constr(): def fun(a): return a*2+1 return fun >>> constr() <function constr.<locals>.fun at 0x039638E8> >>>
При вызове constr возвращается fun. Обратим внимание на её родовое имя.
Модифицируем constr и передадим параметр.
>>> def constr(n): def fun(a): return a*2+n return fun >>> g = constr(100500) >>> g(2) 100504 >>>
Кажется, что здесь нет никакого подвоха. А так ли это?
Вспомним, как устроено локальное пространство имен. Все имена очищаются в момент выхода из функции.
Python анализирует функцию на содержание глобальных и локальных имен. Это делается для того, что бы случайно не перебить имя. И Python выясняет, что имя n приехало в пространство имен local, а потом происходит залипание. Т.е. появляется щель и n всегда будет доступно при вызове fun. Это и есть замыкание.
Посмотрим что интерпретируется у функции g.
>>> dir(g) ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] >>> dir(g.__closure__) ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index'] >>> g.__closure__[0] <cell at 0x036FFA90: int object at 0x036DA5A0> >>> dir(a) ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'cell_contents'] >>> a.cell_contents 100500 >>> type(g.__closure__) <class 'tuple'> >>> len(g.__closure__) 1 >>>
В tuple лежат не сами объекты, а ячейки. Покажем, что closure бывают развесистые.
>>> def f1(x): def f2(): def f3(): return x return f3 return f2 >>> f = f1(100500) >>> f <function f1.<locals>.f2 at 0x039639C0> >>> dir(f) ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] >>> len(f.__closure__) 1 >>> f.__closure__[0].cell_contents 100500 >>> ff=f() >>> ff <function f1.<locals>.f2.<locals>.f3 at 0x03963A08> >>>
В f1 есть локальное имя х, в f2 нет ничего, она создается в f3, а в f3 х используется и в итоге происходит залипание. Благодаря замыканию, мы можем создать конструктор функции, не думая, что происходит с пространством имен. По сути, мы приближаемся к функциональному программированию.
Вопрос: Обязательно ли имя локальное или глобальное?
Имя может быть не из этих пространств, а именно из nonlocal.
def f1(): a = 42 def f2(): nonlocal a a+=1 f2() f2() f2() return a
Хитрость:
def f1(x): def f2(a): return a+x return f2 >>> s = f1(2) >>> s.__closure__ (<cell at 0x0363FA90: int object at 0x63C65700>,) >>> s.__closure__[0].cell_contents 2 >>>
А если не такой эффект
def f1(x): def f2(a, t=x): return a+t return f2 >>> s=f1(2) >>> s(2) 4 >>> s(5) 7 >>> len(s.__closure__) Traceback (most recent call last): File "<pyshell#15>", line 1, in <module> len(s.__closure__) TypeError: object of type 'NoneType' has no len() >>>
Функциональность не изменилась. Этой функции не требуется замыкание. Этот объект теперь называется t.
Декораторы
Определим функцию, которая что-то делает
def fun(a,b,c): return a+b+c z = fun(1,2,3) print(z)
Хотим поправить fun так, что бы:
- С какими параметрами вызывали?
- Что собирается возвращать?
Теперь функция стала параметром
def fun(a,b,c): return a+b+c def fwrap(t,*argp,**argn): print("WRAP",*argp,**argn) res = f(*argp,**arrgn) print("WRAP",res) return res z = fwrap(fun,1,2,3) print(z)
Пойдем более хитрым путем.
Создадим конструктор функции, который взял исходную функцию и вызвал её.
def fun(a,b,c): return a+b+c def fwrap(t,*argp,**argn): print("WRAP",*argp,**argn) res = f(*argp,**arrgn) print("WRAP",res) return res def fdec(f): def fun(*argp,**argn): print("WRAP",*argp,**argn) res = f(*argp,**argn) print("WRAP",res) return res return res z = fwrap(fun,1,2,3) print(z)
Напишем красивее
def fun(a,b,c): return a+b+c def fwrap(t,*argp,**argn): print("WRAP",*argp,**argn) res = f(*argp,**arrgn) print("WRAP",res) return res def fdec(f): def fun(*argp,**argn): print("WRAP",*argp,**argn) res = f(*argp,**argn) print("WRAP",res) return res return res z = fwrap(fun,1,2,3) y = fdec(fun)(1,2,3) print(z,y)
Место, где происходит вызов fun, приходится преобразовывать.
#z = fwrap(fun,1,2,3) #y = fdec(fun)(1,2,3) fun = fdec(fun) z = fun(1,2,3) print(z)
Когда вызывается fun, всякий раз вызывается декодированная функция. Остается синтаксический сахар.
Хочется заранее знать, что fun обернута в декоратор. А здесь сначала определяется декоратор, потом fun, а потом производится обертывание.
В Python был принят синтаксис
#z = fwrap(fun,1,2,3) #y = fdec(fun)(1,2,3) #fun = fdec(fun) z = fun(1,2,3) y = fun(4,5,6) print(z,y)
Сделаем конструктор декоратора
def deco(n): def dec2(f): def fun(*ap,**an): print(****n) return f(*ap,**an) return fun return dec2 @deco(4) def fun(a): return a*2+1 z = fun(42) print(z)
deco используется как декоратор вокруг fun.
Дескрипторы
class Desc: v = 0 def __get__(self,o,c): return self.v def __set__(self,o,v): self.v = v def __delete__(self,o): pass class C: p = Desc()
Вывод
>>> c = C() >>> c.p 0 >>> c.p=9 >>> c.p 9 >>> dir(c) ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'p'] >>> c.__dict__ {} >>>
Дескриптор не присваивает в dict, так как он является полем класса. Как определить self?
Есть 3 метода, а задекорирован только один. Значит, надо придумать еще 2 декоратора.
class Desc: v = 0 def __get__(self,o,c): return self.v def __set__(self,o,v): self.v = v def __delete__(self,o): pass class C: v = 0 @property def x(self): return self.v
Допишем декораторы
class Desc: v = 0 def __get__(self,o,c): return self.v def __set__(self,o,v): self.v = v def __delete__(self,o): pass class C: p = Desc() class D: v = 0 @property def x(self): return self.v @x.setter def x(self,v): self.v = v @x.deleter def x(self): pass