Замыкание и декораторы
С этими понятиями вы, скорее всего, не столкнетесь на первом кругу обучения языка программирования, а вероятнее всего и на втором. Мы разберемся с этими понятиями, не углубляясь в теоретическую часть, а на практике.
Замыкания
Замыкания представляю собой базовую конструкцию, но они являются сложными для понимания.
Давайте определим функцию, которая определяет объект, а у объекта есть поле, у поля есть значение.
>>> 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