Зачем использовать объект ячейки в реализации закрытия Python? - PullRequest
0 голосов
/ 11 ноября 2018
def outer():
    n = 1
    def inner():
        return n
    n = 2
    return inner

inner = outer()
print innner()  # output 2

Я хорошо знаю, что, как CPython реализует замыкание, мой вопрос не в том, почему вывод равен 2, а в том, почему Python создает вывод 2 .

Python использует объект ячейки во внедрении замыкания, который косвенно ссылается именно на PyObject, который мы хотим захватить. PythonVM создает ровно один объект ячейки для одного freevar, в этом примере во внешней области видимости объекта ячейки сначала ref 1, а затем ref 2. Когда мы вызываем внутреннюю функцию, freevar всегда загружает самое новое значение во внешней функции, поэтому выводим 2.

«Объект ячейки» - это дополнительный абстрактный уровень в реализации замыкания. На самом деле я изменил несколько строк кода CPython о процессе кода STORE_DEREF и LOAD_DEREF, удалил уровень «объекта ячейки», сохранил реальный объект во внутреннем закрытии. Тогда пример выдаст 1. Все работает нормально, кроме простой трассировки в стандартной библиотеке, в некотором коде предполагается, что ячейка является хэшируемой. Но я думаю, что это не имеет большого значения.

Я думаю, что вывод "1" - это интуитивный смысл. Итак, мой вопрос: почему Python делает уровень «объект-ячейка» в реализации замыкания? Я четко знаю реализацию, но почему такой дизайн Python?

1 Ответ

0 голосов
/ 12 ноября 2018

Ментальная модель состоит в том, что вложенная функция (включая lambda, которая является чисто синтаксической разницей) использует ту же самую переменную в качестве внешней функции и, следовательно, наблюдает изменения в ее значении даже после создания функции. Это может быть полезно: вложенная функция всегда актуальна с помощью назначений в длинной функции (которая содержит и вызывает ее). Это также, однако, дает известную проблему с lambda s, созданными в цикле: они все совместно используют переменную одного цикла.

Эта модель не более или менее мощная, чем та, в которой функция захватывает значение: чтобы эмулировать этот режим, вы просто создаете другую переменную только для использования вложенной функции (что означает другой вызов функции, если задействован цикл), в то время как чтобы эмулировать поведение Python с помощью захвата значений, вы просто захватываете контейнер, чей (единственный) элемент может быть мутирован.

Это вопрос философии и языковой согласованности в отношении того, какое поведение предпочтительнее синтаксиса. Решение здесь состоит в том, чтобы все чтения были переменной; C ++, напротив, поддерживает оба поведения даже в пределах одной лямбды и даже позволяет (с mutable) обновлять копий захваченных значений.

...