Закрытие Python против закрытия javascript

Следующая функция закрытия отлично работает в javascript.

function generateNextNumber(startNumber) {
    var current = startNumber;
    return function(){
        return current += 1;
    }
}

var getNextNumber = generateNextNumber(10);
for (var i = 0; i < 10; i++) {
    console.log(getNextNumber());
}

Я попытался сделать то же самое в Python

def generateNextNumber(startNumber):
    current = startNumber
    def tempFunction():
        current += 1
        return current
    return tempFunction

getNextNumber = generateNextNumber(10)
for i in range(10):
    print (getNextNumber())

Я получаю следующую ошибку

Traceback (most recent call last):
  File "/home/thefourtheye/Desktop/Test1.py", line 10, in <module>
    print (getNextNumber())
  File "/home/thefourtheye/Desktop/Test1.py", line 4, in tempFunction
    current += 1
UnboundLocalError: local variable 'current' referenced before assignment

Когда я напечатал vars() и locals() внутри tempFunction, они подтверждают, что присутствует current.

({'current': 10}, {'current': 10})

Но когда я немного изменил программу

def generateNextNumber(startNumber):
    current = {"Number" : startNumber}
    def tempFunction():
        current["Number"] += 1
        return current["Number"]
    return tempFunction

он работает. Я не могу объяснить, почему это работает. Может кто-нибудь объяснить пожалуйста?

Ответ 1

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

Используйте объявление global

def generateNextNumber(startNumber):
    global current
    current= startNumber
    def tempFunction():
        global current
        current += 1
        return current 
    return tempFunction

Действителен в некоторых случаях, но в вашем случае может быть активен только один экземпляр tempFunction.

Использовать атрибут функции

def generateNextNumber(startNumber):
    def tempFunction():
        tempFunction.current += 1
        return tempFunction.current
    tempFunction.current= startNumber
    return tempFunction

Используется тот факт, что функции являются объектами (и, следовательно, могут иметь атрибуты), что они создаются при их объявлении и становятся локальными для закрывающей функции (или модуля, и в этом случае они действительно глобальны). Это также работает, потому что имя tempFunction используется впервые в своем собственном определении с помощью оператора "член доступа" . и, следовательно, не считается локальным. Что-то подобное происходит с операторами "call" () и "element access" []. В следующем случае объясняется, почему ваш код работает.

Привяжите имя к нелокальному

def generateNextNumber(startNumber):
    current= type("OnTheFly",(),{})()
    current.value= startNumber
    def tempFunction():
        current.value += 1
        return current.value
    return tempFunction

Об этом уже говорилось в предыдущем разделе. Используя оператор доступа элемента ., мы говорим: "current уже существует", и поэтому он искал в охватывающей области. В этом конкретном случае мы создаем класс с использованием функции type и сразу создаем его экземпляр (со вторым набором скобок). Вместо общего объекта мы могли бы также использовать список или словарь. Второй случай был очень распространенным решением.

Использовать объект функции

def generateNextNumber(startNumber):
    class TempFunction:
        def __call__(self):
            self.current += 1
            return self.current
    tempFunction= TempFunction()
    tempFunction.current= startNumber
    return tempFunction

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

Используйте объявление nonlocal

def generateNextNumber(startNumber):
    current= startNumber
    def tempFunction():
        nonlocal current
        current += 1
        return current
    return tempFunction

Точно так же, как global означает... ну, глобальный, nonlocal означает "в предыдущей области". Действителен в Python 3 и, возможно, более поздних версиях Python 2.

Использовать генераторы

def generateNextNumber(current):
    while True :
        current+= 1
        yield current

Это, пожалуй, самый "питонический" способ приблизиться не к общей проблеме нелокального доступа к переменной, а к конкретному случаю, который вы использовали для объяснения. Я не мог закончить, не упомянув об этом. Вы должны называть его незначительными изменениями:

getNextNumber = generateNextNumber(10)
for i in range(10):
    print (getNextNumber.next())

При вождении for вызов next() является неявным (но генератор не может быть бесконечным, как в моем примере).

Ответ 2

Python решает, какие функции используют локальные переменные, определяя, что любая переменная, содержащая функцию присваивания, является локальной, если не объявлена ​​ nonlocal или global. Таким образом,

current += 1

создает локальную переменную с именем current, которая скрывает нелокальную переменную. Если вы находитесь на Python 2, стандартное решение (помимо попытки не делать этого) состоит в том, чтобы сделать current список из 1 элемента и использовать

current[0] += 1

Для справки "попытка не делать этого" может выглядеть примерно так:

class Counter(object):
    def __init__(self):
        self.count = 0
    def __call__(self):
        self.count += 1
        return self.count
c = Counter()
c()  # Returns 1
c()  # Returns 2