web-dev-qa-db-fra.com

Accédez à la classe externe à partir de la classe interne dans python

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 méthode de la classe Outer à partir de la classe Inner?

Modifier - Merci pour les réponses. Je conclus que je dois réévaluer la façon dont j'avais conçu cela pour être mis en œuvre et trouver une méthode plus robuste.

78
T. Stone

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

Notez qu'il 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 internes et externes.

51
Daniel Vassallo

Vous essayez d'accéder à l'instance de classe Outer, à partir de l'instance de classe interne. Il suffit donc d'utiliser la méthode d'usine pour créer une instance interne et lui passer une 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()
45
Kitlbast

peut-être que je suis fou, mais cela semble très facile en effet - le fait 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 ensuite créer cette classe interne à 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 le bateau un peu 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 à propos de 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 ))
24
mike rodent

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

Vous pouvez accéder à la méthode de Outer en référençant simplement Outer.some_method dans les 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 objet Inner, car Outer.some_method s'attend à recevoir une instance de Outer comme premier argument. (Dans l'exemple ci-dessus, vous essayez essentiellement d'appeler some_method comme méthode de classe de Outer.)

4
zenbot

J'ai créé du code Python pour utiliser un classe externe à partir de sa classe interne, basé sur une bonne idée de un autre réponse pour 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, "production ready" (sans commentaires, etc.). N'oubliez pas de remplacer toutes les valeurs entre parenthèses (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_container Pour 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_class Qui est une référence à la variable self de cette fonction, à laquelle les sous-classes de _subclass_container Peuvent accéder (évite les conflits de nom avec les autres self variables dans les sous-classes).

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

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

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

Quelques conseils pour simplifier 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 qui ont leur __init__ fonction 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 sous-classes/quantités inconnues.

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é.

Puis une autre classe, nommée "b" est créée, dérivée de la classe "a" (class b(a):). Après cela, du code exécute b.x1() (appelant la sous-fonction "x1" de b, une sous-classe dérivée). Cette fonction exécute a.run_func(), appelant la fonction "run_func" de la classe "a", pas 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 causerait des problèmes (par exemple si la fonction a.run_func A été supprimée) et la seule solution sans réécrire le code dans la classe a.x1 Serait de redéfinir la sous-classe x1 Avec une mise à jour code pour toutes les classes dérivées de la classe "a", ce qui serait évidemment difficile et n'en vaut pas la peine.

3
Edward

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

Vous pouvez facilement accéder à la classe externe à l'aide de la métaclasse: après la création de la classe externe, vérifiez son attribut dict pour toutes les classes (ou appliquez la logique dont vous avez besoin - la mienne n'est qu'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.

2
grundic

Développant la pensée convaincante de @ tsnorri, que 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 réellement 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 externe.


Cela profite de deux fonctionnalités Python, 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 (textuellement) actuelle.

... ou la classe actuelle dans notre cas. Obtient donc "local" à la définition de la classe Outer (Inner et some_static_method) peuvent être mentionnés directement dans cette définition.

1
Bob Stein

j'ai trouvé ce .

Ajusté pour répondre à votre question, c'est la réponse:

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 :)
/modifier: n pe

1
flying sheep