Maison > Article > développement back-end > Analyse des pièges de l'affectation dynamique Python
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 'a' 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 = 'test' # 加入显示赋值 s for i in ['a', 'b', 'c']: 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.
{} {'i': 'c', 'a': 1, 's': 'test', 'b': 1, 'c': 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 'a' 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 ('test') 11 STORE_FAST 0 (s) 15 14 SETUP_LOOP 36 (to 53) 17 LOAD_CONST 2 ('a') 20 LOAD_CONST 3 ('b') 23 LOAD_CONST 4 ('c') 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'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!