iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Python locals() 的陷阱
  • 490
分享到

Python locals() 的陷阱

陷阱Pythonlocals 2023-01-31 00:01:05 490人浏览 泡泡鱼

Python 官方文档:入门教程 => 点击学习

摘要

在工作中, 有时候会遇到一种情况: 动态地进行变量赋值, 不管是局部变量还是全局变量, 在我们绞尽脑汁的时候, python 已经为我们解决了这个问题. Python 的命名空间通过一种字典的形式来体现, 而具体到函数也就是 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)
{
    
    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(); 
            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() 实际上做了下面几件事:

  1. 判断帧对象 的 f_f->f_locals 是否为空, 若是, 则新建一个字典对象.
  2. 分别将 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() 可读可写

作者:Lin_R
出处:Http://t.cn/RHki2rr

51Reboot 2019 最新课程招生信息
Python 零基础入门课程
此课程为面授班和网络班,一共 15 个课时,每周上一个全天,历时4个月。附加:录播视频+笔记+答疑2019-6月份开课

Python 自动化运维进阶课程
此课程为面授班和网络班,一共 15 个课时,每周上一个全天,历时4个月。附加:录播视频+笔记+答疑2019-4月份开课

Docker+k8s 课程
此课程为网络班,一共 150个课时,每周上一个全天,历时4个月。附加:录播视频+笔记+答疑现已开课
现在报名即可享受早鸟价

您可以添加我们的小助手WeChat:17812796384 咨询。

--结束END--

本文标题: Python locals() 的陷阱

本文链接: https://www.lsjlt.com/news/182821.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Python locals() 的陷阱
    在工作中, 有时候会遇到一种情况: 动态地进行变量赋值, 不管是局部变量还是全局变量, 在我们绞尽脑汁的时候, Python 已经为我们解决了这个问题. Python 的命名空间通过一种字典的形式来体现, 而具体到函数也就是 locals(...
    99+
    2023-01-31
    陷阱 Python locals
  • Python 与 ODBC 的陷阱
    连接    除了pyodbc还有win32com,熟悉ADO编程的就选择win32comeasy_install -U pyodbc    在这里若连接SQL Server可以使用 ACE.12.0 或者 JET.4.0,    Acces...
    99+
    2023-01-31
    陷阱 Python ODBC
  • 坑爹的Python陷阱
    我个人对陷阱的定义是这样的:代码看起来可以工作,但不是以你“想当然“”的方式。如果一段代码直接出错,抛出了异常,我不认为这是陷阱。比如,Python程序员应该都遇到过的“UnboundLocalError”, 示例: >>&g...
    99+
    2023-01-31
    陷阱 Python
  • Python字典遍历的陷阱
    众所周知,Python中常常按照key、value的形式来遍历字典的items。若value是基本数据类型(int,float等),则是传的拷贝,是不能直接修改value的: d...
    99+
    2022-11-12
  • python默认参数陷阱
    对于学习python的人都有这样的困惑   def foo(a=[]): a.append(5) return a Python新手希望这个函数总是返回一个只包含一个元素的列表:[5]。结果却非常不同,而且非常惊人(对...
    99+
    2023-01-31
    陷阱 参数 python
  • Python陷阱与缺陷列表有哪些
    本篇内容主要讲解“Python陷阱与缺陷列表有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python陷阱与缺陷列表有哪些”吧!比如,Python程序员应该都遇到过的“UnboundLoc...
    99+
    2023-06-02
  • Python陷阱与缺陷列表的示例分析
    Python陷阱与缺陷列表的示例分析,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。我个人对陷阱的定义是这样的:代码看起来可以工作,但不是以你“想当然“”的方式。如果一段代码...
    99+
    2023-06-17
  • 如何理解Python陷阱与缺陷列表
    如何理解Python陷阱与缺陷列表,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。我个人对陷阱的定义是这样的:代码看起来可以工作,但不是以你“想当然“”的方式。如...
    99+
    2023-06-17
  • python 默认参数问题的陷阱
    python 里面一个常见的陷阱就是函数的默认参数问题。如下: def func(mylist = []): mylist.append(1) return mylist 以下的执行结果如下...
    99+
    2022-06-04
    陷阱 参数 python
  • Python的C扩展-应用与陷阱
    Python的C扩展-应用与陷阱 1. 背景 2. Python扩展的用武之地-库测试 (1)动态库的测试 (2)静态库的测试 3 python模块级扩展 4 小结 反馈建议 ...
    99+
    2023-01-31
    陷阱 Python
  • Python面试题陷阱实例分析
    这篇文章主要讲解了“Python面试题陷阱实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python面试题陷阱实例分析”吧!问题1:请问如何修改以下Python代码,使得下面的代码调...
    99+
    2023-06-17
  • 程序员必知的Python陷阱与缺陷列表有哪些
    程序员必知的Python陷阱与缺陷列表有哪些,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。我个人对陷阱的定义是这样的:代码看起来可以工作,但不是以你“想当然“”的方式。如果一段...
    99+
    2023-06-17
  • ArrayList的陷阱实例分析
    这篇文章主要介绍“ArrayList的陷阱实例分析”,在日常操作中,相信很多人在ArrayList的陷阱实例分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”ArrayList的陷阱实例分析”的疑惑有所帮助!...
    99+
    2023-06-17
  • python使用语句的常见陷阱有哪些
    这篇文章主要为大家展示了“python使用语句的常见陷阱有哪些”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“python使用语句的常见陷阱有哪些”这篇文章吧。1、冗余input语句的冗余使用,这...
    99+
    2023-06-20
  • 怎样避免Kotlin里的陷阱
    今天给大家介绍一下怎样避免Kotlin里的陷阱。文章的内容小编觉得不错,现在给大家分享一下,觉得有需要的朋友可以了解一下,希望对大家有所帮助,下面跟着小编的思路一起来阅读吧。最近 Kotlin 特别流行,并且我也赞同 Kotlin &nbs...
    99+
    2023-06-17
  • Python闭包与闭包陷阱举例详解
    目录1 什么是闭包2 闭包示例代码3 什么是闭包陷阱4 闭包陷阱代码实例4.2 第二组代码实例总结1 什么是闭包 在 Python 中,闭包是一种特殊的函数,它能够记住它所在的环境(...
    99+
    2023-01-28
    python中闭包的概念 python闭包详解 Python闭包
  • Python量化回测常见陷阱有哪些
    这篇文章主要介绍“Python量化回测常见陷阱有哪些”,在日常操作中,相信很多人在Python量化回测常见陷阱有哪些问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Python量化回测常见陷阱有哪些”的疑惑有所...
    99+
    2023-06-02
  • Float与Real数据类型的陷阱
    SQL Prompt根据数据库的对象名称、语法和代码片段自动进行检索,为用户提供合适的代码选择。自动脚本设置使代码简单易读--当开发者不大熟悉脚本时尤其有用。SQL Prompt安装即可使用,能大幅提高编码...
    99+
    2022-10-18
  • 8 个不得不说的 MySQL 陷阱
    Mysql安装简单,速度较快,功能丰富。另外它还是开源运动的标杆,它的伟大成就向我们展示了一个成功的公司是可以建立在开源代码之上的。 然而用过mysql的人都曾对着显示器挥舞过拳头。但你不可能发明一种每秒能...
    99+
    2022-05-17
    mysql 陷阱
  • Scala数据库开发时的陷阱
          今天在Scala数据库开发时遇到一个新建用户接口一直插入不了数据的情况,后来发现是我还没数据scala的play-slick框架导致的,其实类似于DataCloud...
    99+
    2022-10-18
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作