>  기사  >  백엔드 개발  >  Python 동적 할당의 트랩 분석

Python 동적 할당의 트랩 분석

不言
不言앞으로
2019-03-25 10:03:502539검색

이 기사는 Python에서 동적 할당의 함정에 대한 분석을 제공합니다. 이는 특정 참조 가치가 있으므로 도움이 될 수 있습니다.

네임스페이스와 범위의 문제는 사소해 보일 수 있지만 사실 그 뒤에는 많은 것이 있습니다.

공간의 제약으로 인해 아직 논의되지 않은 중요한 지식 내용이 있습니다. 바로 "locals() 및 globals()의 읽기 및 쓰기 문제"입니다. 이 문제가 중요한 이유는 유연한 동적 할당 기능을 구현할 수 있다는 것입니다.

모두 사전 유형이며 사용법은 자명합니다. 그러나 이를 사용할 때 주의해야 할 함정이 있습니다. globals()는 읽고 쓸 수 있지만 locals()는 읽기만 가능하고 쓸 수는 없습니다. 오늘 제가 공유한 기사는 이 문제를 탐구하는 것입니다. 매우 심층적이어서 여러분과 공유하고 싶습니다.

직장에서 우리는 때때로 로컬 변수이든 전역 변수이든 동적 변수 할당이라는 상황에 직면합니다. 우리가 고민하고 있을 때 Python은 이 문제를 해결했습니다.

Python 네임스페이스는 사전 형태이며 특정 함수는 각각 로컬 네임스페이스와 전역 네임스페이스에 해당하는 locals() 및 globals()입니다. 따라서 이러한 메서드를 사용하여 "동적 "할당" 요구 사항을 달성할 수도 있습니다.

예를 들면 다음과 같습니다.

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

글로벌은 전역 네임스페이스를 변경할 수 있으므로 물론 로컬도 로컬 네임스페이스를 수정하고 함수 내에서 로컬 변수를 수정할 수 있어야 합니다.

하지만 정말 그럴까요?

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

출력:

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

프로그램이 실행되고 오류가 보고됩니다!

그러나 두 번째 인쇄 locals()에서는 로컬 공간에 이미 해당 변수가 있고 변수 a도 있으며 값은 1이라는 것을 분명히 볼 수 있습니다. , 그런데 print a에 도달하면 NameError 예외가 보고되는 이유는 무엇입니까?

다른 예를 살펴보세요:

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

출력:

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

상위 코드와 하위 코드는 할당도 트리거하지만 차이점은 다음과 같습니다. NameError 예외가 발생했습니다. 그런데 지역 변수 s의 값이 출력되었습니다.

이것은 직접 할당과 다르게 locals()를 통해 지역 변수를 변경하는 것인지 궁금합니다. , 그리고 큰 킬러 dis~

근본 원인 탐색

두 번째 코드 부분을 직접 분석하세요:

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

위 바이트코드에서 볼 수 있습니다:

locals() 해당 바이트코드는 LOAD_GLOBAL

s='test'에 해당하는 바이트코드는: LOAD_CONST 및 STORE_FAST

print s에 해당하는 바이트코드는: LOAD_FAST

print a해당 바이트코드는: LOAD_GLOBAL

위에 나열된 여러 키입니다. locals()를 통한 직접 할당/읽기와 할당/읽기의 성격이 매우 다르다는 진술. 그러면 NameError 예외가 발생하여 locals()[i] = 1을 통해 저장된 값이 두 위치가 다른지 확인합니까? 실제 로컬 네임스페이스?

이 질문에 대답하려면 먼저 실제 로컬 네임스페이스를 얻는 방법이 무엇인지 결정해야 합니다. 사실 이 질문은 위 바이트코드에서 이미 해결되었습니다. 표준 답변이 나와 있습니다!

실제 로컬 네임스페이스는 해당 데이터 구조 STORE_FAST에 존재합니다. 이것은 도대체 무엇입니까? 대답하려면 소스 코드가 필요합니다.

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

이것을 보면 명확해야 합니다. 함수 내부의 로컬 네임스페이스는 실제로 f_localsplus 멤버입니다. 이는 프레임 객체입니다. 함수에 의해 생성된 어린이 신발을 이해하는 것이 더 명확할 수 있습니다. 이 배열은 초기화되고 형식 매개변수는 바이트코드 18 61에 할당됩니다. LOAD_FAST 0(s), 네 번째 열의 0은 f_localsplus의 0번째 멤버인 "s" 값을 꺼내는 것입니다.

그래서 STORE_FAST는 로컬 네임스페이스의 실제 Store 변수이므로 locals()는 무엇인가요? ? 왜 실제처럼 보일까요?

이를 위해서는 바이트코드가 도움이 되지 않을 수 있습니다. 내장 함수를 직접 살펴보는 것은 어떨까요?

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

위의 PyFrame_FastToLocals에서 볼 수 있듯이 , locals()는 실제로 다음 작업을 수행합니다.

프레임 개체의 f_f->f_locals가 비어 있는지 확인하고, 비어 있으면 새 사전 개체를 만듭니다.

f_f->에 localsplus, co_cellvars 및 co_freevars를 씁니다.

다음은 위 내용에 대한 간략한 소개입니다.

localsplus: 함수 매개변수(위치 매개변수 + 키워드 매개변수), 할당된 변수 표시

co_cellvars 및 co_freevars: 클로저 함수에 사용되는 지역 변수

결론

위의 소스 코드를 통해 우리는 locals()가 보는 것이 실제로는 함수의 로컬 네임스페이스의 내용이라는 것을 이미 분명히 알고 있지만, 그 자체로는 로컬 네임스페이스를 나타낼 수 없는 프록시와 같습니다. 하지만 이 프록시를 변경하여 A, B, C가 실제로 소유한 것을 간단히 변경할 수는 없습니다.

이것이 바로 로컬을 통해 값을 동적으로 할당할 때( )[i] = 1, print는 NameError 예외를 발생시킵니다. 반대로, globals()는 실제 전역 네임스페이스이므로 일반적으로 말하면: locals()는 읽기 전용이고 globals()는 읽고 쓸 수 있습니다

이 기사는 여기서 끝났습니다. 더 흥미로운 내용을 보려면 PHP 중국어 웹사이트의 python 비디오 튜토리얼 컬럼을 주목하세요!

위 내용은 Python 동적 할당의 트랩 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 微信公众号:python猫에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제