• [^] # Re: Et pourtant c'est simple

    Posté par (site web personnel) . En réponse au journal Quizz Python : esp[èa]ce de nom. Évalué à 7. Dernière modification le 09 mars 2023 à 15:03.

    le bloc crée une variable x donc on retire x de la table

    Ha mais non, on supprime rien.

    C'est plutôt que la variable x dans le namespace de la class bar "masque" la variable x dans le namespace de foo.

    Le fonctionnement global est celui ci:

    Lors de la définition d'une fonction (ou d'une class ici), un "scope" (local) est créé.

    Lors de la résolution de nom (la recherche de l’objet qui correspond à x), on cherche d'abord dans ce scope local. Si on le trouve pas et si la définition de la fonction à lieu dans un scope (par exemple à l'intérieur de l’exécution de foo()), on cherche dans ce scope "parent". Si on le trouve toujours pas, on remonte à nouveau (si on peut). Enfin, on arrive au scope global (celui du module) et si on trouve toujours pas, on arrête avec une exception NameError.

    La subtilité, c'est qu'on attend pas de faire un x = 1 pour créer la variable x dans son scope. À la définition de la fonction, python "sait" qu'on va avoir besoin d'une variable x et donc il crée le slot (vide) dans le scope dès le début de l’exécution.

    Donc lors de la résolution de nom, il trouve x dans le scope local et donc arrête de chercher dans le scopes parents. Mais comme x n'est pas encore initialisé, bam !

    Donc c'est bien l'existence de la variable x dans le scope local qui masque x dans les scopes parent même si l'initialisation de x a lieu après la ligne qui plante.

    Ça donne des trucs rigolo :

    • Ça marche de manière récursive :
    a = 5 
    def foo():
     b = 10
     def bar():
     c = 15
     def baz():
     d = 20
     print(a + b + c + d)
     return baz
     return bar()
    print(foo()()) # Affiche 50
    • La résolution de nom à lieu à l’exécution du code, ce qui peut amener des surprises :
    def foo():
     for i in range(3):
     def bar():
     print(i)
     yield bar
    # Affiche 0, 1 et 2
    for f in foo():
     f()
    # Affiche 2, 2, 2
    functions = list(foo())
    for f in functions:
     # Lors de l'appel à `f`, la boucle dans foo a fini et donc i vaut tout le temps 2
     f()
    • Si on veut forcer la résolution des nom à la définition, il faut lire la valeur à la définition:
    def foo():
     for i in range(3):
     def bar(i=i):
     # i est dans le scope local (c'est un argument) et ça valeur par défaut
     # est la valeur de i (dans foo) *au moment de la définition de bar*
     print(i)
     yield bar
    # Affiche 0, 1 et 2
    functions = list(foo())
    for f in functions:
     f()
    • Le mot clé nonlocal permet de dire à python de ne pas créer de slot dans le scope local mais d'utiliser celui du scope parent (et seulement celui là). global fait la même chose mais dit d'utiliser le scope global :
    def foo():
     x = 5
     def bar():
     nonlocal x
     x = 6
     print(x)
     yield bar
     def baz():
     print(x)
     yield baz
    # Affiche 6 et 6
    for f in foo():
     f()

    Matthieu Gautier|irc:starmad