web-dev-qa-db-fra.com

Portée des classes imbriquées?

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 classe ne se termine pas et j'obtiens l'erreur suivante:

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

En essayant inner_var = Outerclass.outer_var ne fonctionne pas. Je reçois:

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

J'essaie d'accéder à la statique outer_var de InnerClass.

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

113
user214870
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 utilise la recherche globale au lieu d'étendre l'accès à outer_var. (Si vous changez l'objet auquel le nom Outer est lié, alors ce code utilisera cet objet la prochaine fois qu'il sera exécuté.)

Si vous voulez plutôt que tous les objets Inner aient une référence à un Outer parce que outer_var est 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 inhabituelle en Python et n'implique pas automatiquement une relation particulière entre les classes. Tu ferais mieux de ne pas nidifier. (Vous pouvez toujours définir un attribut de classe sur Outer à Inner, si vous le souhaitez.)

104
Roger Pate

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 élément du texte du programme Python exécuté en tant qu'unité. Voici les blocs: un module, un corps de fonction et une définition de classe.
(...)
Une étendue 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 de générateur car elles sont implémentées à l'aide d'une portée de fonction. Cela signifie que les éléments suivants vont échouer:

   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. Les noms définis en dehors du corps de fonction présent dans une définition de classe ne s'étendent pas au corps de fonction.

En paraphrasant ceci pour votre cas:
Une définition de classe est un bloc de code. Par conséquent, les noms définis dans la définition de classe interne présente dans une définition de classe externe ne sont pas étendus à la définition de classe interne.

44
eyquem

Vous pourriez être mieux lotis si vous n'utilisez simplement pas les 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 InnerClass Si tu as besoin de.)

20
Jason Orendorff

La solution la plus simple:

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

Cela exige que vous soyez explicite, mais cela ne demande pas beaucoup d'effort.

4
Cristian Garcia

Dans Python sont passés en tant que référence, vous pouvez donc transmettre 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)
1
T. Alpers

Toutes les explications peuvent être trouvées dans Documentation Python Le Python Tutorial

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

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

cité de Le 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é de Le Python Didacticiel 9.3.1

Alors quand tu essaies inner_var = Outerclass.outer_var, le Quterclass n'a pas encore été créé, c'est pourquoi name '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 portées des fonctions englobantes, elles n'agissent pas comme des portées au code imbriqué dans la classe: Python recherche les fonctions englobant les noms référencés, mais jamais les classes englobantes. En d'autres termes, une classe est une étendue locale et a accès à des étendues locales englobantes, mais elle ne sert pas d'étendue locale englobante pour le code imbriqué.

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

0
Andy Guo