Что именно содержится в 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 внутреннего или внешнего формата