注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Tsecer的回音岛

Tsecer的博客

 
 
 

日志

 
 

通过__name__的查找看python变量的作用域  

2017-07-28 21:05:50|  分类: python |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
一、__name__使用的例子
在python的代码中,经常可以看到下面的判断
if __name__ == "__main__":
用于判断脚本是否作为主脚本来运行。那么这个变量从哪里来,如何赋值,具体什么意义呢?

下面是一个简单的测试例子。例子比较简单,主要就是一个主脚本main.py,调用print__name__.py脚本来打印其中的预定义变量__name__的内容看其输出。

tsecer@harry: cat print__name__.py
def pname():
print "in pman function ", __name__

print "in file ", __file__, " ", __name__
tsecer@harry: cat main.py 
import print__name__
print "in ", __file__, " ", __name__
print__name__.pname()

tsecer@harry: python -V
Python 2.6.6
tsecer@harry: python main.py 
in file  /home/tsecer/Download/Python-2.2.2/tsecer/print__name__.py   print__name__
in  main.py   __main__
in pman function  print__name__
tsecer@harry: 

这个例子是我们意料之中的输出结果,就是可以同样的变量,在不同的脚本中执行有不同的预定义内容。无论如何,人们总是希望把新的事物类比为之前已经存在的事物,比方说这个输出结构,自然而然的就是和C语言中预定义的宏__FILE__有类似的效果:编译器在不同的文件中对于这个变量赋值为不同的具体内容。但是,它和传统的C语言面临的问题不尽相同:可以观察到的一个问题是print__name__.py文件的内容
def pname():
print "in pman function ", __name__

print "in file ", __file__, " ", __name__
其中的pname函数内部使用的__name__变量,它最终执行的时候,它是被main.py脚本执行,main.py脚本中的__name__明显应该等于__main__才对,如何保证在main.py中执行另一个脚本中的函数,这个函数使用的是另一个模块的名字,这是python解释器需要处理的一个问题。

二、原始的__name__从哪里来
当新创建一个module对象时,传入的参数决定了模块的__name__键值对应的value
Python-2.2.2\Objects\moduleobject.c

PyObject *
PyModule_New(char *name)
{
……
if (PyDict_SetItemString(m->md_dict, "__name__", nameobj) != 0)
goto fail;
if (PyDict_SetItemString(m->md_dict, "__doc__", Py_None) != 0)
goto fail;
……
}
对于我们这里关心的场景,其中也就是模块导入时执行的import命令传入的内容
load_module===>>>load_source_module===>>>PyImport_ExecCodeModuleEx
PyObject *
PyImport_ExecCodeModuleEx(char *name, PyObject *co, char *pathname)
{
PyObject *modules = PyImport_GetModuleDict();
PyObject *m, *d, *v;

m = PyImport_AddModule(name);
if (m == NULL)
return NULL;
d = PyModule_GetDict(m);
if (PyDict_GetItemString(d, "__builtins__") == NULL) {
if (PyDict_SetItemString(d, "__builtins__",
PyEval_GetBuiltins()) != 0)
return NULL;
}
……
PyImport_AddModule===>>>PyModule_New,而PyModule_New函数就是我们看到的函数,它将import中指定的文件名作为模块字典的__name__键值对应的值,也就是说,执行完该语句之后,在该模块对应的PyModule_Type类型的字典中可以通过__name__来获得import后的名字。

三、模块中语句的执行
模块加载时,首先进行符号表的构建,在构建过程中,对于遇到的变量类型,通过symtable_add_use表示变量是一个使用过的符号(USE)的符号
static void
symtable_node(struct symtable *st, node *n)
{
……
case atom:
if (TYPE(n) == atom && TYPE(CHILD(n, 0)) == NAME) {
symtable_add_use(st, STR(CHILD(n, 0)));
break;
}
在符号的解析阶段,会判断符号是否是未定义,如果未定义则根据当前场景是否为嵌套上下文,如果是则放在 freevar中,否则放入st_global中
static int
symtable_load_symbols(struct compiling *c)
{
……
if (is_free(flags)) {
if (ste->ste_nested) {
v = PyInt_FromLong(si.si_nfrees++);
if (v == NULL)
goto fail;
if (PyDict_SetItem(c->c_freevars, name, v) < 0)
goto fail;
Py_DECREF(v);
} else {
si.si_nimplicit++;
if (PyDict_SetItem(c->c_globals, name,
  implicit) < 0)
goto fail;
if (st->st_nscopes != 1) {
v = PyInt_FromLong(flags);
if (PyDict_SetItem(st->st_global, 
  name, v)) 
goto fail;
Py_DECREF(v);
}
}
}
当真正使用的时候,通过get_ref_type获得符号的类型,进而判断这个变量的操作码是使用全局、局部、未绑定等不同的虚拟指令。
我们看下生成的反汇编指令
tsecer@harry: cat print__name__.py
def pname():
print "in pman function ", __name__
print noexit
#print "in file ", __file__, " ", __name__

tsecer@harry: vi pnoexist.py
tsecer@harry: python 
Python 2.6.6 (r266:84292, Nov 21 2013, 10:39:36) 
[GCC 4.4.7 20120313 (Red Hat 4.4.7-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import dis, pnoexist
>>> dis.dis(pnoexist.pnoexist)
  2           0 LOAD_GLOBAL              0 (noexist)
              3 PRINT_ITEM          
              4 PRINT_NEWLINE       
              5 LOAD_CONST               0 (None)
              8 RETURN_VALUE        
>>> 
可以看到其中生成的对noexist的访问使用的是LOAD_GLOBAL指令,也就是默认假设它是一个全局变量。

>>> dis.dis(pnest.pL1.__code__.co_consts[2])
  4           0 LOAD_CONST               1 ('in L2')
              3 PRINT_ITEM          
              4 LOAD_GLOBAL              0 (noexist)
              7 PRINT_ITEM          
              8 PRINT_NEWLINE       
              9 LOAD_CONST               0 (None)
             12 RETURN_VALUE        
>>> 

四、函数定义时可见的符号表

load_source_module===>>>PyImport_ExecCodeModuleEx
PyObject *
PyImport_ExecCodeModuleEx(char *name, PyObject *co, char *pathname)
{
……
d = PyModule_GetDict(m);
if (PyDict_SetItemString(d, "__file__", v) != 0)
PyErr_Clear(); /* Not important enough to report */
Py_DECREF(v);

v = PyEval_EvalCode((PyCodeObject *)co, d, d);
……
}

PyEval_EvalCode===>>>PyEval_EvalCodeEx===>>>PyFrame_New===>>>
PyFrameObject *
PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals, 
   PyObject *locals)
{
……
f->f_globals = globals;
……

执行函数定义代码时使用的也是module私有的字典
PyObject *
PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
  PyObject **args, int argcount, PyObject **kws, int kwcount,
  PyObject **defs, int defcount, PyObject *closure)
{
……
case MAKE_FUNCTION:
v = POP(); /* code object */
x = PyFunction_New(v, f->f_globals);

五、作为模块调用时
PyObject *
PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
  PyObject **args, int argcount, PyObject **kws, int kwcount,
  PyObject **defs, int defcount, PyObject *closure)
{
……
case CALL_FUNCTION:
{
……
   if (PyFunction_Check(func)) {
   x = fast_function(func, &stack_pointer,
     n, na, nk);
   }
……
}

调用下面的fast_function函数,该函数把之前保存的模块字典作为globals传递给PyEval_EvalCodeEx作为栈帧参数执行。
static PyObject *
fast_function(PyObject *func, PyObject ***pp_stack, int n, int na, int nk)
{
PyObject *co = PyFunction_GET_CODE(func);
PyObject *globals = PyFunction_GET_GLOBALS(func);
PyObject *argdefs = PyFunction_GET_DEFAULTS(func);
PyObject *closure = PyFunction_GET_CLOSURE(func);
PyObject **d = NULL;
int nd = 0;

if (argdefs != NULL) {
d = &PyTuple_GET_ITEM(argdefs, 0);
nd = ((PyTupleObject *)argdefs)->ob_size;
}
return PyEval_EvalCodeEx((PyCodeObject *)co, globals,
 (PyObject *)NULL, (*pp_stack)-n, na,
 (*pp_stack)-2*nk, nk, d, nd,
 closure);
}
六、回到开始的例子
在main.py中通过print__name__.pname()调用pname()的时候,它看到的依然是print__name__模块中隔离的字典全局变量,进而访问的__name__依然是模块print__name__的名字,并且无法访问main.py中的全局变量。再进一步说,模块具有天然隔离的效果,一个模块不能直接访问另一个模块中的内容,必须通过模块名前缀引入作用域。

  评论这张
 
阅读(33)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017