Résolution de noms dans votre code – Real Python

By | mars 18, 2020

trouver un expert Python

Le concept de portée détermine comment les variables et les noms sont recherchés dans votre code. Il détermine la visibilité d'une variable dans le code. La portée d'un nom ou d'une variable dépend de l'endroit dans votre code où vous créez cette variable. Le concept de portée Python est généralement présenté à l'aide d'une règle connue sous le nom de Règle LEGB.

Les lettres de l'acronyme LEGB signifient Local, boîtier, global et intégré portées. Cela résume non seulement les niveaux de portée Python mais également la séquence d'étapes que Python suit lors de la résolution de noms dans un programme.

Dans ce didacticiel, vous apprendrez:

  • Quoi portées sont et comment ils fonctionnent en Python
  • Pourquoi est-il important de connaître Portée Python
  • Qu'est-ce que Règle LEGB est et comment Python l'utilise pour résoudre les noms
  • Comment modifier le comportement standard de la portée Python en utilisant global et non local
  • Quoi outils liés à la portée Offres Python et comment les utiliser

Grâce à ces connaissances, vous pouvez profiter des étendues Python pour écrire des programmes plus fiables et plus faciles à gérer. L'utilisation de la portée Python vous aidera à éviter ou à minimiser les bogues liés à la collision de noms ainsi qu'à la mauvaise utilisation des noms globaux dans vos programmes.

Vous tirerez le meilleur parti de ce didacticiel si vous connaissez les concepts Python intermédiaires tels que les classes, les fonctions, les fonctions internes, les variables, les exceptions, les compréhensions, les fonctions intégrées et les structures de données standard.

Comprendre la portée

En programmation, le portée d'un nom définit la zone d'un programme dans laquelle vous pouvez accéder sans ambiguïté à ce nom, comme les variables, les fonctions, les objets, etc. Un nom ne sera visible et accessible que par le code dans sa portée. Plusieurs langages de programmation profitent de la possibilité d'éviter les collisions de noms et les comportements imprévisibles. Le plus souvent, vous distinguerez deux étendues générales:

  1. Portée mondiale: Les noms que vous définissez dans cette étendue sont disponibles pour tout votre code.

  2. Portée locale: Les noms que vous définissez dans cette étendue sont uniquement disponibles ou visibles pour le code dans l'étendue.

La portée est apparue parce que les premiers langages de programmation (comme BASIC) noms mondiaux. Avec ce type de nom, n'importe quelle partie du programme peut modifier n'importe quelle variable à tout moment, donc la maintenance et le débogage de gros programmes peuvent devenir un véritable cauchemar. Pour travailler avec des noms globaux, vous devez garder à l'esprit tout le code en même temps pour savoir à tout moment quelle est la valeur d'un nom donné. Ce fut un effet secondaire important de ne pas avoir de portées.

Certaines langues comme Python utilisent portée pour éviter ce genre de problème. Lorsque vous utilisez un langage qui implémente la portée, vous n'avez aucun moyen d'accéder à toutes les variables d'un programme à tous les emplacements de ce programme. Dans ce cas, votre capacité à accéder à un nom donné dépendra de l'endroit où vous avez défini ce nom.

Les noms dans vos programmes auront la portée du bloc de code dans lequel vous les définissez. Lorsque vous pouvez accéder à la valeur d'un nom donné de quelque part dans votre code, vous dites que le nom est portée. Si vous ne pouvez pas accéder au nom, vous direz que le nom est hors champ.

Noms et étendues en Python

Étant donné que Python est un langage à typage dynamique, les variables en Python prennent naissance lorsque vous leur attribuez une valeur pour la première fois. D'un autre côté, les fonctions et les classes sont disponibles une fois que vous les avez définies à l'aide def ou classe, respectivement. Enfin, les modules existent après leur importation. En résumé, vous pouvez créer des noms Python via l'une des opérations suivantes:

Opération Déclaration
Affectations x = valeur
Opérations d'importation module d'importation ou à partir du nom d'importation du module
Définitions des fonctions def my_func (): ...
Définitions d'arguments dans le contexte des fonctions def my_func (arg1, arg2, ... argN): ...
Définitions des classes classe MyClass: ...

Toutes ces opérations créent ou, dans le cas des affectations, mettent à jour de nouveaux noms Python car toutes affectent un nom à une variable, une constante, une fonction, une classe, une instance, un module ou un autre objet Python.

Python utilise l'emplacement de l'affectation ou de la définition de nom pour l'associer à une étendue particulière. En d'autres termes, l'endroit où vous attribuez ou définissez un nom dans votre code détermine la portée ou la visibilité de ce nom.

Par exemple, si vous attribuez une valeur à un nom dans une fonction, ce nom aura un portée Python locale. En revanche, si vous attribuez une valeur à un nom en dehors de toutes les fonctions – disons, au niveau supérieur d'un module – alors ce nom aura un portée globale de Python.

Étendue Python vs espace de noms

En Python, le concept de portée est étroitement lié au concept d'espace de noms. Comme vous l'avez appris jusqu'à présent, une étendue Python détermine où dans votre programme un nom est visible. Les étendues Python sont implémentées sous forme de dictionnaires qui mappent les noms aux objets. Ces dictionnaires sont communément appelés espaces de noms. Ce sont les mécanismes concrets que Python utilise pour stocker les noms. Ils sont stockés dans un attribut spécial appelé .__ dict__.

Les noms au niveau supérieur d'un module sont stockés dans l'espace de noms du module. En d'autres termes, ils sont stockés dans le module .__ dict__ attribut. Jetez un œil au code suivant:

>>>

>>> importation sys
>>> sys.__dict__.clés()
dict_keys (['__name__', '__doc__', '__package__',..., 'argv', 'ps1', 'ps2'])

Après avoir importé sys, vous pouvez utiliser .clés() inspecter les clés de sys .__ dict__. Cela renvoie une liste avec tous les noms définis au niveau supérieur du module. Dans ce cas, vous pouvez dire que .__ dict__ contient l'espace de noms de sys et est une représentation concrète de la portée du module.

Comme autre exemple, supposons que vous devez utiliser le nom ps1, qui est défini dans sys. Si tu sais comment .__ dict__ et les espaces de noms fonctionnent en Python, alors vous pouvez référencer ps1 d'au moins deux manières différentes:

  1. Utilisation de la notation par points sur le nom du module dans le formulaire module.name
  2. Utilisation d'une opération d'abonnement sur .__ dict__ sous la forme module .__ dict__['name']

Jetez un œil au code suivant:

>>>

>>> sys.ps1
'>>>'
>>> sys.__dict__[[[[«ps1»]
'>>>'

Une fois que vous avez importé sys vous pouvez accéder ps1 en utilisant la notation par points sur sys. Vous pouvez également accéder ps1 à l'aide d'une recherche de clé de dictionnaire avec la clé «ps1». Les deux actions retournent le même résultat, '>>>'.

Chaque fois que vous utilisez un nom, tel qu'une variable ou un nom de fonction, Python recherche dans différents niveaux de portée (ou espaces de noms) pour déterminer si le nom existe ou non. Si le nom existe, vous en obtiendrez toujours la première occurrence. Sinon, vous obtiendrez une erreur. Vous couvrirez ce mécanisme de recherche dans la section suivante.

Utilisation de la règle LEGB pour l'étendue Python

Python résout les noms en utilisant le soi-disant Règle LEGB, qui est nommé d'après la portée Python pour les noms. Les lettres dans LEGB représentent Local, Enclosing, Global et Built-in. Voici un bref aperçu de la signification de ces termes:

  • Portée locale (ou fonction) est le bloc de code ou le corps de toute fonction Python ou lambda expression. Cette étendue Python contient les noms que vous définissez dans la fonction. Ces noms ne seront visibles qu'à partir du code de la fonction. Il est créé lors de l'appel de fonction, ne pas lors de la définition de la fonction, vous aurez donc autant d'étendues locales différentes que d'appels de fonction. Cela est vrai même si vous appelez la même fonction plusieurs fois ou de manière récursive. Chaque appel entraînera la création d'une nouvelle portée locale.

  • Périmètre clos (ou non local) est une étendue spéciale qui n'existe que pour les fonctions imbriquées. Si la portée locale est une fonction interne ou imbriquée, la portée englobante est la portée de la fonction externe ou englobante. Cette étendue contient les noms que vous définissez dans la fonction englobante. Les noms dans la portée englobante sont visibles à partir du code des fonctions internes et englobantes.

  • Portée globale (ou module) est la portée la plus élevée d'un programme, d'un script ou d'un module Python. Cette étendue Python contient tous les noms que vous définissez au niveau supérieur d'un programme ou d'un module. Les noms dans cette étendue Python sont visibles de partout dans votre code.

  • Portée intégrée est une étendue Python spéciale qui est créée ou chargée chaque fois que vous exécutez un script ou ouvrez une session interactive. Cette portée contient des noms tels que des mots clés, des fonctions, des exceptions et d'autres attributs intégrés à Python. Les noms dans cette étendue Python sont également disponibles de partout dans votre code. Il est automatiquement chargé par Python lorsque vous exécutez un programme ou un script.

La règle LEGB est une sorte de procédure de recherche de nom, qui détermine l'ordre dans lequel Python recherche les noms. Par exemple, si vous référencez un nom donné, Python recherchera ce nom séquentiellement dans la portée locale, englobante, globale et intégrée. Si le nom existe, vous obtiendrez sa première occurrence. Sinon, vous obtiendrez une erreur.

En résumé, lorsque vous utilisez des fonctions imbriquées, les noms sont résolus en vérifiant d'abord la portée locale ou la portée locale de la fonction la plus interne. Ensuite, Python examine toutes les étendues englobantes des fonctions externes de la portée la plus intérieure à la portée la plus extérieure. Si aucune correspondance n'est trouvée, Python examine les étendues globales et intégrées. S'il ne trouve pas le nom, vous obtiendrez une erreur.

À tout moment pendant l'exécution, vous aurez au maximum quatre étendues Python actives – locale, englobante, globale et intégrée – selon l'endroit où vous vous trouvez dans le code. En revanche, vous aurez toujours au moins deux étendues actives, qui sont les étendues globales et intégrées. Ces deux portées seront toujours disponibles pour vous.

Fonctions: la portée locale

le portée locale ou la portée de la fonction est une portée Python créée lors des appels de fonction. Chaque fois que vous appelez une fonction, vous créez également une nouvelle portée locale. D'un autre côté, vous pouvez penser à chacun def déclaration et lambda expression comme modèle pour de nouvelles étendues locales. Ces étendues locales prendront naissance chaque fois que vous appelez la fonction à portée de main.

Par défaut, les paramètres et les noms que vous attribuez à l'intérieur d'une fonction n'existent que dans la fonction ou la portée locale associée à l'appel de fonction. Lorsque la fonction revient, la portée locale est détruite et les noms sont oubliés. Voici comment cela fonctionne:

>>>

>>> def carré(base):
...     résultat = base ** 2
...     impression(F«Le carré de base    est: résultat")
...
>>> carré(dix)
Le carré de 10 est: 100
>>> résultat  # N'est pas accessible depuis le carré extérieur ()
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
    résultat
NameError: le nom «résultat» n'est pas défini
>>> base  # N'est pas accessible depuis le carré extérieur ()
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
    base
NameError: le nom «base» n'est pas défini
>>> carré(20)
Le carré de 20 est: 400

carré() est une fonction qui calcule le carré d'un nombre donné, base. Lorsque vous appelez la fonction, Python crée une portée locale contenant les noms base (un argument) et résultat (une variable locale). Après le premier appel à carré(), base détient une valeur de dix et résultat détient une valeur de 100. La deuxième fois, les noms locaux ne se souviendront pas des valeurs qui y étaient stockées lors du premier appel de la fonction. Remarquerez que base détient maintenant la valeur 20 et résultat détient 400.

Étant donné que vous ne pouvez pas accéder aux noms locaux à partir d'instructions extérieures à la fonction, différentes fonctions peuvent définir des objets portant le même nom. Découvrez cet exemple:

>>>

>>> def cube(base):
...     résultat = base ** 3
...     impression(F«Le cube de base    est: résultat")
...
>>> cube(30)
Le carré de 30 est: 27000

Notez que vous définissez cube() en utilisant la même variable et le même paramètre que vous avez utilisé dans carré(). Cependant, depuis cube() ne peut pas voir les noms dans la portée locale de carré() et vice versa, les deux fonctions fonctionnent comme prévu sans collision de nom.

Vous pouvez éviter les collisions de noms dans vos programmes en utilisant correctement la portée Python locale. Cela rend également les fonctions plus autonomes et crée des unités de programme maintenables. De plus, comme vous ne pouvez pas modifier les noms locaux à partir d'emplacements distants dans votre code, vos programmes seront plus faciles à déboguer, à lire et à modifier.

Vous pouvez inspecter les noms et les paramètres d'une fonction à l'aide de .__code__, qui est un attribut contenant des informations sur le code interne de la fonction. Jetez un œil au code ci-dessous:

>>>

>>> carré.__code__.co_varnames
('base', 'résultat')
>>> carré.__code__.co_argcount
1
>>> carré.__code__.co_consts
(Aucun, 2, 'Le carré de', 'est:')
>>> carré.__code__.co_name
'carré'

Dans cet exemple de code, vous inspectez .__code__ sur carré(). Il s'agit d'un attribut spécial qui contient des informations sur le code d'une fonction Python. Dans ce cas, vous voyez que .co_varnames contient un tuple contenant les noms que vous définissez à l'intérieur carré().

Fonctions imbriquées: l'étendue du boîtier

Périmètre clos ou non local est observé lorsque vous imbriquez des fonctions dans d'autres fonctions. La portée englobante a été ajoutée dans Python 2.2. Il prend la forme de la portée locale des portées locales de toute fonction englobante. Les noms que vous définissez dans la portée Python englobante sont communément appelés noms non locaux. Considérez le code suivant:

>>>

>>> def external_func():
...     # Ce bloc est la portée locale de external_func ()
...     var = 100  # Un var non local
...     # C'est aussi la portée englobante de inner_func ()
...     def inner_func():
...         # Ce bloc est la portée locale de inner_func ()
...         impression(F"Impression de var depuis inner_func (): var")
...
...     inner_func()
...     impression(F"Impression de var depuis external_func (): var")
...
>>> external_func()
Impression de var depuis inner_func (): 100
Impression de var à partir de external_func (): 100
>>> inner_func()
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
NameError: le nom 'inner_func' n'est pas défini

Quand vous appelez external_func (), vous créez également une portée locale. La portée locale de external_func () est, en même temps, le champ d'application inner_func (). De l'Intérieur inner_func (), cette portée n'est ni la portée globale ni la portée locale. C’est une portée spéciale qui se situe entre ces deux portées et est connue sous le nom de délimitant la portée.

Tous les noms que vous créez dans la portée englobante sont visibles de l'intérieur inner_func (), à l'exception de ceux créés après avoir appelé inner_func (). Voici une nouvelle version de externe_fun () qui montre ce point:

>>>

>>> def external_func():
...     var = 100
...     def inner_func():
...         impression(F"Impression de var depuis inner_func (): var")
...         impression(F"Impression d'un autre_var à partir de inner_func (): another_var")
...
...     inner_func()
...     another_var = 200  # Ceci est défini après avoir appelé inner_func ()
...     impression(F"Impression de var depuis external_func (): var")
...
>>> external_func()
Impression de var depuis inner_func (): 100
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
    external_func()
  Fichier "", ligne sept, dans external_func
    inner_func()
  Fichier "", ligne 5, dans inner_func
    impression(F"Impression d'un autre_var à partir de inner_func (): another_var")
NameError: variable libre 'another_var' référencée avant affectation dans le cadre
    portée

Quand vous appelez external_func () le code descend jusqu'au point où vous appelez inner_func (). La dernière déclaration de inner_func () tente d'accéder another_var. À ce point, another_var n'est pas encore défini, donc Python soulève un NameError car il ne trouve pas le nom que vous essayez d'utiliser.

Enfin et surtout, vous ne pouvez pas modifier les noms dans la portée englobante depuis une fonction imbriquée à moins que vous ne les déclariez comme non local dans la fonction imbriquée. Vous découvrirez comment utiliser non local plus loin dans ce didacticiel.

Modules: la portée mondiale

À partir du moment où vous démarrez un programme Python, vous êtes dans la portée globale de Python. En interne, Python transforme le script principal de votre programme en un module appelé __principale__ pour maintenir l'exécution du programme principal. L'espace de noms de ce module est le principal portée mondiale de votre programme.

Si vous travaillez dans une session interactive Python, vous remarquerez que '__principale__' c'est aussi le nom de son module principal. Pour vérifier cela, ouvrez une session interactive et saisissez ce qui suit:

>>>

>>> __Nom__
'__principale__'

Chaque fois que vous exécutez un programme Python ou une session interactive comme dans le code ci-dessus, l'interpréteur exécute le code dans le module ou le script qui sert de point d'entrée à votre programme. Ce module ou script est chargé avec le nom spécial, __principale__. À partir de ce moment, vous pouvez dire que votre portée mondiale principale est celle de __principale__.

Pour inspecter les noms dans votre portée globale principale, vous pouvez utiliser dir (). Si vous appelez dir () sans arguments, vous obtiendrez alors la liste des noms qui vivent dans votre portée globale actuelle. Jetez un oeil à ce code:

>>>

>>> dir()
['__annotations__', '__builtins__',..., '__package__', '__spec__']
>>> var = 100  # Assigner var au niveau supérieur de __main__
>>> dir()
['__annotations__', '__builtins__',..., '__package__', '__spec__', 'var']

Quand vous appelez dir () sans arguments, vous obtenez la liste des noms disponibles dans votre étendue Python globale principale. Notez que si vous attribuez un nouveau nom (comme var ici) au niveau supérieur du module (qui est __principale__ ici), ce nom sera ajouté à la liste renvoyée par dir ().

Il n'y a qu'une seule étendue Python globale par exécution de programme. Cette portée existe jusqu'à ce que le programme se termine et que tous ses noms soient oubliés. Sinon, la prochaine fois que vous exécuterez le programme, les noms se souviendront de leurs valeurs lors de l'exécution précédente.

Vous pouvez accéder ou référencer la valeur de n'importe quel nom global à partir de n'importe quel endroit de votre code. Cela inclut les fonctions et les classes. Voici un exemple qui clarifie ces points:

>>>

>>> var = 100
>>> def func():
...     revenir var  # Vous pouvez accéder à var depuis func ()
...
>>> func()
100
>>> var  # Reste inchangé
100

À l'intérieur func (), vous pouvez accéder librement à la valeur de var. Cela n'a aucun effet sur votre nom global var, mais cela vous montre que var peut être librement accessible de l'intérieur func (). D'un autre côté, vous ne pouvez pas attribuer de noms globaux à l'intérieur des fonctions, sauf si vous les déclarez explicitement comme des noms globaux à l'aide d'un global , que vous verrez plus tard.

Chaque fois que vous attribuez une valeur à un nom en Python, deux choses peuvent se produire:

  1. Vous créer un nouveau nom
  2. Vous mise à jour un nom existant

Le comportement concret dépendra de la portée Python dans laquelle vous attribuez le nom. Si vous essayez d'attribuer une valeur à un nom global dans une fonction, vous créerez ce nom dans la portée locale de la fonction, en l'observant ou en le remplaçant. Cela signifie que vous ne pourrez pas modifier la plupart des variables qui ont été définies en dehors de la fonction depuis l'intérieur de la fonction.

Si vous suivez cette logique, vous vous rendrez compte que le code suivant ne fonctionnera pas comme vous pouvez vous y attendre:

>>>

>>> var = 100  # Une variable globale
>>> def incrément():
...     var = var + 1  # Essayez de mettre à jour une variable globale
...
>>> incrément()
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
    incrément()
  Fichier "", ligne 2, dans incrément
    var = var + 1
UnboundLocalError: variable locale 'var' référencée avant l'affectation

Dans incrément(), vous essayez d'incrémenter la variable globale, var. Puisque var n'est pas déclaré global à l'intérieur incrément(), Python crée une nouvelle variable locale du même nom, var, à l'intérieur de la fonction. Dans le processus, Python se rend compte que vous essayez d'utiliser le local var avant sa première mission (var + 1), il soulève donc une UnboundLocalError.

Voici un autre exemple:

>>>

>>> var = 100  # Une variable globale
>>> def func():
...     impression(var)  # Référence la variable globale, var
...     var = 200   # Définissez une nouvelle variable locale en utilisant le même nom, var
...
>>> func()
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
    func()
  Fichier "", ligne 2, dans func
    impression(var)
UnboundLocalError: variable locale 'var' référencée avant l'affectation

Vous vous attendez probablement à pouvoir imprimer le var et pouvoir mettre à jour var plus tard, mais encore une fois, vous obtenez un UnboundLocalError. Ce qui se passe ici, c'est que lorsque vous exécutez le corps de func (), Python décide que var est une variable locale car elle est affectée dans la portée de la fonction. Ce n'est pas un bug, mais un choix de conception. Python suppose que les noms attribués dans le corps d'une fonction sont locaux à cette fonction.

Jusqu'à présent, vous avez couvert trois étendues Python. Consultez l'exemple suivant pour obtenir un résumé de leur emplacement dans votre code et de la façon dont Python recherche les noms à travers eux:

>>>

>>> # Cette zone est la portée globale ou du module
>>> nombre = 100
>>> def external_func():
...     # Ce bloc est la portée locale de external_func ()
...     # C'est aussi la portée englobante de inner_func ()
...     def inner_func():
...         # Ce bloc est la portée locale de inner_func ()
...         impression(nombre)
...
...     inner_func()
...
>>> external_func()
100

Quand vous appelez external_func (), vous obtenez 100 imprimé sur votre écran. Mais comment Python recherche le nom nombre dans ce cas? En suivant la règle LEGB, vous rechercherez nombre aux endroits suivants:

  1. À l'intérieur inner_func (): Ceci est la portée locale, mais nombre n’existe pas là-bas.
  2. À l'intérieur external_func (): Ceci est la portée englobante, mais nombre n'y est pas défini non plus.
  3. Dans la portée du module: Ceci est la portée mondiale, et vous trouvez nombre là, vous pouvez donc imprimer nombre à l'écran.

Si nombre n’est pas défini à l’intérieur de la portée globale, alors Python poursuit la recherche en examinant la portée intégrée. Il s'agit du dernier élément de la règle LEGB, comme vous le verrez dans la section suivante.

intégrés: La portée intégrée

le portée intégrée est une étendue Python spéciale implémentée en tant que module de bibliothèque standard nommé intégrés en Python 3.x. Tous les objets intégrés de Python vivent dans ce module. Ils sont automatiquement chargés dans la portée intégrée lorsque vous exécutez l'interpréteur Python. Recherches Python intégrés dernier dans sa recherche LEGB, vous obtenez donc gratuitement tous les noms qu'il définit. Cela signifie que vous pouvez les utiliser sans importer de module.

Notez que les noms intégrés sont toujours chargés dans votre portée Python globale avec le nom spécial __builtins__, comme vous pouvez le voir dans le code suivant:

>>>

>>> dir()
['__annotations__', '__builtins__',..., '__package__', '__spec__']
>>> dir(__builtins__)
['ArithmeticError', 'AssertionError',..., 'tuple', 'type', 'vars', 'zip']

Dans la sortie du premier appel à dir (), tu peux voir ça __builtins__ est toujours présent dans la portée globale de Python. Si vous inspectez __builtins__ lui-même en utilisant dir (), vous obtiendrez alors la liste complète des noms intégrés Python.

La portée intégrée apporte plus de 150 noms à votre portée Python globale actuelle. Par exemple, dans Python 3.8, vous pouvez connaître le nombre exact de noms comme suit:

>>>

>>> len(dir(__builtins__))
152

Avec l'appel à len (), vous obtenez le nombre d'articles dans le liste retourné par dir (). Cela renvoie 152 noms qui incluent des exceptions, des fonctions, des types, des attributs spéciaux et d'autres objets intégrés Python.

Même si vous pouvez accéder gratuitement à tous ces objets intégrés Python (sans rien importer), vous pouvez également importer explicitement intégrés et accéder aux noms en utilisant la notation par points. Voici comment cela fonctionne:

>>>

>>> importation intégrés  # Importer des builds en tant que module standard
>>> dir(intégrés)
['ArithmeticError', 'AssertionError',..., 'tuple', 'type', 'vars', 'zip']
>>> intégrés.somme([[[[1, 2, 3, 4, 5])
15
>>> intégrés.max([[[[1, 5, 8, sept, 3])
8
>>> intégrés.trié([[[[1, 5, 8, sept, 3])
[1, 3, 5, 7, 8]
>>> intégrés.pow(dix, 2)
100

Vous pouvez importer intégrés comme vous le feriez pour tout autre module Python. A partir de là, vous pouvez accéder à tous les noms intégrés en utilisant la recherche d'attribut en pointillés ou des noms pleinement qualifiés. Cela peut être très utile si vous voulez vous assurer que vous n'aurez pas de collision de noms si l'un de vos noms globaux remplace un nom intégré.

Vous pouvez remplacer ou redéfinir n'importe quel nom intégré dans votre portée globale. Si vous le faites, gardez à l'esprit que cela affectera tout votre code. Jetez un œil à l'exemple suivant:

>>>

>>> abdos(-15)  # Utilisation standard d'une fonction intégrée
15
>>> abdos = 20  # Redéfinir un nom intégré dans la portée globale
>>> abdos(-15)
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
Erreur-type: l'objet 'int' ne peut pas être appelé

Si vous remplacez ou réaffectez abdos, puis l'original intégré abdos() est affecté partout dans votre code. Supposons maintenant que vous devez appeler l'original abdos() et vous oubliez que vous avez réaffecté le nom. Dans ce cas, lorsque vous appelez abdos() encore une fois, vous obtiendrez un Erreur-type car abdos contient désormais une référence à un entier, qui n'est pas appelable.

Si vous testez du code et que vous réattribuez accidentellement un nom intégré à l'invite interactive, vous pouvez soit redémarrer votre session, soit exécuter del nom pour supprimer la redéfinition de votre portée Python globale. De cette façon, vous restaurez le nom d'origine dans la portée intégrée. Si vous revisitez l'exemple de abdos(), alors vous pouvez faire quelque chose comme ceci:

>>>

>>> del abdos  # Supprimez les abs redéfinis de votre portée mondiale
>>> abdos(-15)  # Restaurer les abdos originaux ()
15

Lorsque vous supprimez la personnalisation abdos nom, vous supprimez le nom de votre portée globale. Cela vous permet d'accéder à l'original abdos() dans la portée intégrée à nouveau.

Pour contourner ce type de situation, vous pouvez importer explicitement intégrés puis utilisez des noms complets, comme dans le fragment de code suivant:

>>>

>>> importation intégrés
>>> intégrés.abdos(-15)
15

Une fois que vous importez explicitement intégrés, le nom du module est disponible dans votre étendue Python globale. À partir de ce moment, vous pouvez utiliser des noms complets pour obtenir sans ambiguïté les noms dont vous avez besoin intégrés, comme vous l'avez fait avec builtins.abs () dans l'exemple ci-dessus.

Pour résumer rapidement, certaines des implications de la portée Python sont présentées dans le tableau suivant:

action Code mondial Code local Code de fonction imbriquée
Noms d'accès ou de référence qui vivent dans la portée globale Oui Oui Oui
Modifier ou mettre à jour des noms qui vivent dans la portée globale Oui Non (sauf si déclaré global) Non (sauf si déclaré global)
Noms d'accès ou de référence qui vivent dans une portée locale Non Oui (sa propre portée locale), Non (autre portée locale) Oui (sa propre portée locale), Non (autre portée locale)
Remplacer les noms dans la portée intégrée Oui Oui (pendant l'exécution de la fonction) Oui (pendant l'exécution de la fonction)
Noms d'accès ou de référence qui vivent dans leur portée englobante N / A N / A Oui
Modifier ou mettre à jour des noms qui vivent dans leur portée englobante N / A N / A Non (sauf si déclaré non local)

En outre, le code dans différentes étendues peut utiliser le même nom pour différents objets. De cette façon, vous pouvez utiliser une variable locale nommée Spam et aussi une variable globale du même nom, Spam. Cependant, cela est considéré comme une mauvaise pratique de programmation.

Modification du comportement d'une étendue Python

Jusqu'à présent, vous avez appris comment fonctionne une étendue Python et comment ils restreignent la visibilité des variables, fonctions, classes et autres objets Python à certaines parties de votre code. Vous savez maintenant que vous pouvez accéder ou référencer noms mondiaux depuis n'importe quel endroit de votre code, mais ils peuvent être modifiés ou mis à jour à partir de la portée globale de Python.

Vous savez également que vous pouvez accéder noms locaux uniquement depuis l’étendue Python locale dans laquelle ils ont été créés ou depuis une fonction imbriquée, mais vous ne pouvez pas y accéder depuis l’étendue Python globale ou depuis d’autres étendues locales. De plus, vous avez appris que noms non locaux sont accessibles depuis des fonctions imbriquées, mais elles ne peuvent pas être modifiées ou mises à jour à partir de là.

Même si les étendues Python suivent ces règles générales par défaut, il existe des moyens de modifier ce comportement standard. Python fournit deux mots clés qui vous permettent de modifier le contenu des noms globaux et non locaux. Ces deux mots clés sont:

  1. global
  2. non local

Dans les deux sections suivantes, vous découvrirez comment utiliser ces mots clés Python pour modifier le comportement standard des étendues Python.

le global Déclaration

Vous savez déjà que lorsque vous essayez d'affecter une valeur à un nom global dans une fonction, vous créez un nouveau nom local dans l'étendue de la fonction. Pour modifier ce comportement, vous pouvez utiliser un global déclaration. Avec cette instruction, vous pouvez définir une liste de noms qui seront traités comme des noms globaux.

La déclaration comprend le global mot-clé suivi d'un ou plusieurs noms séparés par des virgules. Vous pouvez également utiliser plusieurs global instructions avec un nom (ou une liste de noms). Tous les noms que vous listez dans un global sera mappée à l'étendue globale ou de module dans laquelle vous les définissez.

Voici un exemple où vous essayez de mettre à jour une variable globale à partir d'une fonction:

>>>

>>> compteur = 0  # Un nom mondial
>>> def update_counter():
...     compteur = compteur + 1  # Échec de la tentative de mise à jour du compteur
...
>>> update_counter()
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
  
  
  
  Fichier "", ligne 2, dans update_counter
UnboundLocalError: variable locale 'compteur' référencée avant affectation

Lorsque vous essayez d'attribuer compteur à l'intérieur update_counter (), Python suppose que compteur est local à update_counter () et soulève un UnboundLocalError car vous essayez d'accéder à un nom qui n'est pas encore défini.

Si vous voulez que ce code fonctionne comme vous l'attendez ici, vous pouvez utiliser un global déclaration comme suit:

>>>

>>> compteur = 0  # Un nom mondial
>>> def update_counter():
...     global compteur  # Déclarer le compteur comme global
...     compteur = compteur + 1  # Mettre à jour le compteur avec succès
...
>>> update_counter()
>>> compteur
1
>>> update_counter()
>>> compteur
2
>>> update_counter()
>>> compteur
3

Dans cette nouvelle version de update_counter (), vous ajoutez la déclaration compteur global to the body of the function right before you try to change counter. With this tiny change, you’re mapping the name counter in the function scope to the same name in the global or module scope. From this point on, you can freely modify counter inside update_counter(). All the changes will reflect in the global variable.

With the statement global counter, you’re telling Python to look in the global scope for the name counter. This way, the expression counter = counter + 1 doesn’t create a new name in the function scope, but updates it in the global scope.

You can also use a global statement to create lazy global names by declaring them inside a function. Take a look at the following code:

>>>

>>> def create_lazy_name():
...     global lazy  # Create a global name, lazy
...     lazy = 100
...     revenir lazy
...
>>> create_lazy_name()
100
>>> lazy  # The name is now available in the global scope
100
>>> dir()
['__annotations__', '__builtins__',..., 'create_lazy_name', 'lazy']

When you call create_lazy_name(), you’re also creating a global variable called lazy. Notice that after calling the function, the name lazy is available in the global Python scope. If you inspect the global namespace using dir(), then you’ll see that lazy appears last in the list.

Finally, it’s worth noting that you can use global from inside any function or nested function and the names listed will always be mapped to names in the global Python scope.

Also notice that, even though using a global statement at the top level of a module is legal, it doesn’t make much sense because any name assigned in the global scope is already a global name by definition. Take a look at the following code:

>>>

>>> Nom = 100
>>> dir()
['__annotations__', '__builtins__',..., '__spec__', 'name']
>>> global Nom
>>> dir()
['__annotations__', '__builtins__',..., '__spec__', 'name']

The use of a global statement like global name doesn’t change anything in your current global scope, as you can see in the output of dir(). The variable Nom is a global variable whether you use global or not.

le nonlocal Statement

Similarly to global names, nonlocal names can be accessed from inner functions, but not assigned or updated. If you want to modify them, then you need to use a nonlocal déclaration. Avec un nonlocal statement, you can define a list of names that are going to be treated as nonlocal.

le nonlocal statement consists of the nonlocal keyword followed by one or more names separated by commas. These names will refer to the same names in the enclosing Python scope. The following example shows how you can use nonlocal to modify a variable defined in the enclosing or nonlocal scope:

>>>

>>> def func():
...     var = 100  # A nonlocal variable
...     def nested():
...         nonlocal var  # Declare var as nonlocal
...         var += 100
...
...     nested()
...     impression(var)
...
>>> func()
200

With the statement nonlocal var, you tell Python that you’ll be modifying var inside nested(). Then, you increment var using an augmented assignment operation. This change is reflected in the nonlocal name var, which now has a value of 200.

Unlike global, you can’t use nonlocal outside of a nested or enclosed function. To be more precise, you can’t use a nonlocal statement in either the global scope or in a local scope. Here’s an example:

>>>

>>> nonlocal my_var  # Try to use nonlocal in the global scope
        File "", line 1
SyntaxError: nonlocal declaration not allowed at module level
>>> def func():
...     nonlocal var  # Try to use nonlocal in a local scope
...     impression(var)
...
  File "", line 2
SyntaxError: no binding for nonlocal 'var' found

Here, you first try to use a nonlocal statement in the global Python scope. Puisque nonlocal only works inside an inner or nested function, you get a SyntaxError telling you that you can’t use nonlocal in a module scope. Notice that nonlocal doesn’t work inside a local scope either.

In contrast to global, you can’t use nonlocal to create lazy nonlocal names. Names must already exist in the enclosing Python scope if you want to use them as nonlocal names. This means that you can’t create nonlocal names by declaring them in a nonlocal statement in a nested function. Take a look at the following code example:

>>>

>>> def func():
...     def nested():
...         nonlocal lazy_var  # Try to create a nonlocal lazy name
...
  File "", line 3
SyntaxError: no binding for nonlocal 'lazy_var' found

In this example, when you try to define a nonlocal name using nonlocal lazy_var, Python immediately raises a SyntaxError because lazy_var doesn’t exist in the enclosing scope of nested().

Using Enclosing Scopes as Closures

Closures are a special use case of the enclosing Python scope. When you handle a nested function as data, the statements that make up that function are packaged together with the environment in which they execute. The resulting object is known as a closure. In other words, a closure is an inner or nested function that carries information about its enclosing scope, even though this scope has completed its execution.

Closures provide a way to retain state information between function calls. This can be useful when you want to write code based on the concept of lazy or delayed evaluation. Take a look at the following code for an example of how closures work and how you can take advantage of them in Python:

>>>

>>> def power_factory(exp):
...     def power(base):
...         revenir base ** exp
...     revenir power
...
>>> square = power_factory(2)
>>> square(dix)
100
>>> cube = power_factory(3)
>>> cube(dix)
1000
>>> cube(5)
125
>>> square(15)
225

Your closure factory function power_factory() takes an argument called exp. You can use this function to build closures that run different power operations. This works because each call to power_factory() gets its own set of state information. In other words, it gets its value for exp.

In the above example, the inner function power() is first assigned to square. In this case, the function remembers that exp equals 2. In the second example, you call power_factory() using 3 as an argument. This way, cube holds a function object, which remembers that exp est 3. Notice that you can freely reuse square et cube because they don’t forget their respective state information.

For a final example on how to use closures, suppose that you need to calculate the mean of some sample data. You collect the data through a stream of successive measurements of the parameter you’re analyzing. In this case, you can use a closure factory to generate a closure that remembers the previous measurements in the sample. Take a look at the following code:

>>>

>>> def signifier():
...     sample = []
...     def _mean(number):
...         sample.append(number)
...         revenir sum(sample) / len(sample)
...     revenir _mean
...
>>> current_mean = signifier()
>>> current_mean(dix)
10.0
>>> current_mean(15)
12.5
>>> current_mean(12)
12.333333333333334
>>> current_mean(11)
12.0
>>> current_mean(13)
12.2

The closure that you create in the above code remembers the state information of sample between calls of current_mean. This way, you can solve the problem in an elegant and Pythonic way.

Notice that if your data stream gets too large, then this function can become a problem in terms of memory usage. That’s because with each call to current_mean, sample will hold a bigger and bigger list of values. Take a look at the following code for an alternative implementation using nonlocal:

>>>

>>> def signifier():
...     total = 0
...     length = 0
...     def _mean(number):
...         nonlocal total, length
...         total += number
...         length += 1
...         revenir total / length
...     revenir _mean
...
>>> current_mean = signifier()
>>> current_mean(dix)
10.0
>>> current_mean(15)
12.5
>>> current_mean(12)
12.333333333333334
>>> current_mean(11)
12.0
>>> current_mean(13)
12.2

Even though this solution is more verbose, you don’t have an endlessly growing list anymore. You now have a single value for total et length. This implementation is a lot more efficient in terms of memory consumption than the previous solution.

Finally, you can find some examples of using closures in the Python standard library. Par exemple, functools provides a function named partial() that makes use of the closure technique to create new function objects that can be called using predefined arguments. Here’s an example:

>>>

>>> de functools import partial
>>> def power(exp, base):
...     revenir base ** exp
...
>>> square = partial(power, 2)
>>> square(dix)
100

You use partial to build a function object that remembers the state information, where exp=2. Then, you call this object to perform the power operation and get the final result.

Bringing Names to Scope With import

When you write a Python program, you typically organize the code into several modules. For your program to work, you’ll need to bring the names in those separate modules to your __main__ module. To do that, you need to import the modules or the names explicitly. This is the only way you can use those names in your main global Python scope.

Take a look at the following code for an example of what happens when you import some standard modules and names:

>>>

>>> dir()
['__annotations__', '__builtins__',..., '__spec__']
>>> import sys
>>> dir()
['__annotations__', '__builtins__',..., '__spec__', 'sys']
>>> import os
>>> dir()
['__annotations__', '__builtins__',..., '__spec__', 'os', 'sys']
>>> de functools import partial
>>> dir()
['__annotations__', '__builtins__',..., '__spec__', 'os', 'partial', 'sys']

You first import sys et os from the Python standard library. By calling dir() with no arguments, you can see that these modules are now available for you as names in your current global scope. This way, you can use dot notation to get access to the names that are defined in sys et os.

Dans les derniers import operation, you use the form de import . This way, you can use the imported name directly in your code. In other words, you don’t need to explicitly use the dot notation.

Discovering Unusual Python Scopes

You’ll find some Python structures where name resolution seems not to fit into the LEGB rule for Python scopes. These structures include:

In the next few sections, you’ll cover how Python scope works on these three structures. With this knowledge, you’ll be able to avoid subtle errors related to the use of names in these kinds of Python structures.

Comprehension Variables Scope

The first structure you’ll cover is the comprehension. A comprehension is a compact way to process all or part of the elements in a collection or sequence. You can use comprehensions to create lists, dictionaries, and sets.

Comprehensions consist of a pair of brackets ([]) or curly braces () containing an expression, followed by one or more pour clauses and then zero or one si clause per pour clause.

le pour clause in a comprehension works similarly to a traditional pour loop. The loop variable in a comprehension is local to the structure. Check out the following code:

>>>

>>> [[[[article pour article dans intervalle(5)]
[0, 1, 2, 3, 4]
>>> article  # Try to access the comprehension variable
Traceback (most recent call last):
  File "", line 1, in 
    article
NameError: name 'item' is not defined

Once you run the list comprehension, the variable article is forgotten and you can’t access its value anymore. It’s unlikely that you need to use this variable outside of the comprehension, but regardless, Python makes sure that its value is no longer available once the comprehension finishes.

Note that this only applies to comprehensions. When it comes to regular pour loops, the loop variable holds the last value processed by the loop:

>>>

>>> pour article dans intervalle(5):
...     impression(article)
...
0
1
2
3
4
>>> article  # Access the loop variable
4

You can freely access the loop variable article once the loop has finished. Here, the loop variable holds the last value processed by the loop, which is 4 in this example.

Exception Variables Scope

Another atypical case of Python scope that you’ll encounter is the case of the exception variable. The exception variable is a variable that holds a reference to the exception raised by a try déclaration. In Python 3.x, such variables are local to the sauf block and are forgotten when the block ends. Check out the following code:

>>>

>>> lst = [[[[1, 2, 3]
>>> try:
...     lst[[[[4]
... sauf IndexError comme err:
...     # The variable err is local to this block
...     # Here you can do anything with err
...     impression(err)
...
list index out of range
>>> err # Is out of scope
Traceback (most recent call last):
  File "", line 1, in 
    err
NameError: name 'err' is not defined

err holds a reference to the exception raised by the try clause. Vous pouvez utiliser err only inside the code block of the sauf clause. This way, you can say that the Python scope for the exception variable is local to the sauf code block. Also note that if you try to access err from outside the sauf block, then you’ll get a NameError. That’s because once the sauf block finishes, the name doesn’t exist anymore.

To work around this behavior, you can define an auxiliary variable out of the try statement and then assign the exception to that variable inside the sauf block. Check out the following example:

>>>

>>> lst = [[[[1, 2, 3]
>>> ex = Aucun
>>> try:
...     lst[[[[4]
... sauf IndexError comme err:
...     ex = err
...     impression(err)
...
list index out of range
>>> err  # Is out of scope
Traceback (most recent call last):
  File "", line 1, in 
NameError: name 'err' is not defined
>>> ex  # Holds a reference to the exception
list index out of range

You use ex as an auxiliary variable to hold a reference to the exception raised by the try clause. This can be useful when you need to do something with the exception object once the code block has finished. Note that if no exception is raised, then ex remains Aucun.

Class and Instance Attributes Scope

When you define a class, you’re creating a new local Python scope. The names assigned at the top level of the class live in this local scope. The names that you assigned inside a class statement don’t clash with names elsewhere. You can say that these names follow the LEGB rule, where the class block represents the L niveau.

Unlike functions, the class local scope isn’t created at call time, but at execution time. Each class object has its own .__dict__ attribute that holds the class scope or namespace where all the class attributes live. Check out this code:

>>>

>>> class UNE:
...     attr = 100
...
>>> UNE.__dict__.clés()
dict_keys(['__module__', 'attr', '__dict__', '__weakref__', '__doc__'])

When you inspect the keys of .__dict__ you’ll see that attr is in the list along with other special names. This dictionary represents the class local scope. The names in this scope are visible to all instances of the class and to the class itself.

To get access to a class attribute from outside the class, you need to use the dot notation as follows:

>>>

>>> class UNE:
...     attr = 100
...     impression(attr)  # Access class attributes directly
...
100
>>> UNE.attr  # Access a class attribute from outside the class
100
>>> attr  # Isn't defined outside A
Traceback (most recent call last):
  File "", line 1, in 
    attr
NameError: name 'attr' is not defined

Inside the local scope of UNE, you can access the class attributes directly, just like you did in the statement print(attr). To access any class attribute once the code block of the class is executed, you’ll need to use the dot notation or attribute reference, as you did with A.attr. Otherwise, you’ll get a NameError, because the attribute attr is local to the class block.

On the other hand, if you try to access an attribute that isn’t defined inside a class, then you’ll get an AttributeError. Check out the following example:

>>>

>>> UNE.undefined  # Try to access an undefined class attribute
Traceback (most recent call last):
  File "", line 1, in 
    UNE.undefined
AttributeError: type object 'A' has no attribute 'undefined'

In this example, you try to access the attribute undefined. Since this attribute doesn’t exist in UNE, you get an AttributeError telling you that UNE doesn’t have an attribute named undefined.

You can also access any class attribute using an instance of the class as follows:

>>>

>>> obj = UNE()
>>> obj.attr
100

Once you have the instance you can access the class attributes using the dot notation, as you did here with obj.attr. Class attributes are specific to the class object, but you can access them from any instances of the class. It’s worth noting that class attributes are common to all instances of a class. If you modify a class attribute, then the changes will be visible in all instances of the class.

Whenever you call a class, you’re creating a new instance of that class. Instances have their own .__dict__ attribute that holds the names in the instance local scope or namespace. These names are commonly called instance attributes and are local and specific to each instance. This means that if you modify an instance attribute, then the changes will be visible only to that specific instance.

To create, update, or access any instance attribute from inside the class, you need to use self along with the dot notation. Ici, self is a special attribute that represents the current instance. On the other hand, to update or access any instance attribute from outside the class, you need to create an instance and then use the dot notation. Here’s how this works:

>>>

>>> class UNE:
...     def __init__(self, var):
...         self.var = var  # Create a new instance attribute
...         self.var *= 2  # Update the instance attribute
...
>>> obj = UNE(100)
>>> obj.__dict__
'var': 200
>>> obj.var
200

The class UNE takes an argument called var, which is automatically doubled inside .__init__() using the assignment operation self.var *= 2. Note that when you inspect .__dict__ sur obj, you get a dictionary containing all instance attributes. In this case, the dictionary contains only the name var, whose value is now 200.

Even though you can create instance attributes within any method in a class, it’s good practice to create and initialize them inside .__init__(). Take a look at this new version of UNE:

>>>

>>> class UNE:
...     def __init__(self, var):
...         self.var = var
...
...     def duplicate_var(self):
...         revenir self.var * 2
...
>>> obj = UNE(100)
>>> obj.var
100
>>> obj.duplicate_var()
200
>>> UNE.var
Traceback (most recent call last):
  File "", line 1, in 
    UNE.var
AttributeError: type object 'A' has no attribute 'var'

Here, you modify UNE to add a new method called duplicate_var(). Then, you create an instance of UNE by passing in 100 to the class initializer. After that, you can now call duplicate_var() sur obj to duplicate the value stored in self.var. Finally, if you try to access var using the class object instead of an instance, then you’ll get an AttributeError because instance attributes can’t be accessed using class objects.

In general, when you’re writing object-oriented code in Python and you try to access an attribute, your program takes the following steps:

  1. Vérifier la instance local scope or namespace first.
  2. If the attribute is not found there, then check the class local scope or namespace.
  3. If the name doesn’t exist in the class namespace either, then you’ll get an AttributeError.

This is the underlying mechanism by which Python resolves names in classes and instances.

Although classes define a class local scope or namespace, they don’t create an enclosing scope for methods. Therefore, when you’re implementing a class, references to attributes and methods must be done using the dot notation:

>>>

>>> class UNE:
...     var = 100
...     def print_var(self):
...         impression(var)  # Try to access a class attribute directly
...
>>> UNE().print_var()
Traceback (most recent call last):
  File "", line 1, in 
    UNE().print_var()
  File "", line 4, in print_var
    impression(var)
NameError: name 'var' is not defined

Since classes don’t create an enclosing scope for methods, you can’t access var directly from within print_var() like you try to do here. To get access to class attributes from inside any method, you need to use the dot notation. To fix the problem in this example, change the statement print(var) inside print_var() à print(A.var) and see what happens.

You can override a class attribute with an instance attribute, which will modify the general behavior of your class. However, you can access both attributes unambiguously using the dot notation like in the following example:

>>>

>>> class UNE:
...     var = 100
...     def __init__(self):
...         self.var = 200
...
...     def access_attr(self):
...         # Use dot notation to access class and instance attributes
...         impression(f'The instance attribute is: self.var")
...         impression(f'The class attribute is: A.var")
...
>>> obj = UNE()
>>> obj.access_attr()
The instance attribute is: 200
The class attribute is: 100
>>> UNE.var  # Access class attributes
100
>>> UNE().var # Access instance attributes
200
>>> UNE.__dict__.clés()
dict_keys(['__module__', 'var', '__init__',..., '__getattribute__'])
>>> UNE().__dict__.clés()
dict_keys(['var'])

The above class has an instance attribute and a class attribute with the same name var. You can use the following code to access each of them:

  1. Instance: Utilisation self.var to access this attribute.
  2. Classe: Utilisation A.var to access this attribute.

Since both cases use the dot notation, there are no name collision problems.

Finally, notice that the class .__dict__ and the instance .__dict__ are totally different and independent dictionaries. That’s why class attributes are available immediately after you run or import the module in which the class was defined. In contrast, instance attributes come to life only after an object or instance is created.

There are many built-in functions that are closely related to the concept of Python scope and namespaces. In previous sections, you’ve used dir() to get information on the names that exist in a given scope. Besides dir(), there are some other built-in functions that can help you out when you’re trying to get information about a Python scope or namespace. In this section, you’ll cover how to work with:

Since all these are built-in functions, they’re available for free in the built-in scope. This means that you can use them at any time without importing anything. Most of these functions are intended to be used in an interactive session to get information on different Python objects. However, you can find some interesting use cases for them in your code as well.

globals()

In Python, globals() is a built-in function that returns a reference to the current global scope or namespace dictionary. This dictionary always stores the names of the current module. This means that if you call globals() in a given module, then you’ll get a dictionary containing all the names that you’ve defined in that module, right before the call to globals(). Here’s an example:

>>>

>>> globals()
'__name__': '__main__',..., '__builtins__': 
>>> my_var = 100
>>> globals()
'__name__': '__main__',..., 'my_var': 100

The first call to globals() returns a dictionary containing the names in your __main__ module or program. Note that when you assign a new name at the top level of the module, like in my_var = 100, the name is added to the dictionary returned by globals().

An interesting example of how you can use globals() in your code would be to dynamically dispatch functions that live in the global scope. Suppose you want to dynamically dispatch platform-dependent functions. To do this, you can use globals() as follows:

    1 # Filename: dispatch.py
    2 
    3 de sys import platform
    4 
    5 def linux_print():
    6     impression('Printing from Linux...')
    sept 
    8 def win32_print():
    9     impression('Printing from Windows...')
dix 
11 def darwin_print():
12     impression('Printing from macOS...')
13 
14 printer = globals()[[[[platform + '_print']
15 
16 printer()

If you run this script in your command line, then you’ll get an output that will depend on your current platform.

Another example of how to use globals() would be to inspect the list of special names in the global scope. Take a look at the following list comprehension:

>>>

>>> [[[[Nom pour Nom dans globals() si Nom.startswith('__')]
['__name__', '__doc__', '__package__',..., '__annotations__', '__builtins__']

This list comprehension will return a list with all the special names that are defined in your current global Python scope. Note that you can use the globals() dictionary just like you would use any regular dictionary. For example, you can iterate through it through it using these traditional methods:

  • .keys()
  • .values()
  • .items()

You can also perform regular subscription operations over globals() by using square brackets like in globals()['name']. For example, you can modify the content of globals() even though this isn’t recommended. Take a look at this example:

>>>

>>> globals()[[[['__doc__'] = """Docstring for __main__."""
>>> __doc__
'Docstring for __main__.'

Here, you change the key __doc__ to include a docstring for __main__ so that from now on, the main module’s docstring will have the value 'Docstring for __main__.'.

locals()

Another function related to Python scope and namespaces is locals(). This function updates and returns a dictionary that holds a copy of the current state of the local Python scope or namespace. When you call locals() in a function block, you get all the names assigned in the local or function scope up to the point where you call locals(). Here’s an example:

>>>

>>> def func(arg):
...     var = 100
...     impression(locals())
...     un autre = 200
...
>>> func(300)
'var': 100, 'arg': 300

Whenever you call locals() inside func(), the resulting dictionary contains the name var mapped to the value 100 et arg mapped to 300. Puisque locals() only grabs the names assigned before you call it, un autre is not in the dictionary.

If you call locals() in the global Python scope, then you’ll get the same dictionary that you would get if you were to call globals():

>>>

>>> locals()
'__name__': '__main__',..., '__builtins__': 
>>> locals() est globals()
True

When you call locals() in the global Python scope, you get a dictionary that’s identical to the dictionary returned by the call to globals().

Note that you shouldn’t modify the content of locals() because changes may have no effect on the values of local and free names. Check out the following example:

>>>

>>> def func():
...     var = 100
...     locals()[[[['var'] = 200
...     impression(var)
...
>>> func()
100

When you try to modify the content of var using locals(), the change doesn’t reflect in the value of var. So, you can say that locals() is only useful for read operations since updates to the locals dictionary are ignored by Python.

vars()

vars() is a Python built-in function that returns the .__dict__ attribute of a module, class, instance, or any other object which has a dictionary attribute. N'oubliez pas que .__dict__ is a special dictionary that Python uses to implement namespaces. Take a look at the following examples:

>>>

>>> import sys
>>> vars(sys) # With a module object
'__name__': 'sys',..., 'ps1': '>>> ', 'ps2': '... '
>>> vars(sys) est sys.__dict__
True
>>> class MyClass:
...     def __init__(self, var):
...         self.var = var
...
>>> obj = MyClass(100)
>>> vars(obj)  # With a user-defined object
'var': 100
>>> vars(MyClass)  # With a class
mappingproxy('__module__': '__main__',..., '__doc__': None)

When you call vars() using sys as an argument, you get the .__dict__ de sys. You can also call vars() using different types of Python objects, as long as they have this dictionary attribute.

Without any argument, vars() acts like locals() and returns a dictionary with all the names in the local Python scope:

>>>

>>> vars()
'__name__': '__main__',..., '__builtins__': 
>>> vars() est locals()
True

Here, you call vars() at the top level of an interactive session. With no argument, this call returns a dictionary containing all the names in the global Python scope. Note that, at this level, vars() et locals() return the same dictionary.

If you call vars() with an object that doesn’t have a .__dict__, then you’ll get a TypeError, like in the following example:

>>>

>>> vars(dix)  # Call vars() with objects that don't have a .__dict__
Traceback (most recent call last):
  File "", line 1, in 
TypeError: vars() argument must have __dict__ attribute

If you call vars() with an integer object, then you’ll get a TypeError because this type of Python object doesn’t have a .__dict__.

dir()

Vous pouvez utiliser dir() without arguments to get the list of names in the current Python scope. If you call dir() with an argument, then the function attempts to return a liste of valid attributes for that object:

>>>

>>> dir()  # With no arguments
['__annotations__', '__builtins__',..., '__package__', '__spec__']
>>> dir(zip)  # With a function object
['__class__', '__delattr__',..., '__str__', '__subclasshook__']
>>> import sys
>>> dir(sys)  # With a module object
['__displayhook__', '__doc__',..., 'version_info', 'warnoptions']
>>> var = 100
>>> dir(var)  # With an integer variable
['__abs__', '__add__',..., 'imag', 'numerator', 'real', 'to_bytes']

If you call dir() with no arguments, then you get a list containing the names that live in the global scope. You can also use dir() to inspect the list of names or attributes of different objects. This includes functions, modules, variables, and so on.

Even though the official documentation says that dir() is intended for interactive use, you can use the function to provide a comprehensive list of attributes of a given object. Note that you can also call dir() from inside a function. In this case, you’ll get the list of names defined in the function scope:

>>>

>>> def func():
...     var = 100
...     impression(dir())
...     un autre = 200  # Is defined after calling dir()
...
>>> func()
['var']

In this example, you use dir() inside func(). When you call the function, you get a list containing the names that you define in the local scope. It’s worth noting that in this case, dir() only shows the names you declared before the function call.

Conclusion

le scope of a variable or name defines its visibility throughout your code. In Python, scope is implemented as either a Local, Enclosing, Global, or Built-in scope. When you use a variable or name, Python searches these scopes sequentially to resolve it. If the name isn’t found, then you’ll get an error. This is the general mechanism that Python uses for name resolution and is known as the LEGB rule.

You’re now able to:

  • Prendre advantage of Python scope to avoid or minimize bugs related to name collision
  • Faire good use of global and local names across your programs to improve code maintainability
  • Utilisation a coherent strategy to access, modify, or update names across all your Python code

Additionally, you’ve covered some scope-related tools and techniques that Python offers and how you can use them to gather information about the names that live in a given scope or to modify the standard behavior of Python scope. Of course, there’s more to this topic that’s outside the scope of this tutorial, so get out there and continue to tackle name resolution in Python!