Нужна помощь в понимании закрытий Python

У меня есть этот код:

import re

def doReplace(toReplace):
    i = 1
    def chapterReplacer(_):
        result = 'Chapter %i' % i
        i += 1
        return result

    return re.sub('Chapter [a-zA-Z]+', chapterReplacer, test)

test = 'Chapter one Chapter Two Chapter three'
print doReplace(test)

когда я запускаю его, я получаю следующую ошибку:

Traceback (most recent call last):
  File "C:/Python26/replace.py", line 13, in <module>
    print doReplace(test)
  File "C:/Python26/replace.py", line 10, in doReplace
    return re.sub('Chapter [a-zA-Z]+', chapterReplacer, test)
  File "C:\Python26\lib\re.py", line 151, in sub
    return _compile(pattern, 0).sub(repl, string, count)
  File "C:/Python26/replace.py", line 6, in chapterReplacer
    result = 'Chapter %i' % i
UnboundLocalError: local variable 'i' referenced before assignment

У меня сложилось впечатление, что chapterReplacer перехватит локальную переменную i, но, похоже, этого не происходит?

 Martijn Pieters♦13 нояб. 2013 г., 14:37
Цель dupe здесь не так полезна, поскольку речь идет о вложенных функциях, пытающихся присвоить имя из родительской области.
 jfs15 июн. 2012 г., 22:03

Ответы на вопрос(4)

Решение Вопроса

i атрибут функции

def doReplace(toReplace):
    chapterReplacer.i = 1
    def chapterReplacer(_):
        result = 'Chapter %i' % chapterReplacer.i
        chapterReplacer.i += 1
        return result

    return re.sub('Chapter [a-zA-Z]+', chapterReplacer, test)

EDIT: Начиная с Python 3, вы можете использоватьnonlocal а-ля решение @MartijnPieters.

 15 июн. 2012 г., 23:36
@ Ханс: да, потому что каждый раз, когда вы бежитеdoReplace вы можете получить новыйchapterReplacer, Если вы поставите атрибут наdoReplaceтогда, если вы запуститеdoReplace опять же, вы будете использовать тот жеi, что неверно
 15 июн. 2012 г., 22:04
Один из многих способов злоупотребления атрибутами функции.
 15 июн. 2012 г., 22:05
Здесь я бы предпочел использоватьchapterReplacer.i чемdoReplace.i...
 15 июн. 2012 г., 23:38
хм @newacct, но вы явно сбрасываете значение каждый раз при входе в doReplace, так что это не имеет значения?
 15 июн. 2012 г., 22:07
если chapterReplacer вызывается только внутри doReplace, это будет иметь значение?

а в Python 2 вы вообще не можете использовать трюки с изменяемыми файлами:

def doReplace(toReplace):
    i = [1]
    def chapterReplacer(_):
        result = 'Chapter %i' % i[0]
        i[0] += 1
        return result

    return re.sub('Chapter [a-zA-Z]+', chapterReplacer, test)

Обычно python будет искать переменную только в окружающей области видимости, если она не назначена локально; как только байт-компилятор увидит прямое назначение (i = something) и нетglobal i В противном случае переменная считается локальной.

Но в приведенном выше коде мы никогда не назначаемi вchapterReplacer функция. Да, мы меняемi[0] но значение хранится вi Сам список не меняет.

В Python 3 просто используйтеnonlocal statement чтобы Python заглянул в его закрытие для переменной:

def doReplace(toReplace):
    i = 1
    def chapterReplacer(_):
        nonlocal i
        result = 'Chapter %i' % i
        i += 1
        return result

    return re.sub('Chapter [a-zA-Z]+', chapterReplacer, test)
 Bwmat15 июн. 2012 г., 22:07
почему это работает, если я список?
 15 июн. 2012 г., 22:08
Потому что вы не повторяетеi; только членыi, Сама переменная оказывается изменчивой, но, поскольку ее прямое значение (список) не изменяется, python будет искать в окружающей области видимости.

если вы назначаете переменную внутри функции (даже если с составным оператором присваивания, таким как+=), эта переменная считается локальной, если иное не указаноglobal или жеnonlocal заявление.

i получает другое значение внутри функцииchapterReplacer он рассматривает его как локальный, и не «магию закрытия»; применены. Если вы удалите строкуi += 1Ваш код будет работать.

Ваш ответ на вопрос