¿Alcance de las clases anidadas?

116

Estoy tratando de entender el alcance en clases anidadas en Python. Aquí está mi código de ejemplo:

class OuterClass:
    outer_var = 1
    class InnerClass:
        inner_var = outer_var

La creación de la clase no se completa y aparece el error:

<type 'exceptions.NameError'>: name 'outer_var' is not defined

Intentar inner_var = Outerclass.outer_varno funciona. Yo obtengo:

<type 'exceptions.NameError'>: name 'OuterClass' is not defined

Estoy intentando acceder a la estática outer_vardesde InnerClass.

¿Hay alguna forma de hacer esto?

martineau
fuente

Respuestas:

105
class Outer(object):
    outer_var = 1

    class Inner(object):
        @property
        def inner_var(self):
            return Outer.outer_var

Esto no es exactamente lo mismo que funcionan cosas similares en otros lenguajes y utiliza la búsqueda global en lugar de establecer el alcance del acceso a outer_var. (Si cambia el objeto al que Outerestá vinculado el nombre , este código usará ese objeto la próxima vez que se ejecute).

Si, en cambio, desea que todos los Innerobjetos tengan una referencia a un Outerporque outer_vares realmente un atributo de instancia:

class Outer(object):
    def __init__(self):
        self.outer_var = 1

    def get_inner(self):
        return self.Inner(self)
        # "self.Inner" is because Inner is a class attribute of this class
        # "Outer.Inner" would also work, or move Inner to global scope
        # and then just use "Inner"

    class Inner(object):
        def __init__(self, outer):
            self.outer = outer

        @property
        def inner_var(self):
            return self.outer.outer_var

Tenga en cuenta que anidar clases es algo poco común en Python y no implica automáticamente ningún tipo de relación especial entre las clases. Es mejor no anidar. (Todavía se puede establecer un atributo de clase en el Outerque Inner, si lo desea.)

martineau
fuente
2
Puede ser útil agregar con qué versión (s) de python funcionará su respuesta.
AJ.
1
Escribí esto con 2.6 / 2.x en mente, pero, mirándolo, no veo nada que no funcione igual en 3.x.
No entiendo muy bien lo que quiere decir en esta parte, "(Si cambia el objeto al que está vinculado el nombre Outer, entonces este código usará ese objeto la próxima vez que se ejecute)" ¿Puede ayudarme a entender?
batbrat
1
@batbrat significa que la referencia a Outerse busca de nuevo cada vez que lo hace Inner.inner_var. Entonces, si vuelve a vincular el nombre Outera un nuevo objeto, Inner.inner_varcomenzará a devolver ese nuevo objeto.
Felipe
45

Creo que simplemente puedes hacer:

class OuterClass:
    outer_var = 1

    class InnerClass:
        pass
    InnerClass.inner_var = outer_var

El problema que encontró se debe a esto:

Un bloque es una parte del texto del programa Python que se ejecuta como una unidad. Los siguientes son bloques: un módulo, un cuerpo de función y una definición de clase.
(...)
Un alcance define la visibilidad de un nombre dentro de un bloque.
(...)
El alcance de los nombres definidos en un bloque de clase se limita al bloque de clase; no se extiende a los bloques de código de métodos; esto incluye expresiones generadoras, ya que se implementan usando un alcance de función. Esto significa que fallará lo siguiente:

   class A:  

       a = 42  

       b = list(a + i for i in range(10))

http://docs.python.org/reference/executionmodel.html#naming-and-binding

Lo anterior significa:
un cuerpo de función es un bloque de código y un método es una función, entonces los nombres definidos fuera del cuerpo de la función presente en una definición de clase no se extienden al cuerpo de la función.

Parafraseando esto para su caso:
una definición de clase es un bloque de código, entonces los nombres definidos fuera de la definición de clase interna presente en una definición de clase externa no se extienden a la definición de clase interna.

eyquem
fuente
2
Asombroso. Su ejemplo falla, afirmando que "el nombre global 'a' no está definido". Sin embargo, la sustitución de una lista de comprensión [a + i for i in range(10)]vincula con éxito a Ab con la lista esperada [42..51].
George
6
@George Tenga en cuenta que el ejemplo con la clase A no es mío, es del documento oficial de Python cuyo enlace le di. Este ejemplo falla y esa falla es lo que se quiere mostrar en este ejemplo. De hecho list(a + i for i in range(10))es list((a + i for i in range(10)))decir list(a_generator). Dicen que un generador se implementa con un alcance similar al alcance de funciones.
eyquem
@George Para mí, eso significa que las funciones actúan de manera diferente según estén en un módulo o en una clase. En el primer caso, una función sale para encontrar el objeto enlazado a un identificador libre. En el segundo caso, una función, es decir un método, no sale de su cuerpo. Las funciones de un módulo y los métodos de una clase son en realidad dos tipos de objetos. Los métodos no son solo funciones en clase. Esa es mi idea.
eyquem
5
@George: FWIW, ni la list(...)llamada ni la comprensión funcionan en Python 3. La documentación para Py3 también es ligeramente diferente reflejando esto. Ahora dice " El alcance de los nombres definidos en un bloque de clase se limita al bloque de clase; no se extiende a los bloques de código de métodos; esto incluye comprensiones y expresiones generadoras, ya que se implementan usando un alcance de función " . (Énfasis mío ).
Martineau
Tengo curiosidad por saber por qué la lista (Aa + i para i en el rango (10)) tampoco funciona, donde arreglé a por Aa, creo que A puede ser un nombre global.
gaoxinge
20

Es posible que esté mejor si simplemente no usa clases anidadas. Si debe anidar, intente esto:

x = 1
class OuterClass:
    outer_var = x
    class InnerClass:
        inner_var = x

O declare ambas clases antes de anidarlas:

class OuterClass:
    outer_var = 1

class InnerClass:
    inner_var = OuterClass.outer_var

OuterClass.InnerClass = InnerClass

(Después de esto, puede del InnerClasshacerlo si lo necesita).

Jason Orendorff
fuente
3

Solución más sencilla:

class OuterClass:
    outer_var = 1
    class InnerClass:
        def __init__(self):
            self.inner_var = OuterClass.outer_var

Requiere que seas explícito, pero no requiere mucho esfuerzo.

Cristian garcia
fuente
NameError: el nombre 'OuterClass' no está definido - -1
Mr_and_Mrs_D
3
El punto es acceder a él desde el alcance de la clase Outer; además, se producirá un error similar si lo llama desde un alcance estático dentro de Outer. Le sugiero que elimine esta publicación
Mr_and_Mrs_D
1

En Python, los objetos mutables se pasan como referencia, por lo que puede pasar una referencia de la clase externa a la clase interna.

class OuterClass:
    def __init__(self):
        self.outer_var = 1
        self.inner_class = OuterClass.InnerClass(self)
        print('Inner variable in OuterClass = %d' % self.inner_class.inner_var)

    class InnerClass:
        def __init__(self, outer_class):
            self.outer_class = outer_class
            self.inner_var = 2
            print('Outer variable in InnerClass = %d' % self.outer_class.outer_var)
T. Alpers
fuente
2
Tenga en cuenta que aquí tiene un ciclo de referencia y, en algún escenario, la instancia de esta clase no se liberará. Un ejemplo, con cPython, si tiene un __del__ método definido , el recolector de basura no podrá manejar el ciclo de referencia y los objetos entrarán gc.garbage. Sin embargo, el código anterior, tal como está, no es problemático. La forma de lidiar con esto es usando una referencia débil . Puede leer la documentación sobre debilref (2.7) o debilref (3.5)
guyarad
0

Todas las explicaciones se pueden encontrar en la documentación de Python El tutorial de Python

Por su primer error <type 'exceptions.NameError'>: name 'outer_var' is not defined. La explicacion es:

No existe una abreviatura para hacer referencia a atributos de datos (¡u otros métodos!) Desde dentro de los métodos. Encuentro que esto realmente aumenta la legibilidad de los métodos: no hay posibilidad de confundir las variables locales y las variables de instancia cuando se examina un método.

citado de The Python Tutorial 9.4

Por tu segundo error <type 'exceptions.NameError'>: name 'OuterClass' is not defined

Cuando una definición de clase se deja normalmente (al final), se crea un objeto de clase.

citado de The Python Tutorial 9.3.1

Entonces, cuando lo intentas inner_var = Outerclass.outer_var, Quterclassaún no se ha creado, por esoname 'OuterClass' is not defined

Una explicación más detallada pero tediosa de su primer error:

Aunque las clases tienen acceso a los alcances de las funciones adjuntas, no actúan como alcances adjuntos al código anidado dentro de la clase: Python busca funciones adjuntas para nombres referenciados, pero nunca clases adjuntas. Es decir, una clase es un ámbito local y tiene acceso para incluir ámbitos locales, pero no sirve como un ámbito local adjunto para el código anidado adicional.

citado de Learning.Python (quinto) .Mark.Lutz

Andy Guo
fuente