UnboundLocalError en Python : la variable locale utilisée trop tôt

UnboundLocalError signale une variable locale lue avant d'avoir reçu sa valeur. D'où vient l'erreur, pourquoi Python la lève, comment la corriger.
5 min de lecture
Believemy logo

Définition

Une fonction peut sembler lire une variable qui existe déjà, définie plus haut dans le même fichier, et Python refuse pourtant de l'exécuter. C'est ce que signale UnboundLocalError : la fonction lit une variable locale avant de lui avoir donné une valeur. Le nom existe bien aux yeux de Python, qui l'a repéré en analysant le corps de la fonction, mais il ne désigne encore rien : la case est réservée, et elle est vide.

Le cas le plus simple ressemble à ceci, une fonction qui tente d'incrémenter un compteur défini juste au-dessus d'elle.

PYTHON
compteur = 0

def incrementer():
    # l'affectation ci-dessous suffit à rendre "compteur" local
    compteur = compteur + 1
    return compteur

incrementer()

# UnboundLocalError: cannot access local variable 'compteur'
# where it is not associated with a value

Le message déroute, puisque compteur est bien défini deux lignes plus haut. Cette définition ne joue pourtant aucun rôle ici : l'affectation écrite dans le corps de la fonction a suffi à faire de compteur un nom local, et c'est ce nom local, encore vide, que la lecture réclame.

Bon à savoir

Le texte du message a changé avec Python 3.11. Les versions plus anciennes affichent « local variable 'compteur' referenced before assignment », une formulation plus sèche qui désigne la même erreur.


Python décide de la portée avant d'exécuter

Ce mécanisme explique tout le reste. À sa définition, la fonction est analysée d'un seul bloc : tout nom qui reçoit une affectation quelque part dans son corps devient local pour la totalité de ce corps, y compris sur les lignes situées avant l'affectation. La décision est prise une fois pour toutes, avant le moindre appel.

Une lecture seule, elle, ne déclenche rien. Sans affectation dans la fonction, Python cherche le nom à l'extérieur et le trouve sans protester. C'est donc bien l'affectation qui bascule la portée, où qu'elle se trouve, y compris sur une ligne jamais atteinte à l'exécution.

Le tableau qui suit résume ces trois situations, et ce qui arrive quand le nom est lu avant l'affectation qui le concerne.

Dans le corps de la fonctionPortée du nomLecture avant affectation
Lecture seuleGlobaleFonctionne
Une affectation quelque partLocaleUnboundLocalError
Affectation et globalGlobaleFonctionne


Les formes déguisées

Le cas d'école se repère vite dans du code. Les heures perdues viennent des affectations qui ne ressemblent pas à des affectations.

La première est l'affectation augmentée. compteur += 1 raccourcit compteur = compteur + 1 : il lit la valeur puis la réaffecte au même nom, et c'est cette lecture, juste avant, qui échoue.

La deuxième vient de la variable d'une boucle, qui reçoit une valeur neuve à chaque tour : c'est une affectation comme une autre. Relue après la boucle, elle tombe sous la même règle, locale et vide si la boucle n'a jamais tourné.

La troisième est la plus sournoise, parce qu'elle ne produit une erreur que sur certaines données. Voici le même piège, avec une affectation posée dans une seule branche d'un test.

PYTHON
def message(score):
    if score >= 10:
        # texte n'existe que si cette branche s'exécute
        texte = "Réussi"
    return texte

message(5)

# UnboundLocalError: cannot access local variable 'texte'

Au-dessus de dix, tout va bien. En dessous, la branche n'est jamais exécutée, texte ne reçoit jamais de valeur, et le return échoue. Une condition sans else laisse ce trou ouvert, et le trou ne s'ouvre que sur certaines données : les tests passent, la production tombe.


Les trois corrections

La première consiste à poser une valeur par défaut avant le test. Une ligne placée en tête de fonction referme le trou sans rien changer à la logique, et elle documente au passage ce que vaut la variable quand aucune branche ne s'applique.

La deuxième consiste à déclarer l'intention. global autorise la fonction à réaffecter un nom de module, nonlocal fait la même chose pour un nom appartenant à une fonction englobante. Les deux lèvent l'erreur, mais ils ouvrent aussi la porte aux modifications à distance, difficiles à suivre dès que le programme grossit.

La troisième, souvent préférable, consiste à ne rien partager du tout : la fonction reçoit ce dont elle a besoin en argument et renvoie son résultat. Cette erreur est parfois le premier signal qu'un état global circule là où un simple passage de valeur suffirait.


Questions fréquentes

Question

Quelle est la différence avec NameError ?

UnboundLocalError hérite de NameError : c'est un cas particulier. La distinction porte sur ce que Python sait du nom. Ici il le connaît et l'a classé local, mais aucune valeur ne lui a encore été attribuée, alors qu'une NameError signale un nom introuvable partout, souvent une faute de frappe.

Question

Pourquoi une variable globale se lit-elle sans rien déclarer ?

Parce que lire ne crée aucun nom local. Python parcourt le corps de la fonction, n'y trouve aucune affectation, et va donc chercher le nom au niveau du module. Le mot-clé global ne devient nécessaire qu'au moment de réaffecter ce nom, jamais pour le consulter.

Question

Comment la localiser rapidement ?

Le traceback donne la ligne fautive et le nom concerné. Il suffit ensuite de chercher ce nom plus bas dans la même fonction : l'affectation qui s'y trouve est la cause, même lorsqu'elle semble sans rapport avec la ligne qui a échoué.

Termes connexes

Découvrez notre glossaire Python

Parcourez les termes et définitions les plus couramment utilisés dans le domaine du développement avec Python.

Partager cet article

Tu veux nous aider ? Fais un lien vers cet article sur tes réseaux ou encore mieux : sur ton site, dans un article ou dans ta newsletter.