locals(Python中locals的作用是什么)

发布时间:2025-12-10 23:42:51 浏览次数:1

Python的命名空间通过一种字典的形式来体现, 而具体到函数也就是locals() 和 globals(), 分别对应着局部命名空间和全局命名空间. 于是, 我们也就能通过这些方法去实现我们"动态赋值"的需求.

例如:

deftest():globals()['a2']=4test()printa2#输出4

很自然, 既然 globals能改变全局命名空间, 那理所当然locals应该也能修改局部命名空间.修改函数内的局部变量.

但事实真是如此吗? 不是!

defaaaa():printlocals()foriin['a','b','c']:locals()[i]=1printlocals()printaaaaa()

输出:

{}
{'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异常?

再看一个例子:

defaaaa():printlocals()s='test'#加入显示赋值sforiin['a','b','c']:locals()[i]=1printlocals()prints#打印局部变量sprintaaaaa()

输出:

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

根源探讨

直接对第二段代码解析:

130LOAD_GLOBAL0(locals)3CALL_FUNCTION06PRINT_ITEM7PRINT_NEWLINE148LOAD_CONST1('test')11STORE_FAST0(s)1514SETUP_LOOP36(to53)17LOAD_CONST2('a')20LOAD_CONST3('b')23LOAD_CONST4('c')26BUILD_LIST329GET_ITER>>30FOR_ITER19(to52)33STORE_FAST1(i)1636LOAD_CONST5(1)39LOAD_GLOBAL0(locals)42CALL_FUNCTION045LOAD_FAST1(i)48STORE_SUBSCR49JUMP_ABSOLUTE30>>52POP_BLOCK17>>53LOAD_GLOBAL0(locals)56CALL_FUNCTION059PRINT_ITEM60PRINT_NEWLINE1861LOAD_FAST0(s)64PRINT_ITEM65PRINT_NEWLINE1966LOAD_GLOBAL1(a)69PRINT_ITEM70PRINT_NEWLINE71LOAD_CONST0(None)74RETURN_VALUENone

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

  1. locals() 对应的字节码是: LOAD_GLOBAL

  2. s='test' 对应的字节码是: LOAD_CONST 和 STORE_FAST

  3. print s 对应的字节码是: LOAD_FAST

  4. print a 对应的字节码是: LOAD_GLOBAL

从上面罗列出来的几个关键语句的字节码可以看出, 直接赋值/读取 和 通过locals()赋值/读取 本质是很大不同的. 那么触发NameError异常, 是否证明通过 locals()[i] = 1 存储的值, 和真正的局部命名空间 是不同的两个位置?

想要回答这个问题, 我们得先确定一个东西, 就是真正的局部命名空间如何获取? 其实这个问题, 在上面的字节码上, 已经给出了标准答案了!

真正的局部命名空间, 其实是存在 STORE_FAST 这个对应的数据结构里面. 这个是什么鬼, 这个需要源码来解答:

//ceval.c从上往下,依次是相应函数或者变量的定义//指令源码TARGET(STORE_FAST){v=POP();SETLOCAL(oparg,v);FAST_DISPATCH();}--------------------//SETLOCAL宏定义#defineSETLOCAL(i,value)do{PyObject*tmp=GETLOCAL(i);\GETLOCAL(i)=value;\Py_XDECREF(tmp);}while(0)--------------------//GETLOCAL宏定义#defineGETLOCAL(i)(fastlocals[i])--------------------//fastlocals真面目PyObject*PyEval_EvalFrameEx(PyFrameObject*f,intthrowflag){//省略其他无关代码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.cstaticPyMethodDefbuiltin_methods[]={...//找到locals函数对应的内置函数是builtin_locals{"locals",(PyCFunction)builtin_locals,METH_NOARGS,locals_doc},...}-----------------------------//builtin_locals的定义staticPyObject*builtin_locals(PyObject*self){PyObject*d;d=PyEval_GetLocals();Py_XINCREF(d);returnd;}-----------------------------PyObject*PyEval_GetLocals(void){PyFrameObject*current_frame=PyEval_GetFrame();//获取当前堆栈对象if(current_frame==NULL)returnNULL;PyFrame_FastToLocals(current_frame);//初始化和填充f_localsreturncurrent_frame->f_locals;}-----------------------------//初始化和填充f_locals的具体实现voidPyFrame_FastToLocals(PyFrameObject*f){/*Mergefastlocalsintof->f_locals*/PyObject*locals,*map;PyObject**fast;PyObject*error_type,*error_value,*error_traceback;PyCodeObject*co;Py_ssize_tj;intncells,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'treportit:-(*/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写入localsif(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写入localsmap_to_dict(co->co_cellvars,ncells,locals,fast+co->co_nlocals,1);if(co->co_flags&CO_OPTIMIZED){//将co_freevars写入localsmap_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.

在这简单介绍下上面几个分别是什么鬼:

  1. localsplus: 函数参数(位置参数+关键字参数), 显示赋值的变量.

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

locals
需要做网站?需要网络推广?欢迎咨询客户经理 13272073477