Como acessar uma classe externa de uma classe interna?

101

Eu tenho uma situação assim ...

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

Como posso acessar o Outermétodo da Innerclasse a partir da classe?

T. Stone
fonte
Por que você está fazendo isso? O que há de errado com relacionamentos simples entre pares? Você está tentando "esconder" algo?
S.Lott
1
Um exemplo desse cenário poderia ser ter uma classe com subclasses que precisam acessar a classe externa, como normal, mas então precisar criar outra classe (nível superior) derivada da primeira classe. Nesse caso, as subclasses da segunda classe tentariam acessar o pai usando self.<original_parent_name>e obter a classe original, não a nova classe da qual são uma subclasse . Espero que as pessoas que estão lendo isso possam visualizar esse cenário difícil e ver o sentido de perguntas como essa.
Edward
1
Duplique para stackoverflow.com/questions/2278426 e tenha uma boa resposta.
xmedeko

Respostas:

67

Os métodos de uma classe aninhada não podem acessar diretamente os atributos de instância da classe externa.

Observe que não é necessariamente o caso de existir uma instância da classe externa, mesmo quando você cria uma instância da classe interna.

Na verdade, muitas vezes é recomendado não usar classes aninhadas, uma vez que o aninhamento não implica em nenhum relacionamento particular entre as classes internas e externas.

Daniel Vassallo
fonte
1
Hmm, Python é mais agitado que Java / C ++ ... veja minha resposta abaixo. Se estivermos dividindo cabelos, o que geralmente acontece, não posso dizer se minha "classe aninhada dentro do método" conta como uma classe interna. Neste ponto, entretanto, eu tenho que invocar a digitação de pato: se ela faz tudo que uma classe interna poderia fazer ... do ponto de vista pitônico, provavelmente é hora de ficar entediado com cabelos rachados
roedor de microfone
21
Uma classe interna, é claro, implica um relacionamento com a classe externa, normalmente tendo a ver com o escopo de uso implícito da classe interna ou de outro modo um namespace organizacional.
Acumenus,
60

Você está tentando acessar a instância da classe de Outer, a partir da instância da classe interna. Portanto, apenas use o método-fábrica para construir a instância Inner e passar a instância Outer para ela.

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
fonte
Esta é exatamente a resposta correta - deve ser escolhida como a resposta correta, pois fornece uma solução para a pergunta do OP - muito obrigado!
Daniel
28

talvez eu esteja louco, mas isso parece muito fácil - a questão é fazer sua classe interna dentro de um método da classe externa ...

def do_sthg( self ):
    ...

def messAround( self ):

    outerClassSelf = self

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

Além disso ... "self" é usado apenas por convenção, então você pode fazer o seguinte:

def do_sthg( self ):
    ...

def messAround( outerClassSelf ):

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

Pode-se objetar que você não pode criar essa classe interna de fora da classe externa ... mas isso não é verdade:

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

então, em algum lugar a quilômetros de distância:

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

empurre o barco um pouco para fora e estenda esta classe interna (NB para fazer com que super () funcione você tem que mudar a assinatura de classe de mooble para "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()

mais tarde

mrh1997 levantou um ponto interessante sobre a herança não comum de classes internas entregues usando essa técnica. Mas parece que a solução é bastante simples:

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 ))
roedor de microfone
fonte
1
Isso funciona para mim. Como essa construção é chamada exatamente? Uma função de fábrica? Um fechamento?
nakedfanatic
Não tenho a menor ideia de como é chamado ... mas posso sugerir que a razão pela qual os outros participantes não viram isso é porque talvez não tenhamos percebido que a maioria das coisas em Python não são sagradas, incluindo "eu "(nome arbitrário) e classes - eles são" objetos de primeira classe ", o que parece significar que você pode manipulá-los de maneiras bastante ultrajantes
roedor de microfone
Ótimo trabalho. Inspirado por esta ideia de solução e um pouco de reflexão, expandi partes desta resposta para criar outra resposta abaixo com mais algumas explicações.
Edward
1
@mikerodent Devido ao seu comentário (na minha resposta), eu agora editei minha resposta para remover a descrição de "wiki da comunidade". Como você, não tenho nenhum conhecimento sobre as respostas do "wiki da comunidade", então é bom que você corrigiu seu erro.
Edward
@mikerodent Além disso, sem ofensa, mas não acho que as respostas de "wiki da comunidade" tenham uma tag "wiki da comunidade", devem ser apenas um tipo de resposta. Provavelmente haverá uma página de ajuda com uma descrição das respostas do "wiki da comunidade" no Stack Overflow se você estiver interessado.
Edward
4

Você pretende usar herança, em vez de classes aninhadas como esta? O que você está fazendo não faz muito sentido em Python.

Você pode acessar o Outersome_method de apenas referenciando Outer.some_methodos métodos da classe interna, mas não vai funcionar como você espera. Por exemplo, se você tentar isso:

class Outer(object):

    def some_method(self):
        # do something

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

... você obterá um TypeError ao inicializar um Innerobjeto, porque Outer.some_methodespera receber uma Outerinstância como seu primeiro argumento. (No exemplo acima, você basicamente está tentando chamar some_methodcomo um método de classe de Outer.)

zenbot
fonte
1
A razão pela qual provavelmente não faz sentido é porque estou sendo intencionalmente hacky. Adicionar métodos personalizados a um QuerySet no Django requer um pouco de código clichê, e eu estava tentando derivar uma maneira inteligente de fazer isso usando python que me permitiu modelar o código clichê e simplesmente escrever as partes pertinentes em meu código de modelo.
T. Stone
Desculpas - eu não conheço Django e então não posso sugerir uma maneira de modelar o código clichê, mas você pode estar enlouquecendo a árvore errada ao tentar aninhar suas classes. Sua classe interna não adquire nada de sua classe externa. Tudo o que o aninhamento no Outer faz é forçá-lo a acessá-lo via Outer.Inner, em vez de simplesmente Inner.
zenbot
3

Você pode acessar facilmente a classe externa usando metaclasse: após a criação da classe externa, verifique seu atributo dict para quaisquer classes (ou aplique qualquer lógica que você precisar - o meu é apenas um exemplo trivial) e defina os valores correspondentes:

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!

Usando essa abordagem, você pode facilmente vincular e referir duas classes entre si.

Grundic
fonte
3

Criei algum código Python para usar uma classe externa de sua classe interna , com base em uma boa ideia de outra resposta para esta pergunta. Acho que é curto, simples e 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}

O código principal, "pronto para produção" (sem comentários, etc.). Lembre-se de substituir todos os valores entre colchetes angulares (por exemplo <x>) pelo valor desejado.

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>}

Explicação de como esse método funciona (as etapas básicas):

  1. Crie uma função chamada _subclass_containerpara atuar como um invólucro para acessar a variável self, uma referência à classe de nível superior (do código executado dentro da função).

    1. Crie uma variável chamada _parent_classque é uma referência à variável selfdesta função, que as subclasses de _subclass_containerpodem acessar (evita conflitos de nome com outras selfvariáveis ​​nas subclasses).

    2. Retorne a subclasse / subclasse como um dicionário / lista para que o código que chama a _subclass_containerfunção possa acessar as subclasses internas.

  2. Na __init__função dentro da classe de nível superior (ou onde mais necessário), receba as subclasses retornadas da função _subclass_containerna variável subclasses.

  3. Atribua subclasses armazenadas na subclassesvariável a atributos da classe de nível superior.

Algumas dicas para tornar os cenários mais fáceis:

Tornando o código para atribuir as subclasses à classe de nível superior mais fácil de copiar e ser usado em classes derivadas da classe de nível superior que têm sua __init__ função alterada:

Insira antes da linha 12 no código principal:

def _subclass_init(self):

Em seguida, insira nesta função as linhas 5-6 (do código principal) e substitua as linhas 4-7 pelo seguinte código:

self._subclass_init(self)

Tornar possível a atribuição de subclasses à classe de nível superior quando houver muitas / quantidades desconhecidas de subclasses.

Substitua a linha 6 pelo seguinte código:

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

Cenário de exemplo de onde esta solução seria útil e onde o nome da classe de nível superior deveria ser impossível de obter:

Uma classe chamada "a" ( class a:) é criada. Ele tem subclasses que precisam acessá-lo (o pai). Uma subclasse é chamada de "x1". Nesta subclasse, o código a.run_func()é executado.

Em seguida, outra classe, denominada "b", é criada, derivada da classe "a" ( class b(a):). Depois disso, algum código é executado b.x1()(chamando a subfunção "x1" de b, uma subclasse derivada). Esta função é executada a.run_func(), chamando a função "run_func" da classe "a", não a função "run_func" de seu pai, "b" (como deveria), porque a função que foi definida na classe "a" é definida como referência para a função da classe "a", visto que era sua mãe.

Isso causaria problemas (por exemplo, se a função a.run_funcfoi excluída) e a única solução sem reescrever o código na classe a.x1seria redefinir a subclasse x1com o código atualizado para todas as classes derivadas da classe "a", o que obviamente seria difícil e não valeria a pena isto.

Edward
fonte
obrigado pelo seu bom comentário sobre a minha resposta. Eu não sabia nada sobre tags de "respostas de wikis de comunidade" e agora as removi! Então, obrigado também por apontar isso. Você pode querer editar sua resposta de acordo, para evitar confusão para futuros leitores!
roedor de microfone de
3

Eu encontrei isso .

Ajustado para atender à sua pergunta:

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)

Tenho certeza de que você pode escrever um decorador para isso ou algo assim

related: Qual é o propósito das classes internas do python?

ovelha voadora
fonte
2

Outra possibilidade:

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
fonte
1

Expandindo o pensamento convincente de @ tsnorri, que o método externo pode ser um 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

Agora, a linha em questão deve funcionar no momento em que for realmente chamada.

A última linha no código acima fornece à classe Inner um método estático que é um clone do método estático Outer.


Isso tira proveito de dois recursos do Python, que as funções são objetos e o escopo é textual .

Normalmente, o escopo local faz referência aos nomes locais da função atual (textualmente).

... ou classe atual no nosso caso. Portanto, os objetos "locais" para a definição da classe externa ( Innere some_static_method) podem ser referidos diretamente nessa definição.

Bob Stein
fonte
1

Alguns anos atrasado para a festa ... mas para expandir a @mike rodentresposta maravilhosa de, eu forneci meu próprio exemplo abaixo que mostra o quão flexível é sua solução e por que deveria ser (ou deveria ter sido) aceita responda.

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 # ....
)

Resultado:

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

Mais uma vez, uma resposta maravilhosa, adereços para você, microfone!

tre-x
fonte
-2

É muito simples:

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()

Resultado

classe A func1

classe B func1

Dhiman Ghosh
fonte
2
Hmm, observe a pergunta e as palavras acessam a classe externa . Por uma feliz e divertida coincidência, suas classes externa e interna têm um método func1. E, igualmente divertido, você cria um Adentro do construtor de B, que NÃO é absolutamente o Aobjeto de classe externa daquele particular B.
roedor de microfone