Crear una clase dentro de una función y acceder a una función definida en el scope de la función contenedora

Editar :

Vea mi respuesta completa al final de esta pregunta.

tl; respuesta del dr : Python tiene ámbitos nesteds estáticamente. El aspecto estático puede interactuar con las declaraciones de variables implícitas, dando resultados no obvios.

(Esto puede ser especialmente sorprendente debido a la naturaleza generalmente dinámica del lenguaje).

Pensé que tenía un buen manejo de las reglas de scope de Python, pero este problema me ha bloqueado por completo, y mi google-fu me ha fallado (no es que me sorprenda, mira el título de la pregunta)

Voy a comenzar con algunos ejemplos que funcionan como se esperaba, pero siéntase libre de saltar al ejemplo 4 para la parte más jugosa.

Ejemplo 1.

>>> x = 3 >>> class MyClass(object): ... x = x ... >>> MyClass.x 3 

Lo suficientemente sencillo: durante la definición de la clase, podemos acceder a las variables definidas en el ámbito externo (en este caso global).

Ejemplo 2.

 >>> def mymethod(self): ... return self.x ... >>> x = 3 >>> class MyClass(object): ... x = x ... mymethod = mymethod ... >>> MyClass().mymethod() 3 

Nuevamente (ignorando por el momento por qué uno podría querer hacer esto), no hay nada inesperado aquí: podemos acceder a funciones en el ámbito externo.

Nota : como Frédéric señaló a continuación, esta función no parece funcionar. Ver el Ejemplo 5 (y más allá) en su lugar.

Ejemplo 3.

 >>> def myfunc(): ... x = 3 ... class MyClass(object): ... x = x ... return MyClass ... >>> myfunc().x Traceback (most recent call last): File "", line 1, in  File "", line 3, in myfunc File "", line 4, in MyClass NameError: name 'x' is not defined 

Esencialmente, es lo mismo que en el ejemplo 1: estamos accediendo al ámbito externo desde la definición de la clase, solo que esta vez el ámbito no es global, gracias a myfunc() .

Edición 5: como @ user3022222 señaló a continuación , arruiné este ejemplo en mi publicación original. Creo que esto falla porque solo las funciones (no otros bloques de código, como esta definición de clase) pueden acceder a las variables en el ámbito de inclusión. Para los bloques de código que no son de función, solo las variables locales, globales e integradas son accesibles. Una explicación más completa está disponible en esta pregunta.

Uno mas:

Ejemplo 4.

 >>> def my_defining_func(): ... def mymethod(self): ... return self.y ... class MyClass(object): ... mymethod = mymethod ... y = 3 ... return MyClass ... >>> my_defining_func() Traceback (most recent call last): File "", line 1, in  File "", line 4, in my_defining_func File "", line 5, in MyClass NameError: name 'mymethod' is not defined 

Um … disculpe?

¿Qué hace que esto sea diferente del ejemplo 2?

Estoy completamente aturdido. Por favor, ordéname. ¡Gracias!

PD: en la remota posibilidad de que esto no sea solo un problema con mi entendimiento, lo he probado en Python 2.5.2 y Python 2.6.2. Desafortunadamente, a eso es a lo que tengo acceso en este momento, pero ambos muestran el mismo comportamiento.

Editar De acuerdo con http://docs.python.org/tutorial/classes.html#python-scopes-and-namespaces : en cualquier momento durante la ejecución, hay al menos tres ámbitos nesteds cuyos espacios de nombres son directamente accesibles:

  • El ámbito más interno, que se busca primero, contiene los nombres locales
  • los ámbitos de cualquier función de cierre, que se buscan a partir del scope de cierre más cercano, contienen nombres no locales, pero también no globales
  • El scope del siguiente al último contiene los nombres globales del módulo actual
  • el ámbito más externo (el último buscado) es el espacio de nombres que contiene nombres integrados

# 4. Parece ser un contra-ejemplo para el segundo de estos.

Editar 2

Ejemplo 5.

 >>> def fun1(): ... x = 3 ... def fun2(): ... print x ... return fun2 ... >>> fun1()() 3 

Editar 3

Como @ Frédéric señaló que la asignación de una variable del mismo nombre que tiene en el ámbito externo parece “enmascarar” la variable externa, impidiendo que la asignación funcione.

Así que esta versión modificada del Ejemplo 4 funciona:

 def my_defining_func(): def mymethod_outer(self): return self.y class MyClass(object): mymethod = mymethod_outer y = 3 return MyClass my_defining_func() 

Sin embargo, esto no:

 def my_defining_func(): def mymethod(self): return self.y class MyClass(object): mymethod_temp = mymethod mymethod = mymethod_temp y = 3 return MyClass my_defining_func() 

Todavía no entiendo completamente por qué ocurre este enmascaramiento: ¿no debería ocurrir el enlace de nombre cuando ocurre la asignación?

Este ejemplo al menos proporciona alguna sugerencia (y un mensaje de error más útil):

 >>> def my_defining_func(): ... x = 3 ... def my_inner_func(): ... x = x ... return x ... return my_inner_func ... >>> my_defining_func()() Traceback (most recent call last): File "", line 1, in  File "", line 4, in my_inner_func UnboundLocalError: local variable 'x' referenced before assignment >>> my_defining_func()  

Por lo tanto, parece que la variable local se define en la creación de la función (que tiene éxito), lo que da como resultado que el nombre local sea “reservado” y, por lo tanto, enmascara el nombre del ámbito externo cuando se llama a la función.

Interesante.

Gracias Frédéric por la (s) respuesta (s)!

Para referencia, desde la documentación de python :

Es importante darse cuenta de que los ámbitos se determinan textualmente: el scope global de una función definida en un módulo es el espacio de nombres de ese módulo, sin importar desde dónde o por qué alias se llame la función. Por otro lado, la búsqueda real de nombres se realiza dinámicamente, en tiempo de ejecución; sin embargo, la definición del idioma está evolucionando hacia una resolución de nombres estática, en tiempo de “comstackción”, ¡así que no confíe en la resolución de nombres dinámica! (De hecho, las variables locales ya están determinadas estáticamente).

Editar 4

La respuesta real

Este comportamiento aparentemente confuso es causado por los ámbitos nesteds estáticamente de Python como se define en PEP 227 . En realidad, no tiene nada que ver con PEP 3104 .

De PEP 227:

Las reglas de resolución de nombres son típicas para los idiomas de ámbito estático […] [excepto] las variables no se declaran. Si se produce una operación de enlace de nombre en cualquier lugar de una función, ese nombre se trata como local de la función y todas las referencias se refieren al enlace local. Si se produce una referencia antes de que se vincule el nombre, se genera un error de nombre.

[…]

Un ejemplo de Tim Peters demuestra los peligros potenciales de los ámbitos nesteds en ausencia de declaraciones:

 i = 6 def f(x): def g(): print i # ... # skip to the next page # ... for i in x: # ah, i *is* local to f, so this is what g sees pass g() 

La llamada a g () se referirá a la variable i limitada en f () por el bucle for. Si se llama a g () antes de que se ejecute el bucle, se generará un error de nombre.

Vamos a ejecutar dos versiones más simples del ejemplo de Tim:

 >>> i = 6 >>> def f(x): ... def g(): ... print i ... # ... ... # later ... # ... ... i = x ... g() ... >>> f(3) 3 

cuando g() no encuentra i en su ámbito interno, busca dinámicamente hacia afuera, encontrando el i en el scope de f , que se ha vinculado a 3 mediante la asignación i = x .

Pero cambiando el orden, las dos declaraciones finales en f causan un error:

 >>> i = 6 >>> def f(x): ... def g(): ... print i ... # ... ... # later ... # ... ... g() ... i = x # Note: I've swapped places ... >>> f(3) Traceback (most recent call last): File "", line 1, in  File "", line 7, in f File "", line 3, in g NameError: free variable 'i' referenced before assignment in enclosing scope 

Recordando que PEP 227 dijo “Las reglas de resolución de nombres son típicas de los idiomas de ámbito estático”, veamos la oferta de la versión C (semi) equivalente:

 // nested.c #include  int i = 6; void f(int x){ int i; // <--- implicit in the python code above void g(){ printf("%d\n",i); } g(); i = x; g(); } int main(void){ f(3); } 

comstackr y ejecutar:

 $ gcc nested.c -o nested $ ./nested 134520820 3 

Entonces, mientras que C felizmente usará una variable no vinculada (utilizando lo que sea que haya estado almacenado allí antes: 134520820, en este caso), Python (afortunadamente) se niega.

Como nota interesante, los ámbitos nesteds estáticamente habilitan lo que Alex Martelli ha llamado “la optimización más importante que hace el comstackdor de Python: las variables locales de una función no se mantienen en un dictado, están en un vector de valores ajustado, y cada una el acceso a la variable local utiliza el índice en ese vector, no una búsqueda de nombre “.

Eso es un artefacto de las reglas de resolución de nombres de Python: solo tiene acceso a los ámbitos global y local, pero no a los ámbitos intermedios, por ejemplo, no a su ámbito externo inmediato.

EDITAR: Lo anterior estaba mal redactado, tiene acceso a las variables definidas en los ámbitos externos, pero al hacer x = x o mymethod = mymethod desde un espacio de nombres no global, en realidad está ocultando la variable externa con la que está ‘ re definiendo localmente.

En el ejemplo 2, su scope externo inmediato es el scope global, por lo que MyClass puede ver mi mymethod , pero en el ejemplo 4 su scope externo inmediato es my_defining_func() , por lo que no puede, porque la definición externa de mi mymethod ya está enmascarada por su local definición.

Consulte PEP 3104 para obtener más detalles sobre la resolución de nombres no locales.

También tenga en cuenta que, por las razones explicadas anteriormente, no puedo hacer que el ejemplo 3 se ejecute en Python 2.6.5 o 3.1.2:

 >>> def myfunc(): ... x = 3 ... class MyClass(object): ... x = x ... return MyClass ... >>> myfunc().x Traceback (most recent call last): File "", line 1, in  File "", line 3, in myfunc File "", line 4, in MyClass NameError: name 'x' is not defined 

Pero lo siguiente funcionaría:

 >>> def myfunc(): ... x = 3 ... class MyClass(object): ... y = x ... return MyClass ... >>> myfunc().y 3 

Esta publicación tiene algunos años de antigüedad, pero se encuentra entre los raros para discutir el importante problema del scope y el enlace estático en Python. Sin embargo, hay un malentendido importante del autor, por ejemplo 3, que podría confundir a los lectores. (No dé por sentado que los otros son correctos, es solo que solo observé los problemas planteados en el ejemplo 3 en detalle). Déjame aclarar lo que pasó.

En el ejemplo 3

 def myfunc(): x = 3 class MyClass(object): x = x return MyClass >>> myfunc().x 

Debe devolver un error, a diferencia de lo que dijo el autor del post. Creo que se perdió el error porque en el ejemplo 1 x se asignó a 3 en el ámbito global. Por lo tanto, una comprensión errónea de lo que sucedió.

La explicación se describe ampliamente en esta publicación Cómo se resuelven las referencias a las variables en Python