¿Cómo acceder a la clase externa desde una clase interna?

101

Tengo una situación así ...

class Outer(object):

    def some_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            self.Outer.some_method()    # <-- this is the line in question

¿Cómo puedo acceder al Outermétodo de la Innerclase desde la clase?

T. Stone
fuente
¿Por qué estás haciendo esto? ¿Qué pasa con las relaciones simples con los compañeros? ¿Estás intentando "ocultar" algo?
S.Lott
1
Un ejemplo de este escenario podría ser tener una clase con subclases que necesitan acceder a la clase externa, como es normal, pero luego necesitan crear otra clase (nivel superior) derivada de la primera clase. En ese caso, las subclases de la segunda clase intentarían acceder al padre usando self.<original_parent_name>y obtener la clase original, no la nueva clase de la que son una subclase . Espero que las personas que lean esto puedan visualizar este escenario difícil y ver el sentido de preguntas como esta.
Edward
1
Duplique a stackoverflow.com/questions/2278426 y tiene una buena respuesta.
xmedeko

Respuestas:

67

Los métodos de una clase anidada no pueden acceder directamente a los atributos de instancia de la clase externa.

Tenga en cuenta que no es necesariamente el caso de que exista una instancia de la clase externa incluso cuando haya creado una instancia de la clase interna.

De hecho, a menudo se recomienda no usar clases anidadas, ya que el anidamiento no implica ninguna relación particular entre las clases internas y externas.

Daniel Vassallo
fuente
1
Hmm, Python es más rápido que Java / C ++ ... vea mi respuesta a continuación. Si estamos dividiendo los pelos, lo que generalmente hacemos, realmente no podría decirles si mi "clase anidada dentro del método" cuenta como una clase interna. En este punto, sin embargo, tengo que invocar el tipeo de pato: si hace todo lo que una clase interna podría hacer ... desde un punto de vista Pythonic, probablemente es hora de aburrirse con la división de pelos
Mike Rodent
21
Una clase interna, por supuesto, implica una relación con la clase externa, que generalmente tiene que ver con el ámbito de uso implícito de la clase interna o, de lo contrario, un espacio de nombres organizacional.
Acumenus
60

Estás intentando acceder a la instancia de clase de Outer, desde la instancia de clase interna. Así que solo use el método de fábrica para construir una instancia interna y pasarle una instancia externa.

class Outer(object):

    def createInner(self):
        return Outer.Inner(self)

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

        def inner_method(self):
            self.outer_instance.anothermethod()
Kitlbast
fuente
Esta es exactamente la respuesta correcta; debe elegirse como la respuesta correcta, ya que proporciona una solución a la pregunta del OP. ¡Muchas gracias!
Daniel
28

tal vez estoy enojado, pero esto parece muy fácil de hecho: la cuestión es hacer que su clase interna dentro de un método de la clase externa ...

def do_sthg( self ):
    ...

def messAround( self ):

    outerClassSelf = self

    class mooble():
        def do_sthg_different( self ):
            ...
            outerClassSelf.do_sthg()

Además ... "self" solo se usa por convención, por lo que puede hacer esto:

def do_sthg( self ):
    ...

def messAround( outerClassSelf ):

    class mooble():
        def do_sthg_different( self ):
            ...
            outerClassSelf.do_sthg()

Se podría objetar que luego no puede crear esta clase interna desde fuera de la clase externa ... pero esto no es cierto:

class Bumblebee():

    def do_sthg( self ):
        print "sthg"

    def giveMeAnInnerClass( outerClassSelf ):

        class mooble():
            def do_sthg_different( self ):
                print "something diff\n"
                outerClassSelf.do_sthg()
        return mooble

luego, en algún lugar a millas de distancia:

blob = Bumblebee().giveMeAnInnerClass()()
blob.do_sthg_different()    

incluso empuje un poco el bote y extienda esta clase interna (NB para que super () funcione, debe cambiar la firma de clase de mooble a "class mooble (objeto)"

class InnerBumblebeeWithAddedBounce( Bumblebee().giveMeAnInnerClass() ):
    def bounce( self ):
        print "bounce"

    def do_sthg_different( self ):
        super( InnerBumblebeeWithAddedBounce, self ).do_sthg_different()
        print "and more different"


ibwab = InnerBumblebeeWithAddedBounce()    
ibwab.bounce()
ibwab.do_sthg_different()

luego

mrh1997 planteó un punto interesante sobre la herencia no común de clases internas entregadas mediante esta técnica. Pero parece que la solución es bastante sencilla:

class Fatty():
    def do_sthg( self ):
        pass

    class InnerFatty( object ):
        pass

    def giveMeAnInnerFattyClass(self):
        class ExtendedInnerFatty( Fatty.InnerFatty ):
            pass
        return ExtendedInnerFatty

fatty1 = Fatty()
fatty2 = Fatty()

innerFattyClass1 = fatty1.giveMeAnInnerFattyClass()
innerFattyClass2 = fatty2.giveMeAnInnerFattyClass()

print ( issubclass( innerFattyClass1, Fatty.InnerFatty ))
print ( issubclass( innerFattyClass2, Fatty.InnerFatty ))
mike roedor
fuente
1
Esto funciona para mi. ¿Cómo se llama exactamente esta construcción? ¿Una función de fábrica? ¿Un cierre?
nakedfanatic
No tengo ni idea de cómo se llama ... pero podría sugerir que la razón por la que los otros carteles no vieron esto es porque tal vez no se apreció completamente que la mayoría de las cosas en Python no son sagradas, incluido "self "(nombre arbitrario) y clases: son" objetos de primera clase ", lo que parece significar que puede manipularlos de maneras bastante extravagantes
Mike rodent
Buen trabajo. Inspirado por esta idea de solución y un poco de pensamiento, expandí partes de esta respuesta para crear otra respuesta a continuación con alguna explicación más.
Edward
1
@mikerodent Porque a su comentario (sobre mi respuesta), ahora he editado mi respuesta para eliminar la descripción de la "wiki de la comunidad". Como tú, no tengo ningún conocimiento sobre las respuestas de "wiki de la comunidad", así que es bueno que hayas arreglado tu error.
Edward
@mikerodent Además, no te ofendas, pero no creo que las respuestas de "wiki de comunidad" tengan una etiqueta de "wiki de comunidad", deben ser solo un tipo de respuesta. Probablemente habrá una página de ayuda con una descripción de las respuestas del "wiki de la comunidad" en Stack Overflow si estás interesado.
Edward
4

¿Quiere usar la herencia en lugar de anidar clases como esta? Lo que estás haciendo no tiene mucho sentido en Python.

Puede acceder a Outersome_method simplemente haciendo referencia Outer.some_methoddentro de los métodos de la clase interna, pero no va a funcionar como espera. Por ejemplo, si prueba esto:

class Outer(object):

    def some_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            Outer.some_method()

... obtendrá un TypeError al inicializar un Innerobjeto, porque Outer.some_methodespera recibir una Outerinstancia como primer argumento. (En el ejemplo anterior, básicamente está intentando llamar some_methodcomo un método de clase de Outer).

zenbot
fuente
1
La razón por la que probablemente no tenga sentido es porque estoy siendo intencionalmente hacky. Agregar métodos personalizados a un QuerySet en Django requiere un poco de código repetitivo, y estaba intentando derivar una forma inteligente de hacerlo usando Python que me permitiera crear una plantilla del código repetitivo y simplemente escribir las partes pertinentes en mi código Modelo.
T. Stone
Disculpas: no conozco a Django y, por lo tanto, no puedo sugerir una forma de modelar el código repetitivo, pero es posible que esté ladrando al árbol equivocado al intentar anidar sus clases. Tu clase interior no adquiere nada de tu clase exterior. Todo lo que hace que anidarlo dentro de Outer es obligarte a acceder a él a través de Outer.Inner, en lugar de simplemente Inner.
zenbot
3

Puede acceder fácilmente a la clase externa usando metaclase: después de la creación de la clase externa, verifique su atributo dict para cualquier clase (o aplique cualquier lógica que necesite, el mío es solo un ejemplo trivial) y establezca los valores correspondientes:

import six
import inspect


# helper method from `peewee` project to add metaclass
_METACLASS_ = '_metaclass_helper_'
def with_metaclass(meta, base=object):
    return meta(_METACLASS_, (base,), {})


class OuterMeta(type):
    def __new__(mcs, name, parents, dct):
        cls = super(OuterMeta, mcs).__new__(mcs, name, parents, dct)
        for klass in dct.values():
            if inspect.isclass(klass):
                print("Setting outer of '%s' to '%s'" % (klass, cls))
                klass.outer = cls

        return cls


# @six.add_metaclass(OuterMeta) -- this is alternative to `with_metaclass`
class Outer(with_metaclass(OuterMeta)):
    def foo(self):
        return "I'm outer class!"

    class Inner(object):
        outer = None  # <-- by default it's None

        def bar(self):
            return "I'm inner class"


print(Outer.Inner.outer)
>>> <class '__main__.Outer'>
assert isinstance(Outer.Inner.outer(), Outer)

print(Outer().foo())
>>> I'm outer class!
print(Outer.Inner.outer().foo())
>>> I'm outer class!
print(Outer.Inner().outer().foo())
>>> I'm outer class!
print(Outer.Inner().bar())
>>> I'm inner class!

Con este enfoque, puede vincular y referir fácilmente dos clases entre sí.

grundic
fuente
3

He creado un código Python para usar una clase externa de su clase interna , basado en una buena idea de otra respuesta para esta pregunta. Creo que es breve, simple y fácil de entender.

class higher_level__unknown_irrelevant_name__class:
    def __init__(self, ...args...):
        ...other code...
        # Important lines to access sub-classes.
        subclasses = self._subclass_container()
        self.some_subclass = subclasses["some_subclass"]
        del subclasses # Free up variable for other use.

    def sub_function(self, ...args...):
        ...other code...

    def _subclass_container(self):
        _parent_class = self # Create access to parent class.
        class some_subclass:
            def __init__(self):
                self._parent_class = _parent_class # Easy access from self.
                # Optional line, clears variable space, but SHOULD NOT BE USED
                # IF THERE ARE MULTIPLE SUBCLASSES as would stop their parent access.
                #  del _parent_class
        class subclass_2:
            def __init__(self):
                self._parent_class = _parent_class
        # Return reference(s) to the subclass(es).
        return {"some_subclass": some_subclass, "subclass_2": subclass_2}

El código principal, "listo para producción" (sin comentarios, etc.). Recuerde reemplazar todos los valores entre corchetes angulares (por ejemplo <x>) con el valor deseado.

class <higher_level_class>:
    def __init__(self):
        subclasses = self._subclass_container()
        self.<sub_class> = subclasses[<sub_class, type string>]
        del subclasses

    def _subclass_container(self):
        _parent_class = self
        class <sub_class>:
            def __init__(self):
                self._parent_class = _parent_class
        return {<sub_class, type string>: <sub_class>}

Explicación de cómo funciona este método (los pasos básicos):

  1. Cree una función nombrada _subclass_containerpara actuar como un contenedor para acceder a la variable self, una referencia a la clase de nivel superior (del código que se ejecuta dentro de la función).

    1. Cree una variable nombrada _parent_classque sea una referencia a la variable selfde esta función, a la que _subclass_containerpuedan acceder las selfsubclases de (evita conflictos de nombre con otras variables en subclases).

    2. Devuelve la subclase / subclases como un diccionario / lista para que el código que llame a la _subclass_containerfunción pueda acceder a las subclases internas.

  2. En la __init__función dentro de la clase de nivel superior (o donde sea necesario), reciba las subclases devueltas de la función _subclass_containeren la variable subclasses.

  3. Asignar subclases almacenadas en la subclassesvariable a atributos de la clase de nivel superior.

Algunos consejos para facilitar los escenarios:

Haciendo que el código para asignar las subclases a la clase de nivel superior sea más fácil de copiar y usar en clases derivadas de la clase de nivel superior que tienen su __init__ función cambiada:

Inserte antes de la línea 12 en el código principal:

def _subclass_init(self):

Luego inserte en esta función las líneas 5-6 (del código principal) y reemplace las líneas 4-7 con el siguiente código:

self._subclass_init(self)

Hacer posible la asignación de subclases a la clase de nivel superior cuando hay muchas / desconocidas cantidades de subclases.

Reemplace la línea 6 con el siguiente código:

for subclass_name in list(subclasses.keys()):
    setattr(self, subclass_name, subclasses[subclass_name])

Escenario de ejemplo en el que esta solución sería útil y donde el nombre de clase de nivel superior debería ser imposible de obtener:

Se class a:crea una clase, denominada "a" ( ). Tiene subclases que necesitan acceder a él (el padre). Una subclase se llama "x1". En esta subclase, a.run_func()se ejecuta el código .

Luego se crea otra clase, llamada "b", derivada de la clase "a" ( class b(a):). Después de eso, se ejecuta algún código b.x1()(llamando a la subfunción "x1" de b, una subclase derivada). Esta función se ejecuta a.run_func(), llamando a la función "run_func" de la clase "a", no a la función "run_func" de su padre, "b" (como debería), porque la función que se definió en la clase "a" está configurada para referirse a la función de la clase "a", ya que era su padre.

Esto causaría problemas (por ejemplo, si a.run_funcse ha eliminado la función) y la única solución sin reescribir el código en la clase a.x1sería redefinir la subclase x1con código actualizado para todas las clases derivadas de la clase "a", lo que obviamente sería difícil y no valdría la pena. eso.

Eduardo
fuente
gracias por tu lindo comentario sobre mi respuesta. No sabía nada de las etiquetas de "respuesta de wikis de la comunidad" y ahora lo he eliminado. Así que gracias también por señalar esto. Es posible que desee editar su respuesta en consecuencia, para evitar confusiones para futuros lectores.
Mike
3

Encontré esto .

Modificado para adaptarse a su pregunta:

class Outer(object):
    def some_method(self):
        # do something

    class _Inner(object):
        def __init__(self, outer):
            outer.some_method()
    def Inner(self):
        return _Inner(self)

Estoy seguro de que de alguna manera puedes escribir un decorador para esto o algo.

relacionado: ¿Cuál es el propósito de las clases internas de Python?

oveja voladora
fuente
2

Otra posibilidad:

class _Outer (object):
    # Define your static methods here, e.g.
    @staticmethod
    def subclassRef ():
        return Outer

class Outer (_Outer):
    class Inner (object):
        def outer (self):
            return _Outer

        def doSomething (self):
            outer = self.outer ()
            # Call your static mehthods.
            cls = outer.subclassRef ()
            return cls ()
tsnorri
fuente
1

Ampliando el pensamiento convincente de @ tsnorri, que el método externo puede ser un método estático :

class Outer(object):

    @staticmethod
    def some_static_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            self.some_static_method()    # <-- this will work later

    Inner.some_static_method = some_static_method

Ahora la línea en cuestión debería funcionar en el momento en que realmente se llame.

La última línea del código anterior le da a la clase Inner un método estático que es un clon del método estático Outer.


Esto aprovecha dos características de Python, que las funciones son objetos y el alcance es textual .

Por lo general, el ámbito local hace referencia a los nombres locales de la función actual (textualmente).

... o clase actual en nuestro caso. Así que los objetos "locales" a la definición de la clase Outer ( Innery some_static_method) pueden ser referidos directamente dentro de esa definición.

Bob Stein
fuente
1

Unos años tarde para la fiesta ... pero para ampliar @mike rodentla maravillosa respuesta, he proporcionado mi propio ejemplo a continuación que muestra cuán flexible es su solución y por qué debería ser (o debería haber sido) la aceptada. responder.

Python 3.7

class Parent():

    def __init__(self, name):
        self.name = name
        self.children = []

    class Inner(object):
        pass

    def Child(self, name):
        parent = self
        class Child(Parent.Inner):
            def __init__(self, name):
                self.name = name
                self.parent = parent
                parent.children.append(self)
        return Child(name)



parent = Parent('Bar')

child1 = parent.Child('Foo')
child2 = parent.Child('World')

print(
    # Getting its first childs name
    child1.name, # From itself
    parent.children[0].name, # From its parent
    # Also works with the second child
    child2.name,
    parent.children[1].name,
    # Go nuts if you want
    child2.parent.children[0].name,
    child1.parent.children[1].name
)

print(
    # Getting the parents name
    parent.name, # From itself
    child1.parent.name, # From its children
    child2.parent.name,
    # Go nuts again if you want
    parent.children[0].parent.name,
    parent.children[1].parent.name,
    # Or insane
    child2.parent.children[0].parent.children[1].parent.name,
    child1.parent.children[1].parent.children[0].parent.name
)


# Second parent? No problem
parent2 = Parent('John')
child3 = parent2.Child('Doe')
child4 = parent2.Child('Appleseed')

print(
    child3.name, parent2.children[0].name,
    child4.name, parent2.children[1].name,
    parent2.name # ....
)

Salida:

Foo Foo World World Foo World
Bar Bar Bar Bar Bar Bar Bar
Doe Doe Appleseed Appleseed John

Una vez más, una maravillosa respuesta, ¡apoyos para ti, Mike!

tirano saurio Rex
fuente
-2

Es demasiado simple:

Entrada:

class A:
    def __init__(self):
        pass

    def func1(self):
        print('class A func1')

    class B:
        def __init__(self):
            a1 = A()
            a1.func1()

        def func1(self):
            print('class B func1')

b = A.B()
b.func1()

Salida

clase A func1

clase B func1

Dhiman Ghosh
fuente
2
Mmmm, tenga en cuenta la pregunta y las palabras acceden a la clase externa . Por una feliz y bastante divertida coincidencia, sus clases internas y externas tienen un método func1. Y, igualmente divertido, creas un Adentro del constructor de B, que NO es en absoluto el Aobjeto de clase exterior de ese particular B.
Mike Rodent