Что именно содержится в obj.__closure__?
Описание проблемы:
На странице 100 книги Биза (Beazley) упоминается следующий код:
>>> python.__closure__
(<cell at 0x67f50: str object at 0x69230>,)
>>> python.__closure__[0].cell_contents
Я правильно понимаю, что __closure__ — это список, но что такое эти "ячейки" (cell) и "str объект"? Это выглядит как кортеж с одним элементом (1-ary tuple). Можете, пожалуйста, объяснить, как работает __closure__ и что именно представляют собой эти ячейки?
4 ответ(ов)
Closure (замыкания) — это ячейки, которые содержат значения, необходимые функции, но берущиеся из окружающей области видимости.
Когда Python компилирует вложенную функцию, он фиксирует любые переменные, которые она ссылается (но которые определены только в родительской функции, а не являются глобальными), в объектах кода как для вложенной функции, так и для родительской области видимости. Эти переменные находятся в атрибутах co_freevars и co_cellvars на объектах __code__ этих функций соответственно.
Затем, когда происходит создание вложенной функции (что происходит при выполнении родительской функции), эти ссылки используются для привязки замыкания к вложенной функции.
Замыкание функции содержит кортеж ячеек, по одной для каждой свободной переменной (указанной в co_freevars); ячейки — это специальные ссылки на локальные переменные родительской области видимости, которые следуют за значениями, на которые указывают эти локальные переменные. Это лучше всего иллюстрируется на примере:
def foo():
def bar():
print(spam)
spam = 'ham'
bar()
spam = 'eggs'
bar()
return bar
b = foo()
b()
В приведенном выше примере функция bar имеет одну ячейку замыкания, которая указывает на spam в функции foo. Ячейка следит за значением spam. Более того, как только foo() завершает исполнение и bar возвращается, ячейка продолжает ссылаться на значение (строку eggs), даже несмотря на то, что переменная spam внутри foo больше не существует.
Таким образом, приведенный код выведет:
>>> b=foo()
ham
eggs
>>> b()
eggs
и b.__closure__[0].cell_contents будет 'eggs'.
Обратите внимание, что замыкание разыменовывается когда вызывается bar(); замыкание не захватывает значение в этом случае. Это имеет значение, когда вы создаете вложенные функции (с помощью выражений lambda или операторов def), которые ссылаются на переменную цикла:
def foo():
bar = []
for spam in ('ham', 'eggs', 'salad'):
bar.append(lambda: spam)
return bar
for bar in foo():
print(bar())
В приведенном коде трижды будет выведено salad, поскольку все три функции lambda ссылаются на переменную spam, а не на значение, к которому она была привязана в момент создания объекта функции. К моменту завершения цикла for переменная spam была привязана к 'salad', и все три замыкания разрешатся к этому значению.
В Python 3 новое имя для старого func_closure — это __closure__.
Как упоминается в документации Python, атрибуты функций с префиксом func_ были переименованы в формат __X__, что позволяет освободить эти имена в пространстве имен атрибутов функции для пользовательских атрибутов. В частности, func_closure, func_code, func_defaults, func_dict, func_doc, func_globals, func_name были переименованы в __closure__, __code__, __defaults__, __dict__, __doc__, __globals__, __name__ соответственно.
Вкратце:
__closure__ — это либо None, либо кортеж ячеек, которые содержат привязки для свободных переменных функции.
Также стоит отметить, что этот атрибут не является записываемым.
Ссылка: Документация Python
Пример для Python < 3 (здесь я использую func_closure):
def foo():
x = "I am used"
y = "I am free"
z = "I am free too"
def bar(x):
return x, y, z
return bar
c = foo().func_closure
print([i.cell_contents for i in c])
Вывод:
>>>
['I am free', 'I am free too']
В этом случае функция foo возвращает функцию bar, которая использует свое собственное значение x, но не использует y или z. Поэтому они попадают в __closure__.
Когда в Python определена вложенная функция (замыкание), внешняя функция использует атрибут co_cellvars, чтобы отметить переменные, определенные во внешней функции, которые могут быть использованы внутренней функцией. В свою очередь, внутренняя функция использует атрибут co_freevars, чтобы указать переменные, определенные во внешней функции, которые могут быть использованы позже.
Рассмотрим следующий пример:
# python3
def foo(n):
a = 1
def g(n):
return a - n
return g
>>> foo.__closure__ # Проверяем замыкания внешней функции
>>> foo.__code__.co_freevars # Переменные, использующиеся внутри функции g
()
>>> foo.__code__.co_cellvars # Переменные, определенные во внешней функции foo
('a',) # 'a' - это переменная, определенная во внешней функции
>>> foo(0).__closure__ # Замыкания внутренней функции g
(<cell at 0x7f2cd98db1c8: int object at 0x7f2cd9847960>,) # Указывает на 'a'
>>> foo(0).__closure__[0].cell_contents # Получаем значение переменной 'a'
1
>>> foo(0).__code__.co_freevars # Переменные, использованные в g
('a',) # 'a' также указана здесь
>>> foo(0).__code__.co_cellvars # Проверяем переменные, определенные внутри g
()
В этом примере a — это переменная, которая определена во внешней функции foo и используется внутри внутренней функции g. Атрибут co_cellvars показывает, что он определен в foo, а co_freevars в g указывает, что a будет использоваться там. Замыкание, возвращаемое из foo, содержит ссылку на значение a, которое доступно внутренней функции g, даже после того, как выполнение foo завершится.
Да, тип cell действительно используется в Python для хранения переменных, которые нужны замыканиям. Когда функция, например, w, создается внутри другой функции, в ней могут использоваться переменные из области видимости внешней функции. Эти переменные становятся доступными для замыкания и хранятся в объектах типа cell.
Пример, который вы привели, демонстрирует это на практике. Когда вы вызываете функцию f():
>>> w = f()
Она возвращает внутреннюю функцию w, которая замыкает переменные a и b. Если вы обращаетесь к w.__closure__, вы видите список объектов типа cell, которые содержат значения переменных a и b:
>>> w.__closure__
(<cell at 0xa05c4ac: str object at 0x9e91b74>, <cell at 0xa05c3bc: float object at 0xb733dde8>)
Каждый объект cell имеет атрибут cell_contents, который хранит значение соответствующей переменной:
>>> w.__closure__[0].cell_contents
'HELO'
>>> w.__closure__[1].cell_contents
1.0
Таким образом, если вам нужно знать, какие переменные доступны для замыкания, вы можете использовать __closure__ для получения объектов типа cell и затем получить их значения через cell_contents.
Существование типа cell действительно является одним из аспектов реализации Python и может не встречаться в других языках программирования, где замыкания реализуются по-другому.
Каковы преимущества использования лямбд? [закрыто]
Оператор "is" ведет себя неожиданно с целыми числами
Есть ли что-то быстрее, чем dict()?
Как изменить порядок столбцов в DataFrame?
'pip' не распознан как командa внутреннего или внешнего формата