trouver un expert Python
- Création d'un widget WordPress pour afficher une publication aléatoire • WPShout
- Comment créer un lien vers une section de page dans les menus de navigation de WordPress
- Comment demander une image en vedette pour votre publication à publier • WPShout
- Episode # 104 Théorie des jeux en Python
- Selz WordPress eCommerce Plugin Review
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
etnon 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.
Bonus gratuit: 5 réflexions sur la maîtrise de Python, un cours gratuit pour les développeurs Python qui vous montre la feuille de route et l'état d'esprit dont vous aurez besoin pour faire passer vos compétences Python au niveau supérieur.
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:
-
Portée mondiale: Les noms que vous définissez dans cette étendue sont disponibles pour tout votre code.
-
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.
Remarque: Vous utiliserez le terme Nom pour faire référence aux identificateurs de variables, constantes, fonctions, classes ou tout autre objet auxquels un nom peut être attribué.
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.
Remarque: Il y a une différence importante entre opérations d'affectation et opérations de référence ou d'accès. Lorsque vous faites référence à un nom, vous récupérez simplement son contenu ou sa valeur. Lorsque vous attribuez un nom, vous créez ou modifiez ce nom.
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.
Remarque: La sortie de certains des exemples de ce didacticiel a été abrégée (...
) pour économiser de l'espace. La sortie peut varier en fonction de votre plate-forme, de la version de Python ou même de la durée d'utilisation de votre session interactive Python actuelle.
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:
- Utilisation de la notation par points sur le nom du module dans le formulaire
module.name
- Utilisation d'une opération d'abonnement sur
.__ dict__
sous la formemodule .__ 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, '>>>'
.
Remarque: ps1
est une chaîne spécifiant l'invite principale de l'interpréteur Python. ps1
n'est défini que si l'interpréteur est en mode interactif et que sa valeur initiale est '>>>'
.
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.
Remarque: Notez que les portées Python locales et englobantes ne sont recherchées que si vous utilisez un nom à l'intérieur d'une fonction (portée locale) ou une fonction imbriquée ou intérieure (portée locale et englobante).
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
.
Remarque: Si vous essayez d'accéder résultat
ou base
après l'appel de fonction, vous obtenez un NameError
, car ceux-ci n'existent que dans la portée locale créée par l'appel à carré()
. Chaque fois que vous essayez d'accéder à un nom qui n'est défini dans aucune étendue Python, vous obtenez un NameError
. Le message d'erreur comprendra le nom introuvable.
É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.
Remarque: Dans un sens, inner_func ()
est une fonction temporaire qui ne prend vie que lors de l'exécution de sa fonction englobante, external_func ()
. Notez que inner_func ()
n'est visible que par le code external_func ()
.
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.
Remarque: En Python, les notions de portée globale et de noms globaux sont étroitement associées aux fichiers de module. Par exemple, si vous définissez un nom au niveau supérieur de n'importe quel module Python, ce nom est alors considéré comme global pour le module. C’est la raison pour laquelle ce type de portée est aussi appelé portée du module.
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 ()
.
Remarque: Vous couvrirez dir ()
plus en détail plus loin dans ce tutoriel.
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:
- Vous créer un nouveau nom
- 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.
Remarque: Les noms globaux peuvent être mis à jour ou modifiés à partir de n'importe quel endroit de votre portée Python globale. Au-delà de cela, le global
peut être utilisée pour modifier les noms globaux à partir de presque n'importe quel endroit de votre code, comme vous le verrez dans global
Déclaration.
La modification des noms globaux est généralement considérée comme une mauvaise pratique de programmation car elle peut conduire à du code qui est:
- Difficile à déboguer: Presque toutes les instructions du programme peuvent modifier la valeur d'un nom global.
- Difficile à comprendre: Vous devez connaître toutes les instructions qui accèdent et modifient les noms globaux.
- Impossible de réutiliser: Le code dépend de noms globaux spécifiques à un programme concret.
Les bonnes pratiques de programmation recommandent d'utiliser des noms locaux plutôt que des noms globaux. Voici quelques conseils:
- Écrire des fonctions autonomes qui reposent sur des noms locaux plutôt que globaux.
- Essayer pour utiliser des noms d'objets uniques, quelle que soit la portée dans laquelle vous vous trouvez.
- Éviter modifications de nom globales dans tous vos programmes.
- Éviter modifications de nom entre modules.
- Utilisation les noms globaux en tant que constantes qui ne changent pas pendant l'exécution de votre programme.
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:
- À l'intérieur
inner_func ()
: Ceci est la portée locale, maisnombre
n’existe pas là-bas. - À l'intérieur
external_func ()
: Ceci est la portée englobante, maisnombre
n'y est pas défini non plus. - Dans la portée du module: Ceci est la portée mondiale, et vous trouvez
nombre
là, vous pouvez donc imprimernombre
à 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.
Remarque: Remplacer ou redéfinir accidentellement ou par inadvertance des noms intégrés dans votre portée globale peut être une source de bogues dangereux et difficiles à trouver. Il vaut mieux essayer d'éviter ce genre de pratique.
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:
global
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.
Remarque: The use of global
is considered bad practice in general. If you find yourself using global
to fix problems like the one above, then stop and think if there is a better way to write your code.
For example, you can try to write a self-contained function that relies on local names rather than on global names as follows:
>>> global_counter = 0 # A global name
>>> def update_counter(counter):
... revenir counter + 1 # Rely on a local name
...
>>> global_counter = update_counter(global_counter)
>>> global_counter
1
>>> global_counter = update_counter(global_counter)
>>> global_counter
2
>>> global_counter = update_counter(global_counter)
>>> global_counter
3
This implementation of update_counter()
defines counter
as a parameter and returns its value augmented by 1
unit every time the function is called. This way, the result of update_counter()
depends on the counter
you use as an input and not on the changes that other functions (or pieces of code) can perform on the global variable, global_counter
.
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.
Remarque: Even though you can use a global
statement to create lazy global names, this can be a dangerous practice that can lead to buggy code. So, it’s best to avoid things like this in your code.
For example, suppose you’re trying to get access to one of those lazy names and, for some reason, your code hasn’t called the function that creates that name yet. In this case, you’ll get a NameError
and your program will crash.
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.
Remarque: You can also call this kind of function a factory, a factory function, or—to be more precise—a closure factory to specify that the function builds and returns closures (an inner function), rather than classes or instances.
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
.
Remarque: Variables like exp
are called free variables. They are variables that are used in a code block but not defined there. Free variables are the mechanism that closures use to retain state information between calls.
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
. 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.
Remarque: Think of the dot notation as if you were telling Python, “Look for the attribute called attr
dans obj
. If you find it, then give it back to me.”
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:
- Vérifier la instance local scope or namespace first.
- If the attribute is not found there, then check the class local scope or namespace.
- 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:
- Instance: Utilisation
self.var
to access this attribute. - Classe: Utilisation
A.var
to access this attribute.
Since both cases use the dot notation, there are no name collision problems.
Remarque: In general, good OOP practices recommend not to shadow class attributes with instance attributes that have different responsibilities or perform different actions. Doing so can lead to subtle and hard-to-find bugs.
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!
[ad_2]