Переменное время привязки в Python замыканиях - PullRequest
1 голос
/ 28 января 2020

Из Python FAQ по программированию , это не работает, как ожидают многие:

>>> squares = []
>>> for x in range(5):
...     squares.append(lambda: x**2)
>>> squares[2]() # outputs 16, not 4

Предлагается следующее объяснение:

Это происходит потому, что x не является локальным для лямбд, но определяется во внешней области видимости, и к нему обращаются, когда вызывается лямбда, а не когда он определен.

Это хорошо. Тем не менее, объяснение не звучит совершенно правильно для меня. Подумайте об этом:

def buildFunction():
    myNumber = 6 # Won't compile without this line
    def fun():
        return myNumber
    return fun

def main():
    myNumber = 3 # Ignored
    myFun = buildFunction()
    myNumber = 3 # Ignored
    print(myFun()) # prints 6, not 3

Это имеет смысл, но кто-то может предложить более точное / общее определение того, когда происходит привязка переменной Python замыканий?

Ответы [ 2 ]

2 голосов
/ 28 января 2020

Учтите это:

def main():
    def buildFunction():
        def fun():
            return myNumber

        return fun

    myNumber = 3  # can use this...
    myFun = buildFunction()
    myNumber = 3  # ... or this
    print(myFun())  # prints 3 this time

Это более сопоставимо с лямбда-примером, потому что функция замыкания вложена в область действия, которая объявляет интересующую переменную. В вашем примере было две разные области действия, поэтому было две совершенно не связанные, myNumbers.

. Если вы не встретили ключевое слово nonlocal, можете ли вы догадаться, что это будет печатать?

def main():
    def buildFunction():
        nonlocal myNumber
        myNumber = 6

        def fun():
            return myNumber

        return fun

    myNumber = 3
    myFun = buildFunction()
    # myNumber = 3
    print(myFun())
1 голос
/ 28 января 2020

Каждая переменная имеет область видимости. Поиск имени (получение значения, на которое указывает имя) по умолчанию разрешается в самой внутренней области видимости. Вы можете переопределить имена только в локальной области или в содержащей области (используя ключевые слова nonlocal и global). Во втором примере в main вы назначаете myNumber другой области действия, к которой может обращаться ваша забавная функция.

squares = []
 for x in range(5):
    squares.append(lambda: x**2) # scope = global / lambda
print(squares[2]())

def buildFunction():
    myNumber = 6 # scope = global / buildFunction
    def fun():
        return myNumber # scope = global / buildFunction / fun
    return fun

myNumber = 1 # scope = global
def main():
    myNumber = 3 # scope = global / main. Out of scope for global / buildFunction
    myFun = buildFunction()
    print(myFun())

Когда вызывается fun, Python просматривает локальную область действия fun и не могу найти myNumber. Таким образом, он смотрит на его содержание. Область действия buildFunction имеет область действия myNumber (myNumber = 6), поэтому fun возвращает 6.

Если buildFunction не имеет область действия myNumber, то Python просматривает следующая сфера В моей версии глобальная область действия имеет myNumber = 1, поэтому fun вернет 1.

Если myNumber также не существует в глобальной области действия, NameError повышается, потому что myNumber может не может быть найдено ни в локальной области видимости, ни в какой-либо из содержащих его областей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...