2011-01-31 10 views
68

Tengo el siguiente código dentro de una función:cierre Python: Escribir a variable en ámbito padre

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) 

elementos Agregando a stored_blocks funciona bien, pero no puede aumentar num_converted en la segunda función parcial:

UnboundLocalError: local variable 'num_converted' referenced before assignment

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

Tengo curiosidad por saber cómo puedo escribir en una variable en el ámbito de la función principal. nonlocal num_converted probablemente haga el trabajo, pero necesito una solución que funcione con Python 2.x.

+4

Contrariamente a la creencia algo popular (a juzgar por este tipo de preguntas) 'def' no es la única palabra clave que define un espacio de nombres: también hay' clase'. –

Respuesta

71

Problema:.. Esto se debe a reglas de alcance de Python son dementes La presencia de el operador de asignación += marca el destino, num_converted, como local para el alcance de la función adjunta, y no hay forma de sonido en Python 2.x para acceder a un solo nivel de alcance desde allí. Solo la palabra clave global puede sacar las referencias variables de la alcance actual, y lo lleva directamente a la parte superior.

Fix: Gire num_converted en una matriz de elemento único.

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

¿Puedes explicar por qué es necesario? Hubiera esperado que el código OP funcionara. –

+36

Porque las reglas de alcance de Python están dementes. La presencia del operador de asignación '+ =' marca el objetivo, 'num_converted', como local para el alcance de la función envolvente, y no hay forma de sonido en Python 2.x para acceder a un solo nivel de alcance desde allí. Solo la palabra clave 'global' puede sacar las referencias variables del alcance actual, y lo lleva directamente a la cima. –

+0

He usado ese enfoque y funciona bien. – ThiefMaster

9

El uso de la palabra clave global está bien. Si se escribe:

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, que no se hace visible en otros lugares inesperados), sólo significa que puede ser modificada dentro convert_variables. Eso parece ser exactamente lo que quieres.

Para decirlo de otra manera, num_converted es ya una variable global. Toda la sintaxis global num_converted hace es decirle Python "dentro de esta función, no cree un num_converted variable local, en cambio, utilizar el global existente

+3

'global' en 2.x funciona más o menos como' nonlocal' en 3.x. –

+2

"Para decirlo de otra manera, num_converted ya es una variable global" - mi código se ejecuta dentro de una función ... por lo que actualmente no es global. – ThiefMaster

+2

Ah, no he prestado atención a la parte "dentro de una función", lo siento, en ese caso, la longitud-una-lista de Marcelo puede ser una solución mejor (pero fea). – Emile

6

¿Qué ocurre con el uso de una instancia de clase para contener el estado? crear instancias de una clase y aprobar los métodos de instancia a los submarinos y las funciones tendría una referencia a uno mismo ...

+7

Suena un poco excesivo y como una solución proveniente de un programador de Java. ; p – ThiefMaster

+1

¡eh, pero está limpio! ;) – Seb

+1

@ThiefMaster ¿Por qué es esto exagerado? Si desea acceder al alcance principal ** debe ** utilizar una clase en Python. – schlamar

25

(ver más abajo para la respuesta editado)

Usted puede 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 obras como una variable C-como "estática" 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.

+3

Derecha. Y tenga en cuenta que debe crear el atributo 'convert_variables.num_converted' _after_ que define la función, aunque parece extraño hacerlo. –

+0

@PabloG respuesta más satisfactoria a esta pregunta, además de no local en 3.x; usar el tipo mutable [] es una solución económica. – user2290820

6

Tengo un par de comentarios.

En primer lugar, aparece una aplicación para tales funciones anidadas cuando se trata de devoluciones de llamada sin formato, como se usan en bibliotecas como xml.parsers.expat. (Que los autores de la biblioteca eligieron este enfoque puede ser objetable, pero ... hay razones para usarlo de todos modos)

Segundo: dentro de una clase, hay alternativas mucho más agradables a la matriz (num_converted [0]). Supongo que esto es de 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 

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

+0

Hola, bienvenidos a Stack Overflow, pero publicar una "observación" como respuesta no es algo que puedas hacer aquí. Tenemos comentarios para esto (sin embargo, necesita algún representante para publicarlos, pero no publique respuestas solo porque todavía no tiene suficientes representantes como para hacer un comentario) – ThiefMaster

+5

¡Hola, usted también es bienvenido! No entiendo su comentario, o varios de los términos que usa. ¡Y soy un hombre ocupado, tratando de ser útil! –

+0

No hay problema, eche un vistazo a http://stackoverflow.com/about though. Si bien siempre se agradece la ayuda, un comentario publicado con el tiempo se eliminará sin importar lo bueno que sea. – ThiefMaster

Cuestiones relacionadas