Python 동적 할당의 트랩 분석

풀어 주다: 2019-03-25 10:06:03
앞으로
2639명이 탐색했습니다.

이 기사는 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으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿