Maison  >  Article  >  développement back-end  >  Analyse des pièges de l'affectation dynamique Python

Analyse des pièges de l'affectation dynamique Python

不言
不言avant
2019-03-25 10:03:502625parcourir

Cet article vous apporte une analyse des pièges de l'affectation dynamique en Python. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

La question de l'espace de noms et de la portée peut sembler triviale, mais en fait, il y a beaucoup de choses derrière cela.

En raison du manque de place, il reste encore un contenu de connaissances important qui n'a pas été abordé, à savoir "les problèmes de lecture et d'écriture des locaux() et des globals()". La raison pour laquelle ce problème est important est qu'il peut implémenter certaines fonctions d'affectation dynamique flexibles.

Ce sont tous des types de dictionnaires et leur utilisation est explicite. Cependant, il y a un piège à prendre en compte lors de son utilisation : globals() est lisible et inscriptible, tandis que locals() est uniquement lisible mais pas inscriptible. L'article que j'ai partagé aujourd'hui vise à explorer cette question de manière très approfondie et j'aimerais le partager avec vous.

Au travail, nous rencontrons parfois une situation : l'affectation dynamique de variables, qu'il s'agisse d'une variable locale ou d'une variable globale. Quand nous nous creusons la tête, Python a résolu ce problème pour nous. >L'espace de noms de Python se reflète sous la forme d'un dictionnaire, et les fonctions spécifiques sont locals() et globals(), qui correspondent respectivement à l'espace de noms local et à l'espace de noms global. Par conséquent, nous pouvons grâce à ces méthodes, réaliser notre « dynamique ».

Par exemple :

C'est naturel. Puisque les globaux peuvent changer l'espace de noms global, bien sûr, les locaux devraient également pouvoir modifier les noms locaux. Espace. Modifier les variables locales au sein de la fonction.
def test():
    globals()['a2'] = 4
test()
print a2   # 输出 4

Mais est-ce vraiment le cas ? Non !

Sortie :
def aaaa():
    print locals()
    for i in ['a', 'b', 'c']:
        locals()[i] = 1
    print locals()
    print a
aaaa()

Le programme a signalé une erreur lors de son exécution !
{}
{'i': 'c', 'a': 1, 'c': 1, 'b': 1}
Traceback (most recent call last):
  File "5.py", line 17, in <module>
    aaaa()
  File "5.py", line 16, in aaaa
    print a
NameError: global name &#39;a&#39; is not defined

Mais dans le deuxième print locals(), vous pouvez clairement voir que l'espace local a déjà ces variables, y compris la variable a et sa valeur est 1, mais pourquoi une exception NameError apparaît-elle lors de l'impression de a ?

Regardez un autre exemple :

Sortie :
def aaaa():
    print locals()
    s = &#39;test&#39;                    # 加入显示赋值 s       
    for i in [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;]:
        locals()[i] = 1
    print locals()
    print s                       # 打印局部变量 s 
    print a
aaaa()

Deux morceaux de code, la différence est que le code suivant montre l'affectation, bien qu'elle soit également déclenchée. Une exception NameError s'est produit, mais la valeur de la variable locale s a été imprimée.
{}
{&#39;i&#39;: &#39;c&#39;, &#39;a&#39;: 1, &#39;s&#39;: &#39;test&#39;, &#39;b&#39;: 1, &#39;c&#39;: 1}
test
Traceback (most recent call last):
  File "5.py", line 19, in <module>
    aaaa()
  File "5.py", line 18, in aaaa
    print a
NameError: global name &#39;a&#39; is not defined

Cela nous rend très confus. La modification de la variable locale via locals() est-elle différente de l'affectation directe. Vous voulez résoudre ce problème, juste maintenant ? peut voir la vérité sur le fonctionnement du programme, je dois utiliser dis~

Explorer la cause profonde

Analyser directement le deuxième morceau de code :

ci-dessus Vous pouvez voir le bytecode de :
13           0 LOAD_GLOBAL              0 (locals)
              3 CALL_FUNCTION            0
              6 PRINT_ITEM
              7 PRINT_NEWLINE
 14           8 LOAD_CONST               1 (&#39;test&#39;)
             11 STORE_FAST               0 (s)
 15          14 SETUP_LOOP              36 (to 53)
             17 LOAD_CONST               2 (&#39;a&#39;)
             20 LOAD_CONST               3 (&#39;b&#39;)
             23 LOAD_CONST               4 (&#39;c&#39;)
             26 BUILD_LIST               3
             29 GET_ITER
        >>   30 FOR_ITER                19 (to 52)
             33 STORE_FAST               1 (i)
 16          36 LOAD_CONST               5 (1)
             39 LOAD_GLOBAL              0 (locals)
             42 CALL_FUNCTION            0
             45 LOAD_FAST                1 (i)
             48 STORE_SUBSCR
             49 JUMP_ABSOLUTE           30
        >>   52 POP_BLOCK
 17     >>   53 LOAD_GLOBAL              0 (locals)
             56 CALL_FUNCTION            0
             59 PRINT_ITEM
             60 PRINT_NEWLINE
 18          61 LOAD_FAST                0 (s)
             64 PRINT_ITEM
             65 PRINT_NEWLINE
 19          66 LOAD_GLOBAL              1 (a)
             69 PRINT_ITEM
             70 PRINT_NEWLINE
             71 LOAD_CONST               0 (None)
             74 RETURN_VALUE
None

Le bytecode correspondant de locals() est : LOAD_GLOBAL

s='test'Le bytecode correspondant est : LOAD_CONST et STORE_FAST

Le le bytecode correspondant à print s est : LOAD_FAST

Le bytecode correspondant à print a est : LOAD_GLOBAL

On peut voir à partir des bytecodes de plusieurs instructions clés répertoriées ci-dessus, la nature de l'affectation/lecture directe et l'affectation/lecture via locals() est très différente. Ensuite, le déclenchement de l'exception NameError prouve-t-il que la valeur stockée via locals()[i] = 1 est différente du véritable espace de noms local ?

Pour répondre à cette question, nous devons d'abord déterminer une chose, c'est-à-dire comment obtenir le véritable espace de noms local. En fait, cette question a déjà reçu un standard dans le bytecode ci-dessus. La réponse

Le vrai ! L'espace de noms local existe réellement dans la structure de données correspondante STORE_FAST. Qu'est-ce que c'est ? Cela nécessite que le code source réponde :

Voir ici, il devrait être clair que l'espace de noms local à l'intérieur de la fonction est en fait le. membre f_localsplus de l'objet frame. Il s'agit d'un tableau. Il peut être plus clair de comprendre les chaussures pour enfants créées par la fonction, ce tableau sera initialisé dans l'ordre. 18 61 LOAD_FAST 0 (s), le 0 dans la quatrième colonne sert à retirer le 0ème membre de f_localsplus, qui est la valeur "s".
// ceval.c  从上往下, 依次是相应函数或者变量的定义
// 指令源码
TARGET(STORE_FAST)
{
    v = POP();
    SETLOCAL(oparg, v);
    FAST_DISPATCH();
}
--------------------
// SETLOCAL 宏定义      
#define SETLOCAL(i, value)      do { PyObject *tmp = GETLOCAL(i); \
                                     GETLOCAL(i) = value; \
                                     Py_XDECREF(tmp); } while (0)
-------------------- 
// GETLOCAL 宏定义                                    
#define GETLOCAL(i)     (fastlocals[i])     
-------------------- 
// fastlocals 真面目
PyObject * PyEval_EvalFrameEx(PyFrameObject *f, int throwflag){
    // 省略其他无关代码
   fastlocals = f->f_localsplus;
....
}

Store_FAST est donc le véritable moyen de stocker des variables dans le local espace de noms, alors qu'est-ce que c'est que locals() ? Pourquoi ça ressemble à la vraie chose ?

Cela doit analyser les locaux Pour cela, le bytecode peut ne pas fonctionner directement pour voir comment le construit-. dans les fonctions sont définies :

Comme le montre PyFrame_FastToLocals ci-dessus, locals() fait en fait les choses suivantes :
// bltinmodule.c
static PyMethodDef builtin_methods[] = {
    ...
    // 找到 locals 函数对应的内置函数是 builtin_locals 
    {"locals",          (PyCFunction)builtin_locals,     METH_NOARGS, locals_doc},
    ...
}
-----------------------------
// builtin_locals 的定义
static PyObject *
builtin_locals(PyObject *self)
{
    PyObject *d;
    d = PyEval_GetLocals();
    Py_XINCREF(d);
    return d;
}
-----------------------------
PyObject *
PyEval_GetLocals(void)
{
    PyFrameObject *current_frame = PyEval_GetFrame();  // 获取当前堆栈对象
    if (current_frame == NULL)
        return NULL;
    PyFrame_FastToLocals(current_frame); // 初始化和填充 f_locals
    return current_frame->f_locals;
}
-----------------------------
// 初始化和填充 f_locals 的具体实现
void
PyFrame_FastToLocals(PyFrameObject *f)
{
    /* Merge fast locals into f->f_locals */
    PyObject *locals, *map;
    PyObject **fast;
    PyObject *error_type, *error_value, *error_traceback;
    PyCodeObject *co;
    Py_ssize_t j;
    int ncells, nfreevars;
    if (f == NULL)
        return;
    locals = f->f_locals;
    // 如果locals为空, 就新建一个字典对象
    if (locals == NULL) {
        locals = f->f_locals = PyDict_New();  
        if (locals == NULL) {
            PyErr_Clear(); /* Can&#39;t report it :-( */
            return;
        }
    }
    co = f->f_code;
    map = co->co_varnames;
    if (!PyTuple_Check(map))
        return;
    PyErr_Fetch(&error_type, &error_value, &error_traceback);
    fast = f->f_localsplus;
    j = PyTuple_GET_SIZE(map);
    if (j > co->co_nlocals)
        j = co->co_nlocals;
    // 将 f_localsplus 写入 locals
    if (co->co_nlocals)
        map_to_dict(map, j, locals, fast, 0);
    ncells = PyTuple_GET_SIZE(co->co_cellvars);
    nfreevars = PyTuple_GET_SIZE(co->co_freevars);
    if (ncells || nfreevars) {
        // 将 co_cellvars 写入 locals
        map_to_dict(co->co_cellvars, ncells,
                    locals, fast + co->co_nlocals, 1);
        if (co->co_flags & CO_OPTIMIZED) {
            // 将 co_freevars 写入 locals
            map_to_dict(co->co_freevars, nfreevars,
                        locals, fast + co->co_nlocals + ncells, 1);
        }
    }
    PyErr_Restore(error_type, error_value, error_traceback);
}

Détermine si le f_f->f_locals de l'objet frame est vide. Si tel est le cas, créez un nouvel objet dictionnaire.

Écrivez respectivement localsplus, co_cellvars et co_freevars dans f_f->f_locals.

Dans Ceci est une brève introduction à ce que sont les éléments ci-dessus :

localsplus : paramètres de fonction (paramètres de position + paramètres de mots-clés), affichage des variables assignées

co_cellvars et co_freevars : la fonction de fermeture sera utilisée pour les variables locales.

Conclusion

Grâce au code source ci-dessus, nous savons déjà clairement que ce que locals() voit est bien le contenu de l'espace de noms local de la fonction, mais il ne peut pas lui-même représenter un espace de noms local, qui est comme un proxy Il collecte les éléments de A. , B et C et me les montre, mais je ne peux pas simplement changer ce que A, B et C possèdent réellement en changeant ce proxy

C'est pourquoi, lorsque nous attribuons dynamiquement des valeurs via les paramètres locaux (! )[i] = 1, print a déclenche une exception NameError Au contraire, globals() est bien le véritable espace de noms global, on dit donc généralement : locals() est en lecture seule, globals() est lisible et inscriptible.

Cet article est terminé ici. Pour un contenu plus passionnant, vous pouvez faire attention à la colonne tutoriel vidéo python du site Web PHP chinois !

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer