旗下导航:搜·么
当前位置:网站首页 > Python教程 > 正文

Python动态赋值的圈套剖析【Python教程】,python

作者:搜教程发布时间:2019-11-27分类:Python教程浏览:41评论:0


导读:本篇文章给人人带来的内容是关于Python动态赋值的圈套剖析,有肯定的参考价值,有须要的朋侪可以参考一下,愿望对你有所协助。定名空间与作用域题目,看似眇乎小哉,实在背地大有...
本篇文章给人人带来的内容是关于Python动态赋值的圈套剖析,有肯定的参考价值,有须要的朋侪可以参考一下,愿望对你有所协助。

定名空间与作用域题目,看似眇乎小哉,实在背地大有文章。

因为篇幅所限,另有一个主要的学问内容没谈,即“locals() 与 globals() 的读写题目”。之所以说这个题目主要,是因为它可以完成一些天真的动态赋值的功用。

它们都是字典范例,用法不需多言。然则,在使用过程中,有一个圈套须要注重:globals() 可读可写,而 locals() 只可读却不可写。本日分享的文章,就是在讨论这个题目,写得很深切,特分享给人人。

在工作中, 有时刻会碰到一种状况: 动态地举行变量赋值 , 不管是部分变量照样全局变量, 在我们挖空心思的时刻, Python已为我们处理了这个题目.

Python的定名空间经由过程一种字典的情势来表现, 而细致到函数也就是locals() 和 globals(), 离别对应着部分定名空间和全局定名空间. 因而, 我们也就可以经由过程这些要领去完成我们"动态赋值"的需求.

比方:

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

很天然, 既然 globals能转变全局定名空间, 那天经地义locals应当也能修正部分定名空间.修正函数内的部分变量.

但现实真是云云吗? 不是!

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 'a' is not defined

顺序运转报错了!

然则在第二次print locals()很清晰可以看到, 部分空间是已有那些变量了, 个中也有变量a而且值也为1, 然则为何到了print a却报出NameError非常?

再看一个例子:

def aaaa():
    print locals()
    s = 'test'                    # 到场显现赋值 s       
    for i in ['a', 'b', 'c']:
        locals()[i] = 1
    print locals()
    print s                       # 打印部分变量 s 
    print a
aaaa()

输出:

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

高低两段代码, 区分就是, 下面的有显现赋值的代码, 虽然也是一样触发了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 ('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

在上面的字节码可以看到:

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的成员f_localsplus, 这是一个数组, 相识函数建立的童鞋能够会比较清晰, 在CALL_FUNCTION时, 会对这个数组举行初始化, 将形参赋值什么都邑顺次塞进去, 在字节码18 61 LOAD_FAST 0 (s)中, 第四列的0, 就是将f_localsplus第 0 个成员取出来, 也就是值 "s".

所以STORE_FAST才是真正的将变量存入部分定名空间, 那locals()又是什么鬼? 为何看起来就跟真的一样?

这须要剖析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'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是不是为空, 如果, 则新建一个字典对象.

离别将localsplus, co_cellvars和co_freevars 写入 f_f->f_locals.

在这简朴引见下上面几个离别是什么鬼:

localsplus: 函数参数(位置参数+症结字参数), 显现赋值的变量.

co_cellvars 和 co_freevars: 闭包函数会用到的部分变量.

结论

经由过程上面的源码, 我们已很明白晓得locals() 看到的, 的确是函数的部分定名空间的内容, 然则它本身不能代表部分定名空间, 这就彷佛一个代办, 它收集了A, B, C的东西, 展现给我看, 然则我却不能简朴的经由过程转变这个代办, 来转变A, B, C真正具有的东西!

这也就是为何, 当我们经由过程locals()[i] = 1的体式格局去动态赋值时, print a却触发了NameError非常, 而相反的, globals()确切真正的全局定名空间, 所以平常会说:locals() 只读, globals() 可读可写

本篇文章到这里就已悉数完毕了,更多其他精彩内容可以关注ki4网的python视频教程栏目!

以上就是Python动态赋值的圈套剖析的细致内容,更多请关注ki4网别的相干文章!

标签:python


欢迎 发表评论: