Portée des classes imbriquées?

116

J'essaie de comprendre la portée des classes imbriquées en Python. Voici mon exemple de code:

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

La création de la classe ne se termine pas et j'obtiens l'erreur:

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

Essayer inner_var = Outerclass.outer_varne fonctionne pas. Je reçois:

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

J'essaye d'accéder à la statique à outer_varpartir de InnerClass.

Y a-t-il un moyen de faire cela?

Martineau
la source

Réponses:

105
class Outer(object):
    outer_var = 1

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

Ce n'est pas tout à fait la même chose que des choses similaires fonctionnent dans d'autres langues et utilisent la recherche globale au lieu d'étendre l'accès à outer_var. (Si vous modifiez l'objet Outerauquel le nom est lié, ce code utilisera cet objet la prochaine fois qu'il sera exécuté.)

Si vous souhaitez à la place que tous les Innerobjets aient une référence à un Outercar outer_varest vraiment un attribut d'instance:

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

Notez que l'imbrication de classes est quelque peu rare en Python et n'implique pas automatiquement une sorte de relation spéciale entre les classes. Vous feriez mieux de ne pas nicher. (Vous pouvez toujours définir un attribut de classe sur Outerpour Inner, si vous voulez.)

Martineau
la source
2
Il peut être utile d'ajouter avec quelle (s) version (s) de python votre réponse fonctionnera.
AJ.
1
J'ai écrit ceci avec 2.6 / 2.x à l'esprit, mais, en le regardant, je ne vois rien qui ne fonctionnerait pas de la même manière dans 3.x.
Je ne comprends pas tout à fait ce que vous voulez dire dans cette partie, "(Si vous modifiez à quel objet le nom Outer est lié, ce code utilisera cet objet la prochaine fois qu'il sera exécuté.)" Pouvez-vous m'aider à comprendre?
batbrat
1
@batbrat cela signifie que la référence à Outerest recherchée à nouveau à chaque fois que vous le faites Inner.inner_var. Donc, si vous reliez le nom Outerà un nouvel objet, Inner.inner_varcommencera à renvoyer ce nouvel objet.
Felipe
45

Je pense que vous pouvez simplement faire:

class OuterClass:
    outer_var = 1

    class InnerClass:
        pass
    InnerClass.inner_var = outer_var

Le problème que vous avez rencontré est dû à ceci:

Un bloc est un morceau de texte de programme Python qui est exécuté en tant qu'unité. Les blocs suivants sont: un module, un corps de fonction et une définition de classe.
(...)
Une portée définit la visibilité d'un nom dans un bloc.
(...)
La portée des noms définis dans un bloc de classe est limitée au bloc de classe; il ne s'étend pas aux blocs de code des méthodes - cela inclut les expressions génératrices puisqu'elles sont implémentées à l'aide d'une portée de fonction. Cela signifie que ce qui suit échouera:

   class A:  

       a = 42  

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

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

Ce qui précède signifie:
un corps de fonction est un bloc de code et une méthode est une fonction, alors les noms définis à partir du corps de fonction présent dans une définition de classe ne s'étendent pas au corps de fonction.

Paraphrasant ceci pour votre cas:
une définition de classe est un bloc de code, alors les noms définis à partir de la définition de classe interne présente dans une définition de classe externe ne s'étendent pas à la définition de classe interne.

eyquem
la source
2
Incroyable. Votre exemple échoue, affirmant que "le nom global 'a' n'est pas défini". Pourtant, la substitution d'une compréhension de liste [a + i for i in range(10)]lie avec succès Ab à la liste attendue [42..51].
George
6
@George Notez que l'exemple avec la classe A n'est pas le mien, il provient de la doc officielle Python dont j'ai donné le lien. Cet exemple échoue et cet échec est ce que l'on souhaite montrer dans cet exemple. En fait , list(a + i for i in range(10))est list((a + i for i in range(10)))c'est - à - dire list(a_generator). Ils disent qu'un générateur est implémenté avec une portée similaire à celle des fonctions.
eyquem
@George Pour moi, cela signifie que les fonctions agissent différemment selon qu'elles sont dans un module ou dans une classe. Dans le premier cas, une fonction sort pour trouver l'objet lié à un identifiant libre. Dans le second cas, une fonction, c'est-à-dire une méthode, ne sort pas de son corps. Les fonctions d'un module et les méthodes d'une classe sont en réalité deux types d'objets. Les méthodes ne sont pas seulement des fonctions de classe. C'est mon idée.
eyquem
5
@George: FWIW, ni l' list(...)appel ni la compréhension ne fonctionnent en Python 3. La documentation de Py3 est également légèrement différente reflétant cela. Il dit maintenant: " La portée des noms définis dans un bloc de classe est limitée au bloc de classe; elle ne s'étend pas aux blocs de code des méthodes - cela inclut les compréhensions et les expressions génératrices puisqu'elles sont implémentées en utilisant une portée de fonction. " (C'est moi qui souligne ).
martineau le
Je suis curieux de savoir pourquoi la liste (Aa + i pour i dans la plage (10)) ne fonctionne pas non plus, où j'ai fixé a par Aa, je pense que A peut être un nom global.
gaoxinge
20

Vous feriez peut-être mieux de ne pas utiliser de classes imbriquées. Si vous devez imbriquer, essayez ceci:

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

Ou déclarez les deux classes avant de les imbriquer:

class OuterClass:
    outer_var = 1

class InnerClass:
    inner_var = OuterClass.outer_var

OuterClass.InnerClass = InnerClass

(Après cela, vous pouvez del InnerClasssi vous en avez besoin.)

Jason Orendorff
la source
3

Solution la plus simple:

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

Cela vous oblige à être explicite, mais ne demande pas beaucoup d'efforts.

Cristian Garcia
la source
NameError: le nom 'OuterClass' n'est pas défini - -1
Mr_and_Mrs_D
3
Le but est d'y accéder depuis la portée de la classe Outer - plus une erreur similaire se produira si vous l'appelez à partir d'une portée statique à l'intérieur de Outer. Je vous suggère de supprimer ce message
Mr_and_Mrs_D
1

En Python, les objets mutables sont passés comme référence, vous pouvez donc passer une référence de la classe externe à la classe interne.

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
la source
2
Veuillez noter que vous avez un cycle de référence ici, et dans certains scénarios, une instance de cette classe ne sera pas libérée. Un exemple, avec cPython, si vous avez défini une __del__ méthode, le garbage collector ne pourra pas gérer le cycle de référence et les objets entreront dans gc.garbage. Le code ci-dessus, tel quel, n'est cependant pas problématique. La façon de gérer cela consiste à utiliser une référence faible . Vous pouvez lire la documentation sur lowref (2.7) ou lowref (3.5)
guyarad
0

Toutes les explications se trouvent dans la documentation Python Le didacticiel Python

Pour votre première erreur <type 'exceptions.NameError'>: name 'outer_var' is not defined. L'explication est:

Il n'y a pas de raccourci pour référencer des attributs de données (ou d'autres méthodes!) À partir de méthodes. Je trouve que cela augmente en fait la lisibilité des méthodes: il n'y a aucune chance de confondre les variables locales et les variables d'instance en parcourant une méthode.

cité dans The Python Tutorial 9.4

Pour votre deuxième erreur <type 'exceptions.NameError'>: name 'OuterClass' is not defined

Lorsqu'une définition de classe est laissée normalement (via la fin), un objet de classe est créé.

cité dans The Python Tutorial 9.3.1

Alors quand vous essayez inner_var = Outerclass.outer_var, le Quterclassn'a pas encore été créé, c'est pourquoiname 'OuterClass' is not defined

Une explication plus détaillée mais fastidieuse de votre première erreur:

Bien que les classes aient accès aux étendues des fonctions englobantes, elles n'agissent pas comme des étendues englobantes pour le code imbriqué dans la classe: Python recherche les fonctions englobant les noms référencés, mais jamais les classes englobantes. Autrement dit, une classe est une étendue locale et a accès aux étendues locales englobantes, mais elle ne sert pas d'étendue locale englobante à un code imbriqué supplémentaire.

cité de Learning.Python (5e) .Mark.Lutz

Andy Guo
la source