¿Qué limitaciones tienen los cierres en Python en comparación con los cierres del lenguaje X?

Donde X es cualquier lenguaje de progtwigción (C #, Javascript, Lisp, Perl, Ruby, Scheme, etc.) que admite cierto sabor de cierre.

Algunas limitaciones se mencionan en Closures in Python (en comparación con los cierres de Ruby), pero el artículo es antiguo y ya no existen muchas limitaciones en Python moderno.

Ver un ejemplo de código para una limitación concreta sería genial.

Preguntas relacionadas :

  • ¿Puede explicar los cierres (en relación con Python)?
  • ¿Qué es un ‘cierre’?
  • ¿Cómo funciona un cierre de javascript?

La limitación más importante, actualmente, es que no se puede asignar a una variable de scope externo. En otras palabras, los cierres son de solo lectura:

>>> def outer(x): ... def inner_reads(): ... # Will return outer's 'x'. ... return x ... def inner_writes(y): ... # Will assign to a local 'x', not the outer 'x' ... x = y ... def inner_error(y): ... # Will produce an error: 'x' is local because of the assignment, ... # but we use it before it is assigned to. ... tmp = x ... x = y ... return tmp ... return inner_reads, inner_writes, inner_error ... >>> inner_reads, inner_writes, inner_error = outer(5) >>> inner_reads() 5 >>> inner_writes(10) >>> inner_reads() 5 >>> inner_error(10) Traceback (most recent call last): File "", line 1, in  File "", line 11, in inner_error UnboundLocalError: local variable 'x' referenced before assignment 

Un nombre que se asigna en un ámbito local (una función) siempre es local, a menos que se declare lo contrario. Si bien existe la statement ‘global’ para declarar una variable global incluso cuando se asigna, todavía no existe tal statement para las variables incluidas. En Python 3.0, hay (habrá) la statement ‘no local’ que hace eso.

Puede evitar esta limitación mientras tanto usando un tipo de contenedor mutable:

 >>> def outer(x): ... x = [x] ... def inner_reads(): ... # Will return outer's x's first (and only) element. ... return x[0] ... def inner_writes(y): ... # Will look up outer's x, then mutate it. ... x[0] = y ... def inner_error(y): ... # Will now work, because 'x' is not assigned to, just referenced. ... tmp = x[0] ... x[0] = y ... return tmp ... return inner_reads, inner_writes, inner_error ... >>> inner_reads, inner_writes, inner_error = outer(5) >>> inner_reads() 5 >>> inner_writes(10) >>> inner_reads() 10 >>> inner_error(15) 10 >>> inner_reads() 15 

La única dificultad que he visto en particular con Python’s es cuando intentan mezclar características no funcionales como la reasignación de variables con cierres, y se sorprenden cuando esto no funciona:

 def outer (): x = 1 def inner (): print x x = 2 return inner outer () () 

Por lo general, solo señalar que una función tiene sus propias variables locales es suficiente para desalentar tales tonterías.

Una limitación (o “limitación”) de los cierres de Python, en comparación con los cierres de Javascript, es que no se puede usar para ocultar datos de manera efectiva.

Javascript

 var mksecretmaker = function(){ var secrets = []; var mksecret = function() { secrets.push(Math.random()) } return mksecret } var secretmaker = mksecretmaker(); secretmaker(); secretmaker() // privately generated secret number list // is practically inaccessible 

Pitón

 import random def mksecretmaker(): secrets = [] def mksecret(): secrets.append(random.random()) return mksecret secretmaker = mksecretmaker() secretmaker(); secretmaker() # "secrets" are easily accessible, # it's difficult to hide something in Python: secretmaker.__closure__[0].cell_contents # -> eg [0.680752847190161, 0.9068475951742101] 

Corregido en Python 3 a través de la statement nonlocal :

La statement nonlocal hace que los identificadores enumerados se refieran a variables previamente vinculadas en el ámbito envolvente más cercano, excluyendo las globales. Esto es importante porque el comportamiento predeterminado para el enlace es buscar primero en el espacio de nombres local. La statement permite que el código encapsulado vuelva a vincular variables fuera del ámbito local además del scope global (módulo).

@John Millikin

 def outer(): x = 1 # local to `outer()` def inner(): x = 2 # local to `inner()` print(x) x = 3 return x def inner2(): nonlocal x print(x) # local to `outer()` x = 4 # change `x`, it is not local to `inner2()` return x x = 5 # local to `outer()` return (inner, inner2) for inner in outer(): print(inner()) # -> 2 3 5 4 

Comentario para la respuesta de @Kevin Little para incluir el ejemplo de código

nonlocal no resuelve completamente este problema en python3.0:

 x = 0 # global x def outer(): x = 1 # local to `outer` def inner(): global x x = 2 # change global print(x) x = 3 # change global return x def inner2(): ## nonlocal x # can't use `nonlocal` here print(x) # prints global ## x = 4 # can't change `x` here return x x = 5 return (inner, inner2) for inner in outer(): print(inner()) # -> 2 3 3 3 

Por otra parte:

 x = 0 def outer(): x = 1 # local to `outer` def inner(): ## global x x = 2 print(x) # local to `inner` x = 3 return x def inner2(): nonlocal x print(x) x = 4 # local to `outer` return x x = 5 return (inner, inner2) for inner in outer(): print(inner()) # -> 2 3 5 4 

funciona en python3.1-3.3

La mejor solución hasta que 3.0 sea incluir la variable como un parámetro predeterminado en la definición de función adjunta:

 def f ()
     x = 5
     def g (y, z, x = x):
         x = x + 1