В Python 2, как мне написать в переменную в родительской области? - PullRequest
76 голосов
/ 31 января 2011

У меня есть следующий код внутри функции:

stored_blocks = {}
def replace_blocks(m):
    block = m.group(0)
    block_hash = sha1(block)
    stored_blocks[block_hash] = block
    return '{{{%s}}}' % block_hash

num_converted = 0
def convert_variables(m):
    name = m.group(1)
    num_converted += 1
    return '<%%= %s %%>' % name

fixed = MATCH_DECLARE_NEW.sub('', template)
fixed = MATCH_PYTHON_BLOCK.sub(replace_blocks, fixed)
fixed = MATCH_FORMAT.sub(convert_variables, fixed)

Добавление элементов в stored_blocks работает нормально, но я не могу увеличить num_converted во второй подфункции:

UnboundLocalError: локальная переменная 'num_converted', на которую ссылается перед присваиванием

Я мог бы использовать global, но глобальные переменные ужасны, и мне действительно не нужно, чтобы эта переменная была глобальной вообще.

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

Ответы [ 6 ]

85 голосов
/ 31 января 2011

Проблема: Это потому, что правила области видимости Python сумасшедшие. Наличие оператора присваивания += помечает цель, num_converted, как локальную для области видимости вмещающей функции, и в Python 2.x нет звукового способа получить доступ только к одному уровню области видимости. Только ключевое слово global может вывести ссылки на переменные из текущей области, и это приведет вас прямо к вершине.

Исправление: Превращение num_converted в одноэлементный массив.

num_converted = [0]
def convert_variables(m):
    name = m.group(1)
    num_converted[0] += 1
    return '<%%= %s %%>' % name
29 голосов
/ 31 января 2011

(см. Ниже отредактированный ответ)

Вы можете использовать что-то вроде:

def convert_variables(m):
    name = m.group(1)
    convert_variables.num_converted += 1
    return '<%%= %s %%>' % name

convert_variables.num_converted = 0

Таким образом, num_converted работает как C-подобная "статическая" переменнаяМетод convert_variable


(отредактировано)

def convert_variables(m):
    name = m.group(1)
    convert_variables.num_converted = convert_variables.__dict__.get("num_converted", 0) + 1
    return '<%%= %s %%>' % name

Таким образом, вам не нужно инициализировать счетчик в основной процедуре.

9 голосов
/ 31 января 2011

Использование ключевого слова global хорошо. Если вы напишите:

num_converted = 0
def convert_variables(m):
    global num_converted
    name = m.group(1)
    num_converted += 1
    return '<%%= %s %%>' % name

... num_converted не становится «глобальной переменной» (т. Е. Она не становится видимой в других неожиданных местах), это просто означает, что ее можно изменить внутри convert_variables. Кажется, это именно то, что вы хотите.

Другими словами, num_converted уже уже глобальная переменная. Все, что делает синтаксис global num_converted, это говорит Python "внутри этой функции, не создавайте локальную переменную num_converted, вместо этого используйте существующую глобальную.

7 голосов
/ 31 января 2011

Как насчет использования экземпляра класса для хранения состояния? Вы создаете экземпляр класса и передаете методы экземпляра подпрограммам, и эти функции будут иметь ссылку на себя ...

6 голосов
/ 27 марта 2013

У меня есть пара замечаний.

Во-первых, при работе с необработанными обратными вызовами появляется одно приложение для таких вложенных функций, которые используются в таких библиотеках, как xml.parsers.expat.(То, что авторы библиотек выбрали этот подход, может быть нежелательным, но ... тем не менее есть причины использовать его.)

Второе: внутри класса есть много более хороших альтернатив для массива (num_converted [0]).Я предполагаю, что это то, о чем говорил Себастьян.

class MainClass:
    _num_converted = 0
    def outer_method( self ):
        def convert_variables(m):
            name = m.group(1)
            self._num_converted += 1
            return '<%%= %s %%>' % name

Это все еще достаточно странно, чтобы заслужить комментарий в коде ... Но переменная, по крайней мере, локальна для класса.

0 голосов
/ 02 июня 2018

Изменено с: https://stackoverflow.com/a/40690954/819544

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

import inspect 

def get_globals(scope_level=0):
    return dict(inspect.getmembers(inspect.stack()[scope_level][0]))["f_globals"]

num_converted = 0
def foobar():
    get_globals(0)['num_converted'] += 1

foobar()
print(num_converted) 
# 1

При необходимости используйте аргумент scope_level.Установка scope_level=1 работает для функции, определенной в подмодуле, scope_level=2 для внутренней функции, определенной в декораторе в подмодуле, и т. Д.

Примечание: только потому, что вы можете сделать это, не значит, что ты должен.

...