В Python 2, как мне написать в переменную в родительской области?

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

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.

Ответ 1

Проблема: Это связано с тем, что правила обзора 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

Ответ 2

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

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

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

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

Ответ 3

Использование ключевого слова 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, вместо этого используйте существующую глобальную.

Ответ 4

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

Ответ 5

У меня есть несколько замечаний.

Во-первых, одно приложение для таких вложенных функций возникает при работе с необработанными обратными вызовами, которые используются в таких библиотеках, как 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

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

Ответ 6

Изменено с: fooobar.com/info/12815426/...

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

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 для внутренней функции, определенной в декораторе в подмодуле, и т.д.

NB: То, что вы можете это сделать, не означает, что вы должны это делать.