Brève description des règles de cadrage?


472

Quelles sont exactement les règles de délimitation de Python?

Si j'ai du code:

code1
class Foo:
   code2
   def spam.....
      code3
      for code4..:
       code5
       x()

Où se xtrouve? Certains choix possibles incluent la liste ci-dessous:

  1. Dans le fichier source joint
  2. Dans l'espace de noms de classe
  3. Dans la définition de fonction
  4. Dans la variable d'index de boucle for
  5. À l'intérieur de la boucle for

Il y a aussi le contexte pendant l'exécution, lorsque la fonction spamest passée ailleurs. Et peut-être que les fonctions lambda passent un peu différemment?

Il doit y avoir une simple référence ou un algorithme quelque part. C'est un monde déroutant pour les programmeurs Python intermédiaires.


2
Les règles de portée sont décrites de manière assez sommaire - mais aussi complète - dans la documentation Python: docs.python.org/3/reference/… .
jefe2000

Réponses:


420

En fait, une règle concise pour la résolution de l'étendue de Python, tirée de Learning Python, 3rd. Ed. . (Ces règles sont spécifiques aux noms de variables et non aux attributs. Si vous la référencez sans point, ces règles s'appliquent.)

Règle LEGB

  • L ocal - Noms attribués de quelque manière que ce soit dans une fonction ( defou lambda), et non déclarés globaux dans cette fonction

  • E nclosing-function - Noms attribués dans la portée locale de toutes les fonctions ( defou lambda) enfermant statiquement , de l'intérieur vers l'extérieur

  • G lobal (module) - Noms attribués au niveau supérieur d'un fichier de module, ou en exécutant une globalinstruction dans un deffichier

  • B onstruit en (Python) - Les noms réservés dans le module noms intégrés: open, range, SyntaxError, etc.

Donc, dans le cas de

code1
class Foo:
    code2
    def spam():
        code3
        for code4:
            code5
            x()

La forboucle n'a pas son propre espace de noms. Dans l'ordre LEGB, les portées seraient

  • L: en local def spam(en code3, code4et code5)
  • E: Toutes les fonctions englobantes (si l'exemple entier était dans une autre def)
  • G: Y en avait-il xdéclaré globalement dans le module (en code1)?
  • B: Tout intégré xen Python.

xne sera jamais trouvé dans code2(même dans les cas où vous pourriez vous y attendre, voir la réponse d'Antti ou ici ).


45
Comme mise en garde contre l'accès global - la lecture d'une variable globale peut se produire sans déclaration explicite, mais y écrire sans déclarer global (var_name) créera à la place une nouvelle instance locale.
Peter Gibson

12
En fait @Peter, global(var_name)est syntaxiquement incorrect. La syntaxe correcte serait global var_namesans parenthèses. Vous avez cependant un argument valable.
martineau

Si c'est le cas, pourquoi la variable "y" de foo n'est-elle pas visible par la "barre" ci-dessous: >>> def foo(x): ... y = x ... def bar(z): ... y = z ... bar(5) ... print x,y ... >>> foo(3) 3 3
Jonathan Mayer

3
@Jonathan: Parce que chacun yest écrit et qu'il n'y a pas de global ydéclarations - voir le commentaire de @ Peter.
martineau

@LakshmanPrasad Il tombe dans "E", mais a un comportement spécial qui mérite d'être mentionné: c'est une variable de classe, donc c'est un "global" parmi ses objets. Lui attribuant sera conduire à unpredicted et difficile à des problèmes de débogage si vous ne savez pas ce que vous faites.
Ctrl-C

157

Essentiellement, la seule chose en Python qui introduit une nouvelle portée est une définition de fonction. Les classes sont un peu un cas particulier dans la mesure où tout ce qui est défini directement dans le corps est placé dans l'espace de noms de la classe, mais elles ne sont pas directement accessibles depuis les méthodes (ou classes imbriquées) qu'elles contiennent.

Dans votre exemple, il n'y a que 3 étendues dans lesquelles x sera recherché:

  • portée du spam - contenant tout ce qui est défini dans code3 et code5 (ainsi que code4, votre variable de boucle)

  • La portée globale - contenant tout ce qui est défini dans le code1, ainsi que Foo (et tout ce qui change après)

  • L'espace de noms intégré. Un petit cas particulier - il contient les différentes fonctions et types intégrés de Python tels que len () et str (). En général, cela ne devrait être modifié par aucun code utilisateur, alors attendez-vous à ce qu'il contienne les fonctions standard et rien d'autre.

D'autres étendues n'apparaissent que lorsque vous introduisez une fonction imbriquée (ou lambda) dans l'image. Ceux-ci se comporteront à peu près comme vous vous attendez cependant. La fonction imbriquée peut accéder à tout dans la portée locale, ainsi qu'à tout ce qui se trouve dans la portée de la fonction englobante. par exemple.

def foo():
    x=4
    def bar():
        print x  # Accesses x from foo's scope
    bar()  # Prints 4
    x=5
    bar()  # Prints 5

Restrictions:

Les variables dans des étendues autres que les variables de la fonction locale sont accessibles, mais ne peuvent pas être renvoyées à de nouveaux paramètres sans syntaxe supplémentaire. Au lieu de cela, l'affectation créera une nouvelle variable locale au lieu d'affecter la variable dans la portée parent. Par exemple:

global_var1 = []
global_var2 = 1

def func():
    # This is OK: It's just accessing, not rebinding
    global_var1.append(4) 

    # This won't affect global_var2. Instead it creates a new variable
    global_var2 = 2 

    local1 = 4
    def embedded_func():
        # Again, this doen't affect func's local1 variable.  It creates a 
        # new local variable also called local1 instead.
        local1 = 5
        print local1

    embedded_func() # Prints 5
    print local1    # Prints 4

Afin de modifier réellement les liaisons des variables globales à partir d'une portée de fonction, vous devez spécifier que la variable est globale avec le mot-clé global. Par exemple:

global_var = 4
def change_global():
    global global_var
    global_var = global_var + 1

Actuellement, il n'existe aucun moyen de faire de même pour les variables dans les étendues de fonctions englobantes , mais Python 3 introduit un nouveau mot clé, " nonlocal" qui agira de la même manière que global, mais pour les étendues de fonctions imbriquées.


111

Il n'y avait pas de réponse complète concernant le temps Python3, j'ai donc fait une réponse ici. La plupart de ce qui est décrit ici est détaillé dans la résolution 4.2.2 des noms de la documentation Python 3.

Comme indiqué dans d'autres réponses, il existe 4 étendues de base, le LEGB, pour Local, Enclosing, Global et Builtin. En plus de ceux-ci, il existe une portée spéciale, le corps de classe , qui ne comprend pas de portée englobante pour les méthodes définies dans la classe; toutes les affectations au sein du corps de classe rendent la variable à partir de là liée dans le corps de classe.

Surtout, aucune instruction de bloc, outre defet class, crée une portée variable. Dans Python 2, une compréhension de liste ne crée pas une étendue de variable, cependant dans Python 3 la variable de boucle dans les compréhensions de liste est créée dans une nouvelle étendue.

Démontrer les particularités du corps de classe

x = 0
class X(object):
    y = x
    x = x + 1 # x is now a variable
    z = x

    def method(self):
        print(self.x) # -> 1
        print(x)      # -> 0, the global x
        print(y)      # -> NameError: global name 'y' is not defined

inst = X()
print(inst.x, inst.y, inst.z, x) # -> (1, 0, 1, 0)

Ainsi, contrairement au corps de fonction, vous pouvez réaffecter la variable au même nom dans le corps de classe, pour obtenir une variable de classe avec le même nom; d'autres recherches sur ce nom se résolvent à la place à la variable de classe.


L'une des plus grandes surprises pour de nombreux nouveaux arrivants en Python est qu'une forboucle ne crée pas une portée variable. Dans Python 2, les listes de compréhension ne créent pas non plus de portée (contrairement aux générateurs et aux interprétations de dict!). Au lieu de cela, elles fuient la valeur dans la fonction ou la portée globale:

>>> [ i for i in range(5) ]
>>> i
4

Les compréhensions peuvent être utilisées comme un moyen astucieux (ou affreux si vous voulez) de créer des variables modifiables dans les expressions lambda en Python 2 - une expression lambda crée une portée variable, comme le defferait l' instruction, mais dans lambda aucune instruction n'est autorisée. L'affectation étant une instruction en Python signifie qu'aucune affectation de variable dans lambda n'est autorisée, mais une compréhension de liste est une expression ...

Ce comportement a été corrigé dans Python 3 - aucune expression de compréhension ni aucune variable de fuite de générateur.


Le global signifie vraiment la portée du module; le module python principal est le __main__; tous les modules importés sont accessibles via la sys.modulesvariable; pour accéder à l' __main__un peut utiliser sys.modules['__main__'], ou import __main__; il est parfaitement acceptable d'y accéder et d'y attribuer des attributs; ils apparaîtront comme des variables dans la portée globale du module principal.


Si un nom est jamais attribué à dans la portée actuelle (sauf dans la portée de classe), il sera considéré comme appartenant à cette portée, sinon il sera considéré comme appartenant à toute portée englobante qui affecte à la variable (il pourrait ne pas être affecté encore, ou pas du tout), ou enfin la portée mondiale. Si la variable est considérée comme locale, mais qu'elle n'est pas encore définie ou a été supprimée, la lecture de la valeur de la variable se traduira par UnboundLocalError, qui est une sous-classe de NameError.

x = 5
def foobar():
    print(x)  # causes UnboundLocalError!
    x += 1    # because assignment here makes x a local variable within the function

# call the function
foobar()

La portée peut déclarer qu'elle souhaite explicitement modifier la variable globale (portée du module), avec le mot clé global:

x = 5
def foobar():
    global x
    print(x)
    x += 1

foobar() # -> 5
print(x) # -> 6

Cela est également possible même s'il a été masqué dans la portée englobante:

x = 5
y = 13
def make_closure():
    x = 42
    y = 911
    def func():
        global x # sees the global value
        print(x, y)
        x += 1

    return func

func = make_closure()
func()      # -> 5 911
print(x, y) # -> 6 13

En python 2, il n'y a pas de moyen facile de modifier la valeur dans la portée englobante; généralement, cela est simulé en ayant une valeur mutable, telle qu'une liste de longueur 1:

def make_closure():
    value = [0]
    def get_next_value():
        value[0] += 1
        return value[0]

    return get_next_value

get_next = make_closure()
print(get_next()) # -> 1
print(get_next()) # -> 2

Cependant en python 3, le nonlocalvient à la rescousse:

def make_closure():
    value = 0
    def get_next_value():
        nonlocal value
        value += 1
        return value
    return get_next_value

get_next = make_closure() # identical behavior to the previous example.

La nonlocaldocumentation indique que

Les noms répertoriés dans une instruction non locale, contrairement à ceux répertoriés dans une instruction globale, doivent faire référence à des liaisons préexistantes dans une étendue englobante (l'étendue dans laquelle une nouvelle liaison doit être créée ne peut pas être déterminée sans ambiguïté).

c'est-à-dire nonlocalfait toujours référence à la portée externe non globale la plus interne où le nom a été lié (c'est-à-dire affecté à, y compris utilisé comme forvariable cible, dans la withclause ou comme paramètre de fonction).


Toute variable qui n'est pas considérée comme étant locale à la portée actuelle, ou toute portée englobante, est une variable globale. Un nom global est recherché dans le dictionnaire global du module; s'il n'est pas trouvé, le global est ensuite recherché depuis le module intégré; le nom du module a été changé de python 2 à python 3; en python 2 c'était __builtin__et en python 3 il s'appelle maintenant builtins. Si vous attribuez à un attribut du module intégré, il sera visible par la suite à n'importe quel module en tant que variable globale lisible, à moins que ce module ne les masque avec sa propre variable globale du même nom.


La lecture du module intégré peut également être utile; supposons que vous souhaitiez la fonction d'impression de style python 3 dans certaines parties du fichier, mais que d'autres parties du fichier utilisent toujours l' printinstruction. Dans Python 2.6-2.7, vous pouvez obtenir la printfonction Python 3 avec:

import __builtin__

print3 = __builtin__.__dict__['print']

En from __future__ import print_functionfait, la printfonction n'est pas importée n'importe où dans Python 2 - au lieu de cela, elle désactive simplement les règles d'analyse pour l' printinstruction dans le module actuel, se manipulant printcomme tout autre identificateur de variable, et permettant ainsi à printla fonction d'être recherchée dans les commandes internes.


23

Les règles de portée pour Python 2.x ont déjà été décrites dans d'autres réponses. La seule chose que j'ajouterais est qu'en Python 3.0, il y a aussi le concept d'une portée non locale (indiquée par le mot-clé 'nonlocal'). Cela vous permet d'accéder directement aux étendues externes et ouvre la possibilité de faire quelques astuces soignées, y compris des fermetures lexicales (sans hacks laids impliquant des objets mutables).

EDIT: Voici le PEP avec plus d'informations à ce sujet.


23

Un exemple de portée un peu plus complet:

from __future__ import print_function  # for python 2 support

x = 100
print("1. Global x:", x)
class Test(object):
    y = x
    print("2. Enclosed y:", y)
    x = x + 1
    print("3. Enclosed x:", x)

    def method(self):
        print("4. Enclosed self.x", self.x)
        print("5. Global x", x)
        try:
            print(y)
        except NameError as e:
            print("6.", e)

    def method_local_ref(self):
        try:
            print(x)
        except UnboundLocalError as e:
            print("7.", e)
        x = 200 # causing 7 because has same name
        print("8. Local x", x)

inst = Test()
inst.method()
inst.method_local_ref()

production:

1. Global x: 100
2. Enclosed y: 100
3. Enclosed x: 101
4. Enclosed self.x 101
5. Global x 100
6. global name 'y' is not defined
7. local variable 'x' referenced before assignment
8. Local x 200

6
C'est une excellente réponse. Cependant, je pense que les différences entre methodet method_local_refdoivent être soulignées. methodpeut accéder à la variable globale et l'imprimer comme dans 5. Global x. Mais method_local_refne peut pas car plus tard, il définit une variable locale avec ce même nom. Vous pouvez tester cela en supprimant la x = 200ligne et voir la différence
kiril

@brianray: Et z?
Malik A. Rumi

@kiril J'ai ajouté une note à ce sujet
brianray

@ MalikA.Rumi J'ai supprimé z car ce n'était pas intéressant
brianray

Étonnamment, c'est la seule explication claire des portées Python, que j'ai pu trouver sur l'ensemble de SO. En utilisant simplement un exemple très basique. Merci!
not2qubit

13

Python résout vos variables avec - généralement - trois espaces de noms disponibles.

À tout moment pendant l'exécution, il existe au moins trois étendues imbriquées dont les espaces de noms sont directement accessibles: l'étendue la plus interne, recherchée en premier, contient les noms locaux; les espaces de noms de toutes les fonctions englobantes, qui sont recherchés en commençant par l'étendue englobante la plus proche; la portée intermédiaire, recherchée ensuite, contient les noms globaux du module actuel; et la portée la plus externe (recherchée en dernier) est l'espace de noms contenant les noms intégrés.

Il y a deux fonctions: globalset localsqui vous montrent le contenu de deux de ces espaces de noms.

Les espaces de noms sont créés par des packages, des modules, des classes, la construction d'objets et des fonctions. Il n'y a pas d'autres saveurs d'espaces de noms.

Dans ce cas, l'appel à une fonction nommée xdoit être résolu dans l'espace de noms local ou l'espace de noms global.

Local dans ce cas, est le corps de la fonction méthode Foo.spam.

Global est - bien - global.

La règle consiste à rechercher les espaces locaux imbriqués créés par les fonctions de méthode (et les définitions de fonctions imbriquées), puis à effectuer une recherche globale. C'est ça.

Il n'y a pas d'autre portée. L' forinstruction (et d'autres instructions composées comme ifet try) ne crée pas de nouvelles étendues imbriquées. Uniquement les définitions (packages, modules, fonctions, classes et instances d'objets.)

Dans une définition de classe, les noms font partie de l'espace de noms de classe. code2, par exemple, doit être qualifié par le nom de la classe. Généralement Foo.code2. Cependant, self.code2cela fonctionnera également parce que les objets Python regardent la classe contenante comme solution de secours.

Un objet (une instance d'une classe) a des variables d'instance. Ces noms se trouvent dans l'espace de noms de l'objet. Ils doivent être qualifiés par l'objet. ( variable.instance.)

À partir d'une méthode de classe, vous avez des sections locales et globales. Vous dites self.variablede choisir l'instance comme espace de noms. Vous remarquerez qu'il selfs'agit d'un argument pour chaque fonction membre de la classe, ce qui en fait une partie de l'espace de noms local.

Voir Python Règles Scope , Champ d' application Python , la portée des variables .


5
C'est obsolète. Depuis la version 2.1 (il y a 7 ans), il existe plus de deux étendues, car les fonctions imbriquées introduisent de nouvelles étendues, de sorte qu'une fonction au sein d'une fonction aura accès à sa portée locale, à la portée des fonctions englobantes et à la portée globale (également intégrées).
Brian

Je suis désolé, ce n'est plus le cas. Python has two namespaces available. Global and local-to-something.
Rizwan Kassim

9

Où se trouve x?

x n'est pas trouvé car vous ne l'avez pas défini. :-) Il pourrait être trouvé dans code1 (global) ou code3 (local) si vous le mettez là.

code2 (membres de la classe) ne sont pas visibles pour coder à l'intérieur des méthodes de la même classe - vous y accéderez généralement en utilisant self code4 / code5 (boucles) vivent dans la même portée que code3, donc si vous écriviez à x là-dedans, vous changeriez l'instance x définie dans code3, pas un nouveau x.

Python a une portée statique, donc si vous passez du `` spam '' à une autre fonction, le spam aura toujours accès aux globaux du module dont il est issu (défini dans le code1), et à tout autre périmètre contenant (voir ci-dessous). Les membres de code2 seraient à nouveau accessibles via self.

lambda n'est pas différent de def. Si vous avez un lambda utilisé dans une fonction, cela revient à définir une fonction imbriquée. Dans Python 2.2 et versions ultérieures, les étendues imbriquées sont disponibles. Dans ce cas, vous pouvez lier x à n'importe quel niveau d'imbrication de fonction et Python récupérera l'instance la plus interne:

x= 0
def fun1():
    x= 1
    def fun2():
        x= 2
        def fun3():
            return x
        return fun3()
    return fun2()
print fun1(), x

2 0

fun3 voit l'instance x de la portée contenante la plus proche, qui est la portée de la fonction associée à fun2. Mais les autres instances x, définies dans fun1 et globalement, ne sont pas affectées.

Avant nested_scopes - en Python pré-2.1 et en 2.1, sauf si vous demandez spécifiquement la fonctionnalité à l'aide d'une importation future - les étendues de fun1 et fun2 ne sont pas visibles pour fun3, donc la réponse de S.Lott est valable et vous obtiendrez le x global :

0 0

1

En Python,

toute variable à laquelle est affectée une valeur est locale au bloc dans lequel l'affectation apparaît.

Si une variable est introuvable dans la portée actuelle, veuillez vous référer à la commande LEGB.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.