0

Что именно содержится в obj.__closure__?

10

Описание проблемы:

На странице 100 книги Биза (Beazley) упоминается следующий код:

>>> python.__closure__
(<cell at 0x67f50: str object at 0x69230>,)
>>> python.__closure__[0].cell_contents

Я правильно понимаю, что __closure__ — это список, но что такое эти "ячейки" (cell) и "str объект"? Это выглядит как кортеж с одним элементом (1-ary tuple). Можете, пожалуйста, объяснить, как работает __closure__ и что именно представляют собой эти ячейки?

4 ответ(ов)

0

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', и все три замыкания разрешатся к этому значению.

0

В 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__.

0

Когда в 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 завершится.

0

Да, тип 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 и может не встречаться в других языках программирования, где замыкания реализуются по-другому.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь