Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Licence CC BY-NC-ND Thierry Parmentelat & Arnaud Legout Inria - UCA

Complément - niveau basique

Nous venons de voir les règles pour l’affectation (ou l’assignation) et le référencement des variables et des attributs ; en particulier, on doit faire une distinction entre les attributs et les variables.

Vous voyez donc que la différence entre attributs et variables est fondamentale. Dans ce complément, nous allons reprendre et résumer les différentes règles qui régissent l’affectation et le référencement des attributs et des variables.

Attributs

Un attribut est un symbole x utilisé dans la notation obj.xobj est l’objet qui définit l’espace de nommage sur lequel x existe.

L’affectation (explicite ou implicite) d’un attribut x sur un objet obj va créer (ou altérer) un symbole x directement dans l’espace de nommage de obj, symbole qui va référencer l’objet affecté, typiquement l’objet à droite du signe =

class MaClasse:
    pass

# affectation explicite
MaClasse.x = 10 

# le symbole x est défini dans l'espace de nommage de MaClasse
'x' in MaClasse.__dict__
True

Le référencement (la lecture) d’un attribut va chercher cet attribut le long de l’arbre d’héritage en commençant par l’instance, puis la classe qui a créé l’instance, puis les super-classes et suivant la MRO (voir le complément sur l’héritage multiple).

Variables

Une variable est un symbole qui n’est pas précédé de la notation obj. et l’affectation d’une variable rend cette variable locale au bloc de code dans lequel elle est définie, un bloc de code pouvant être :

Une variable référencée est toujours cherchée suivant la règle LEGB :

Si la variable n’est toujours pas trouvée, elle est cherchée dans le module builtins et si elle n’est toujours pas trouvée, une exception est levée.

Par exemple :

var = 'dans le module'

class A:
    var = 'dans la classe A'
    def f(self):
        var = 'dans la fonction f'
        class B:
            print(var)
        B()
A().f()
dans la fonction f

En résumé

Dans la vidéo et dans ce complément basique, on a couvert tous les cas standards, et même si python est un langage plutôt mieux fait, avec moins de cas particuliers que d’autres langages, il a également ses cas étranges entre raisons historiques et bugs qui ne seront jamais corrigés (parce que ça casserait plus de choses que ça n’en réparerait). Pour éviter de tomber dans ces cas spéciaux, c’est simple, vous n’avez qu’à suivre ces règles :

Si vous ne suivez pas ces règles, vous risquez de tomber dans un cas particulier que nous détaillons ci-dessous dans la partie avancée.

Complément - niveau avancé

La documentation officielle est fausse

Oui, vous avez bien lu, la documentation officielle est fausse sur un point subtil. Regardons le modèle d’exécution, on trouve la phrase suivante “If a name binding operation occurs anywhere within a code block, all uses of the name within the block are treated as references to the current block.” qui est fausse, il faut lire “If a name binding operation occurs anywhere within a code block of a function, all uses of the name within the block are treated as references to the current block.”

En effet, les classes se comportent différemment des fonctions :

x = "x du module"
class A():
    print("dans classe A: " + x)
    x = "x dans A"
    print("dans classe A: " + x)
    del x
    print("dans classe A: " + x)
dans classe A: x du module
dans classe A: x dans A
dans classe A: x du module

Alors pourquoi si c’est une mauvaise idée de mélanger variables globales et locales de même nom dans une fonction, c’est possible dans une classe ?

Cela vient de la manière dont sont implémentés les espaces de nommage. Normalement, un objet a pour espace de nommage un dictionnaire qui s’appelle __dict__. D’un côté un dictionnaire est un objet python qui offre beaucoup de flexibilité, mais d’un autre côté, il induit un petit surcoût pour chaque recherche d’éléments. Comme les fonctions sont des objets qui par définition peuvent être appelés très souvent, il a été décidé de mettre toutes les variables locales à la fonction dans un objet écrit en C qui n’est pas dynamique (on ne peut pas ajouter des éléments à l’exécution), mais qui est un peu plus rapide qu’un dictionnaire lors de l’accès aux variables. Mais pour faire cela, il faut déterminer la portée de la variable dans la phase de précompilation. Donc si le précompilateur trouve une affectation (explicite ou implicite) dans une fonction, il considère la variable comme locale pour tout le bloc de code. Donc si on référence une variable définie comme étant locale avant une affectation dans la fonction, on ne va pas la chercher globalement, on a une erreur UnboundLocalError.

Cette optimisation n’a pas été faite pour les classes, parce que dans l’évaluation du compromis souplesse contre efficacité pour les classes, c’est la souplesse, donc le dictionnaire qui a gagné.