En Python 2, ¿cómo escribo en la variable en el ámbito principal?

Tengo el siguiente código dentro de una función:

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 '' % name fixed = MATCH_DECLARE_NEW.sub('', template) fixed = MATCH_PYTHON_BLOCK.sub(replace_blocks, fixed) fixed = MATCH_FORMAT.sub(convert_variables, fixed) 

Agregar elementos a stored_blocks funciona bien, pero no puedo boost num_converted en la segunda subfunción:

UnboundLocalError: variable local ‘num_converted’ referenciada antes de la asignación

Podría usar variables global pero las variables globales son feas y realmente no necesito esa variable para ser global en absoluto.

Así que tengo curiosidad por saber cómo escribir en una variable en el ámbito de la función principal. nonlocal num_converted probable que nonlocal num_converted , pero necesito una solución que funcione con Python 2.x.

Problema: esto se debe a que las reglas de scope de Python son demenciales. La presencia del operador de asignación += marca el objective, num_converted , como local al scope de la función que lo contiene, y en Python 2.x no hay forma de sonido para acceder solo a un nivel de scope desde allí. Solo la palabra clave global puede elevar las referencias de variables fuera del scope actual, y esto lo lleva directamente a la parte superior.

Solución: Convierta num_converted en una matriz de un solo elemento.

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

(ver más abajo para la respuesta editada)

Puedes usar algo como:

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

De esta manera, num_converted funciona como una variable “estática” similar a C del método convert_variable


(editado)

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

De esta manera, no necesita inicializar el contador en el procedimiento principal.

Usar la palabra clave global está bien. Si tú escribes:

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

num_converted no se convierte en una “variable global” (es decir, no se hace visible en ningún otro lugar inesperado), solo significa que se puede modificar dentro de convert_variables . Eso parece ser exactamente lo que quieres.

Para decirlo de otra manera, num_converted ya es una variable global. Toda la syntax global num_converted es decirle a Python “dentro de esta función, no cree una variable num_converted local, en su lugar, use la global existente.

¿Qué pasa con el uso de una instancia de clase para mantener el estado? Usted instancia una clase y pasa los métodos de instancia a subs y esas funciones tendrían una referencia a sí mismo …

Tengo un par de observaciones.

Primero, aparece una aplicación para tales funciones anidadas cuando se trata de devoluciones de llamada sin procesar, como se usa en bibliotecas como xml.parsers.expat. (El hecho de que los autores de la biblioteca hayan elegido este enfoque puede ser objetable, pero … hay razones para usarlo, no obstante).

Segundo: dentro de una clase, hay alternativas mucho más agradables a la matriz (num_converted [0]). Supongo que esto es lo que Sebastjan estaba hablando.

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

Todavía es lo suficientemente extraño como para merecer un comentario en el código … Pero la variable es al menos local para la clase.

Modificado de: https://stackoverflow.com/a/40690954/819544

Puede aprovechar el módulo de inspect para acceder al dictado global del scope de la llamada y escribir en él. Eso significa que este truco puede incluso aprovecharse para acceder al scope de la llamada desde una función anidada definida en un submódulo importado.

 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 

Juega con el argumento scope_level según sea necesario. La configuración de scope_level=1 funciona para una función definida en un submódulo, scope_level=2 para la función interna definida en un decorador en un submódulo, etc.

NB: Solo porque puedes hacer esto, no significa que debas hacerlo.