Heim  >  Artikel  >  Backend-Entwicklung  >  Trap-Analyse der dynamischen Zuweisung von Python

Trap-Analyse der dynamischen Zuweisung von Python

不言
不言nach vorne
2019-03-25 10:03:502630Durchsuche

Dieser Artikel bietet Ihnen eine Analyse der Fallstricke der dynamischen Zuweisung. Ich hoffe, dass er Ihnen als Referenz dienen wird.

Das Thema Namespace und Geltungsbereich mag trivial erscheinen, aber tatsächlich steckt viel dahinter.

Aus Platzgründen gibt es immer noch einen wichtigen Wissensinhalt, der nicht besprochen wurde, nämlich „die Lese- und Schreibprobleme von Locals() und Globals()“. Der Grund, warum dieses Problem wichtig ist, besteht darin, dass es einige flexible dynamische Zuweisungsfunktionen implementieren kann.

Es handelt sich bei allen um Wörterbuchtypen, und ihre Verwendung ist selbsterklärend. Allerdings gibt es bei der Verwendung eine Falle zu beachten: globals() ist lesbar und beschreibbar, während locals() nur lesbar, aber nicht beschreibbar ist. Der Artikel, den ich heute geteilt habe, dient der Untersuchung dieses Themas. Er ist sehr ausführlich und ich möchte ihn gerne mit Ihnen teilen.

Bei der Arbeit stoßen wir manchmal auf eine Situation: dynamische Variablenzuweisung, egal ob es sich um eine lokale Variable oder eine globale Variable handelt. Wenn wir uns den Kopf zerbrechen, hat Python dieses Problem für uns gelöst > Der Namespace von Python wird in Form eines Wörterbuchs widergespiegelt, und die spezifischen Funktionen sind locals () und globals (), die dem lokalen Namespace bzw. dem globalen Namespace entsprechen. Daher können wir durch diese Methoden unsere „Dynamik“ realisieren Zuweisung" benötigt.

Zum Beispiel:

def test():
    globals()['a2'] = 4
test()
print a2   # 输出 4

Da Globals den globalen Namespace ändern können, ist es natürlich, dass Einheimische natürlich auch in der Lage sein sollten, den lokalen Namespace zu ändern. Ändern Sie lokale Variablen darin die Funktion.

Aber ist das wirklich der Fall? Nein!

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

Ausgabe:

{}
{'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

Das Programm hat beim Ausführen einen Fehler gemeldet!

Aber in Wenn Sie locals() zum zweiten Mal drucken, können Sie deutlich erkennen, dass diese Variablen bereits im lokalen Bereich vorhanden sind und dass auch die Variable a mit dem Wert 1 vorhanden ist. Aber warum wird beim Drucken von a eine NameError-Ausnahme angezeigt?

Schauen Sie sich noch einmal ein Beispiel an:

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()

Ausgabe:

{}
{&#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

Der Unterschied besteht darin, dass der folgende Code die Zuweisung zeigt Ausgelöst wird der Wert der lokalen Variablen s.

Dies lässt uns fragen, ob sich die Änderung lokaler Variablen durch locals() von der direkten Zuweisung unterscheidet. Um dieses Problem zu lösen, können wir uns nur die Wahrheit ansehen Programmbetrieb und Der große Killer dis~

Erforschung der Grundursache

Analysieren Sie direkt den zweiten Teil des Codes:

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

Sie können es im obigen Bytecode sehen:

locals() entspricht, ist: LOAD_GLOBAL

s='test'. Der Bytecode, der 'test' entspricht, ist: LOAD_CONST und STORE_FAST

print s entsprechend ist: LOAD_FAST

Der Bytecode, der print a entspricht, ist: LOAD_GLOBAL

Wie aus den Bytecodes mehrerer oben aufgeführter Schlüsselanweisungen ersichtlich ist, sind direkte Zuweisung/Lesung und Zuweisung durch Einheimische( ) /Die Art des Lesens ist sehr unterschiedlich. Wenn also die Ausnahme „NameError“ ausgelöst wird, beweist dies, dass der über locals()[i] = 1 gespeicherte Wert und der tatsächliche lokale Namespace zwei verschiedene Orte sind?

Um diese Frage zu beantworten, müssen wir zunächst eines bestimmen: Wie erhält man den echten lokalen Namespace? Tatsächlich wurde die Standardantwort auf diese Frage im obigen Bytecode gegeben Der Namespace existiert tatsächlich in der entsprechenden Datenstruktur STORE_FAST. Dies erfordert Quellcode, um zu antworten:

// 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;
....
}

Wenn man das sieht, sollte klar sein, dass der lokale Namespace tatsächlich in der Funktion vorhanden ist Das Mitglied f_localsplus des Frame-Objekts ist möglicherweise klarer zu verstehen. Während CALL_FUNCTION wird dieses Array initialisiert und die formalen Parameterzuweisungen werden in das Wort eingefügt Abschnittscode 18 61 LOAD_FAST 0 (s), die 0 in der vierten Spalte dient dazu, das 0. Element von f_localsplus herauszunehmen, das den Wert „s“ darstellt.

STORE_FAST ist also die eigentliche Möglichkeit, Variablen lokal zu speichern Namespace, was zum Teufel ist locals()? Warum sieht es aus wie das Original?

Dafür ist eine Analyse des Bytecodes möglicherweise nicht hilfreich. Definieren Sie es:

// 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);
}

Wie Sie oben in PyFrame_FastToLocals sehen können, führt locals() tatsächlich Folgendes aus:

Bestimmen Sie, ob die f_f->f_locals des Frame-Objekts leer sind , Erstellen Sie dann ein neues Wörterbuchobjekt.

Schreiben Sie localsplus, co_cellvars und co_freevars in f_f->f_locals.

Hier finden Sie eine kurze Einführung in die oben genannten Elemente:

localsplus: Funktionsparameter (Positionsparameter + Schlüsselwortparameter), zugewiesene Variablen anzeigen.

co_cellvars und co_freevars: lokale Variablen, die von Abschlussfunktionen verwendet werden.

Schlussfolgerung

Über die obige Quelle Im Code wissen wir bereits klar, dass das, was locals() sieht, tatsächlich der Inhalt des lokalen Namespace der Funktion ist, aber es selbst kann den lokalen Namespace nicht darstellen. Es ist wie ein Proxy, der A sammelt. Die Dinge von B und C sind gezeigt, aber ich kann nicht einfach die Dinge ändern, die A, B und C wirklich besitzen, indem ich diesen Proxy ändere!

Aus diesem Grund übergeben wir locals( )[i] = 1, um dynamisch Werte zuzuweisen , print a löst eine NameError-Ausnahme aus, im Gegenteil, globals() ist tatsächlich der eigentliche globale Namespace, daher wird im Allgemeinen gesagt: locals() ist schreibgeschützt, globals() kann

lesen und schreiben

Dieser Artikel ist hier zu Ende. Weitere spannende Inhalte finden Sie in der Spalte Python-Video-Tutorial auf der chinesischen PHP-Website!

Das obige ist der detaillierte Inhalt vonTrap-Analyse der dynamischen Zuweisung von Python. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:微信公众号:python猫. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen