• [^] # Re: Petite bizarrerie à noter

    Posté par (site web personnel) . En réponse au journal écrire du code dans le corps d'une classe python. Évalué à 10.

    Tu as bien 2 espaces de nom "global" (accessible via la fonction builtin globals() et "local" (accessible via la fonction builtin locals().

    On vois ici que x ne fait partie ni de "global" ni de "local" :

    >>> def foo():
    ... x = 0
    ... def g():
    ... print(globals())
    ... print(locals())
    ... return g
    ...
    >>> foo()()
    {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'foo': <function foo at 0x000001F518DA7F60>}
    {}
    >>>
    

    Mais alors, d'où il vient ?

    Réponse courte : Du locals() de la fonction f().

    Réponse longue : Pour bien comprendre ce qu'il se passe, voici le code désassemblé :

    >>> def f():
    ... x = 0
    ... def g():
    ... return x
    ... return g
    ...
    >>> import dis
    >>> dis.dis(f)
     0 MAKE_CELL 1 (x)
     1 2 RESUME 0
     2 4 LOAD_CONST 1 (0)
     6 STORE_DEREF 1 (x)
     3 8 LOAD_CLOSURE 1 (x)
     10 BUILD_TUPLE 1
     12 LOAD_CONST 2 (<code object g at 0x00000243D97258F0, file "<stdin>", line 3>)
     14 MAKE_FUNCTION 8 (closure)
     16 STORE_FAST 0 (g)
     5 18 LOAD_FAST 0 (g)
     20 RETURN_VALUE
    Disassembly of <code object g at 0x00000243D97258F0, file "<stdin>", line 3>:
     0 COPY_FREE_VARS 1
     3 2 RESUME 0
     4 4 LOAD_DEREF 0 (x)
     6 RETURN_VALUE
    

    Premièrement, dans la fonction f on créé la variable x avec l'opcode MAKE_CELL. Et on l'initialise à 0 avec LOAD_CONST et STORE_DEREF.

    Vient ensuite le MAKE_FUNCTION qui va créer la fonction g() en utilisant un "code object" (le corps de la fonction g compilé), ainsi qu'un tuple qui va contenir l'espace mémoire pour des variables libres.

    Ce tuple est créé avec l'opcode BUILD_TUPLE 1 (tuple de 1 élément), le tuple va être créé en prenant un élément sur la pile. Cet élément c'est une référence vers x qui est chargée avec LOAD_CLOSURE (python 3.11, depuis python 3.13 c'est LOAD_FAST).

    Enfin, dans le code de la fonction g(), on a COPY_FREE_VARS qui va introduire dans la "stack frame" actuelle les variables référencées par le tuple avec lequel on a construit la "closure".

    Démontrant bien qu'il n'existe que 2 espaces de noms : global et local. La fonction g porte avec elle une copie des variables qu'elle capture. Après tout, g n'est pas une fonction mais une "closure" (fonction + environnement).

    En pseudo-code python, voici à peu près ce qu'il se passe :

    def f():
     x = 0
     g = make_g(x)
     return g
    class make_g:
     # rien ne nous oblige a appeler `self` "self" ;)
     def __init__(free_vars, x):
     free_vars.x = x
     def __call__(free_vars):
     return free_vars.x

    https://link-society.com - https://kubirds.com - https://github.com/link-society/flowg