Comment accéder à une classe externe depuis une classe interne?

102

J'ai une situation comme ça ...

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

Comment puis-je accéder à la Outerméthode de la Innerclasse depuis la classe?

T. Stone
la source
Pourquoi fais-tu ça? Quel est le problème avec de simples relations entre pairs? Essayez-vous de «cacher» quelque chose?
S.Lott
1
Un exemple de ce scénario pourrait être d'avoir une classe avec des sous-classes qui doivent accéder à la classe externe, comme d'habitude, mais qui doivent ensuite créer une autre classe (niveau supérieur) dérivée de la première classe. Dans ce cas, les sous-classes de la seconde classe essaient d'accéder au parent en utilisant self.<original_parent_name>et d' obtenir la classe d'origine, pas la nouvelle classe dont elles sont une sous-classe . J'espère que les gens qui liront ceci pourront visualiser ce scénario difficile et voir l'intérêt de questions comme celle-ci.
Edward
1
Dupliquer sur stackoverflow.com/questions/2278426 et il a une bonne réponse.
xmedeko

Réponses:

68

Les méthodes d'une classe imbriquée ne peuvent pas accéder directement aux attributs d'instance de la classe externe.

Notez que ce n'est pas nécessairement le cas qu'une instance de la classe externe existe même lorsque vous avez créé une instance de la classe interne.

En fait, il est souvent déconseillé d'utiliser des classes imbriquées, car l'imbrication n'implique aucune relation particulière entre les classes interne et externe.

Daniel Vassallo
la source
1
Hmm, Python est plus frisk plus que Java / C ++ ... voir ma réponse ci-dessous. Si nous divisons les cheveux, ce que nous faisons habituellement, je ne pourrais pas vraiment vous dire si ma "classe imbriquée dans la méthode" compte comme une classe interne. À ce stade, cependant, je dois invoquer le typage du canard: s'il fait tout ce qu'une classe interne peut faire ... d'un point de vue pythonique, il est probablement temps de s'ennuyer avec des poils
fendus
21
Une classe interne implique bien sûr une relation avec la classe externe, ayant généralement à voir avec la portée d'utilisation implicite de la classe interne ou autrement un espace de noms organisationnel.
Acumenus
61

Vous essayez d'accéder à l'instance de classe d'Outer, à partir de l'instance de classe interne. Donc, utilisez simplement la méthode factory pour créer l'instance Inner et lui transmettre l'instance externe.

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
la source
C'est exactement la bonne réponse - devrait être choisie comme la bonne réponse car elle fournit une solution à la question du PO - merci beaucoup!
Daniel
28

peut-être que je suis en colère mais cela semble très facile en effet - le truc est de faire de votre classe intérieure une méthode de la classe extérieure ...

def do_sthg( self ):
    ...

def messAround( self ):

    outerClassSelf = self

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

De plus ... "self" n'est utilisé que par convention, vous pouvez donc faire ceci:

def do_sthg( self ):
    ...

def messAround( outerClassSelf ):

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

On pourrait objecter que vous ne pouvez pas créer cette classe interne à partir de l'extérieur de la classe externe ... mais ce n'est pas vrai:

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

puis, quelque part à des kilomètres:

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

même pousser un peu le bateau et étendre cette classe interne (NB pour que super () fonctionne, vous devez changer la signature de classe de mooble en "class mooble (object)"

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

plus tard

mrh1997 a soulevé un point intéressant sur l'héritage non commun des classes internes fournies à l'aide de cette technique. Mais il semble que la solution soit assez simple:

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 rongeur
la source
1
Cela fonctionne pour moi. Comment s'appelle exactement cette construction? Une fonction d'usine? Une fermeture?
nakedfanatic
Je n'ai pas la moindre idée de son nom ... mais pourrais-je suggérer que la raison pour laquelle les autres affiches n'ont pas vu cela est parce qu'il n'était peut-être pas pleinement apprécié que la plupart des choses en Python ne soient pas sacrées, y compris "soi "(nom arbitraire) et classes - ce sont des" objets de première classe ", ce qui semble signifier que vous pouvez les manipuler de manière assez scandaleuse
mike rodent
Bon travail. Inspiré par cette idée de solution et un peu de réflexion, j'ai développé des parties de cette réponse pour créer une autre réponse ci-dessous avec quelques explications supplémentaires.
Edward le
1
@mikerodent Parce que pour votre commentaire (sur ma réponse), j'ai maintenant édité ma réponse pour supprimer la description "wiki de la communauté". Comme vous, je n'ai aucune connaissance des réponses "wiki de la communauté", donc c'est bien que vous ayez corrigé votre erreur.
Edward
@mikerodent Aussi, pas d'offense, mais je ne pense pas que les réponses "wiki de la communauté" aient une balise "wiki de la communauté", elles doivent juste être un type de réponse. Il y aura probablement une page d'aide avec une description des réponses "wiki de la communauté" sur Stack Overflow si vous étiez intéressé.
Edward
4

Voulez-vous utiliser l'héritage plutôt que d'imbriquer des classes comme celle-ci? Ce que vous faites n'a pas beaucoup de sens en Python.

Vous pouvez accéder à la Outerméthode some_method en vous référant simplement Outer.some_methodaux méthodes de la classe interne, mais cela ne fonctionnera pas comme prévu. Par exemple, si vous essayez ceci:

class Outer(object):

    def some_method(self):
        # do something

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

... vous obtiendrez une TypeError lors de l'initialisation d'un Innerobjet, car Outer.some_methods'attend à recevoir une Outerinstance comme premier argument. (Dans l'exemple ci-dessus, vous essayez essentiellement d'appeler en some_methodtant que méthode de classe de Outer.)

zenbot
la source
1
La raison pour laquelle cela n'a probablement pas de sens est que je suis intentionnellement piraté. L'ajout de méthodes personnalisées à un QuerySet dans Django nécessite un peu de code standard, et j'essayais de dériver un moyen intelligent de le faire en utilisant python qui me permettait de modéliser le code standard et d'écrire simplement les parties pertinentes dans mon code modèle.
T.Stone
Excuses - Je ne connais pas Django et je ne peux donc pas suggérer un moyen de modéliser le code standard, mais vous pouvez aboyer le mauvais arbre en essayant d'imbriquer vos classes. Votre classe interne n'acquiert rien de votre classe externe. Tout imbriquer dans Outer vous oblige à y accéder via Outer.Inner, plutôt que simplement Inner.
zenbot
3

Vous pouvez facilement accéder à la classe externe en utilisant la métaclasse: après la création de la classe externe, vérifiez son attribut dict pour toutes les classes (ou appliquez toute logique dont vous avez besoin - la mienne est juste un exemple trivial) et définissez les valeurs correspondantes:

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!

En utilisant cette approche, vous pouvez facilement lier et référencer deux classes entre elles.

grondant
la source
3

J'ai créé du code Python pour utiliser une classe externe de sa classe interne , basée sur une bonne idée d'une autre réponse à cette question. Je pense que c'est court, simple et facile à comprendre.

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}

Le code principal, "prêt pour la production" (sans commentaires, etc.). N'oubliez pas de remplacer toutes les valeurs entre crochets angulaires (par exemple <x>) par la valeur souhaitée.

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

Explication du fonctionnement de cette méthode (les étapes de base):

  1. Créez une fonction nommée _subclass_containerpour agir comme un wrapper pour accéder à la variable self, une référence à la classe de niveau supérieur (à partir du code exécuté à l'intérieur de la fonction).

    1. Créez une variable nommée _parent_classqui est une référence à la variable selfde cette fonction, à laquelle les sous-classes de _subclass_containerpeuvent accéder (évite les conflits de nom avec d'autres selfvariables dans les sous-classes).

    2. Renvoie la sous-classe / sous-classe sous forme de dictionnaire / liste afin que le code appelant la _subclass_containerfonction puisse accéder aux sous-classes à l'intérieur.

  2. Dans la __init__fonction à l'intérieur de la classe de niveau supérieur (ou partout où cela est nécessaire), recevez les sous-classes retournées de la fonction _subclass_containerdans la variable subclasses.

  3. Attribuez des sous-classes stockées dans la subclassesvariable aux attributs de la classe de niveau supérieur.

Quelques conseils pour faciliter les scénarios:

Rendre le code pour affecter les sous-classes à la classe de niveau supérieur plus facile à copier et à utiliser dans les classes dérivées de la classe de niveau supérieur dont la __init__ fonction a été modifiée:

Insérer avant la ligne 12 dans le code principal:

def _subclass_init(self):

Insérez ensuite dans cette fonction les lignes 5-6 (du code principal) et remplacez les lignes 4-7 par le code suivant:

self._subclass_init(self)

Rendre possible l'attribution de sous-classes à la classe de niveau supérieur lorsqu'il existe de nombreuses / quantités inconnues de sous-classes.

Remplacez la ligne 6 par le code suivant:

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

Exemple de scénario où cette solution serait utile et où le nom de classe de niveau supérieur devrait être impossible à obtenir:

Une classe, nommée "a" ( class a:) est créée. Il a des sous-classes qui doivent y accéder (le parent). Une sous-classe est appelée "x1". Dans cette sous-classe, le code a.run_func()est exécuté.

Ensuite, une autre classe, nommée "b" est créée, dérivée de la classe "a" ( class b(a):). Après cela, du code s'exécute b.x1()(appelant la sous-fonction "x1" de b, une sous-classe dérivée). Cette fonction s'exécute a.run_func()en appelant la fonction "run_func" de la classe "a", et non la fonction "run_func" de son parent, "b" (comme il se doit), car la fonction qui a été définie dans la classe "a" est définie pour faire référence à la fonction de la classe "a", car c'était son parent.

Cela poserait des problèmes (par exemple si la fonction a.run_funca été supprimée) et la seule solution sans réécrire le code dans la classe a.x1serait de redéfinir la sous-classe x1avec du code mis à jour pour toutes les classes dérivées de la classe "a" ce qui serait évidemment difficile et ne vaut pas la peine il.

Edward
la source
merci pour votre gentil commentaire sur ma réponse. Je ne connaissais rien des balises "réponse aux wikis de la communauté" et je les ai maintenant supprimées! Merci également de l'avoir signalé. Vous voudrez peut-être modifier votre réponse en conséquence, pour éviter toute confusion pour les futurs lecteurs!
mike rodent
3

J'ai trouvé ça .

Ajusté pour répondre à votre question:

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)

Je suis sûr que vous pouvez en quelque sorte écrire un décorateur pour ceci ou quelque chose

Related: Quel est le but des classes internes de python?

mouton volant
la source
2

Une autre possibilité:

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
la source
1

Expansion sur la pensée convaincante de @ tsnorri, selon laquelle la méthode externe peut être une méthode statique :

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

Maintenant, la ligne en question devrait fonctionner au moment où elle est effectivement appelée.

La dernière ligne du code ci-dessus donne à la classe Inner une méthode statique qui est un clone de la méthode statique Outer.


Cela tire parti de deux fonctionnalités Python, à savoir que les fonctions sont des objets et la portée est textuelle .

En général, la portée locale fait référence aux noms locaux de la fonction actuelle (textuellement).

... ou classe actuelle dans notre cas. Ainsi, les objets "locaux" à la définition de la classe externe ( Inneret some_static_method) peuvent être référencés directement dans cette définition.

Bob Stein
la source
1

Quelques années de retard à la fête ... mais pour développer @mike rodentla réponse merveilleuse de 's, j'ai donné mon propre exemple ci-dessous qui montre à quel point sa solution est flexible et pourquoi elle aurait dû (ou aurait être) acceptée répondre.

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

Production:

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

Encore une fois, une réponse merveilleuse, des accessoires à vous mike!

tre-x
la source
-2

C'est trop simple:

Contribution:

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

Production

classe A func1

classe B func1

Dhiman Ghosh
la source
2
Hmm, notez la question et les mots accèdent à la classe externe . Par une heureuse coïncidence assez amusante, vos classes extérieure et intérieure ont toutes deux une méthode func1. Et, tout aussi amusant, vous créez un Aà l'intérieur du constructeur de B, qui n'est absolument PAS l' Aobjet de classe externe de ce particulier B.
mike rodent