python函数参数

关于Python函数变量作用域的问题,相关的话题非常多了,今天来旧事重提下,记录以下。

各大新手秘笈通常都会讲关于函数访问全局变量的问题,例如这篇(译文在此)。

python虚拟机会把源代码先编译成python操作码,然后执行。从某种角度来讲,程序的一切结果,在操作码生成好之后就注定了,会出现什么BUG,会运行到哪个角落,从刚开始的字节码就可以看出端倪。

首先来围观如下代码:

   
   
   
   
1 bar = 213 2 3 def func1(bar): 4 print bar 5 bar = ' func1 '

由于函数的参数是通过拷贝来传递进函数的,所以上诉代码就很好解释了,global中的bar被传递进去的是一个int指针,所以在函数里面对bar的修改是影响不到global里的bar.

第二段代码:

   
   
   
   
1 def func_a(): 2 print bar 3 4 def func_b(): 5 print bar 6 exec '' 7 8 def func_c(): 9 print bar 10 bar = ' 2b '

c函数出错了,异常为`UnboundLocalError: local variable 'bar' referenced before assignment`,这就说明python在执行到第一句print bar的时候,python的虚拟机就已经认定bar这个变量是一个局部变量了。难道python不是解释执行的?它应该是一行一行执行的,在执行函数a,b,c中的第一句print bar时,他们三者应该是同样的效果,看起来像是前面的代码能感应到后面的代码一样。这就是问题所在,其实python并不是解释执行源代码,它是解释执行由源代码编译后的操作码,看起来同样的源代码一定能编译成同样的操作码吗?查看一下操作码即可:

   
   
   
   
1 sdis.dis( ' func_a ' ) 2 ****************** byte code for func_a ****************** 3 40 0 LOAD_GLOBAL 0 (bar) 4 3 PRINT_ITEM 5 4 PRINT_NEWLINE 6 5 LOAD_CONST 0 (None) 7 8 RETURN_VALUE 8 sdis.dis( ' func_b ' ) 9 ****************** byte code for func_b ****************** 10 43 0 LOAD_NAME 0 (bar) 11 3 PRINT_ITEM 12 4 PRINT_NEWLINE 13 14 44 5 LOAD_CONST 1 ( '' ) 15 8 LOAD_CONST 0 (None) 16 11 DUP_TOP 17 12 EXEC_STMT 18 13 LOAD_CONST 0 (None) 19 16 RETURN_VALUE 20 sdis.dis( ' func_c ' ) 21 ****************** byte code for func_c ****************** 22 47 0 LOAD_FAST 0 (bar) 23 3 PRINT_ITEM 24 4 PRINT_NEWLINE 25 26 48 5 LOAD_CONST 1 ( ' 2b ' ) 27 8 STORE_FAST 0 (bar) 28 11 LOAD_CONST 0 (None) 29 14 RETURN_VALUE

请注意三个函数操作码的第一句,都是在打印之前取bar值,其中a函数用了`LOAD_GLOBAL`,b函数用了`LOAD_NAME`,而c函数则用了`LOAD_FAST`。问题就处在这里了,在生成操作码的时候,python编译器就已经认定了这些变量要从哪里去取,所以就分别使用了不同的操作码,导致了最后的结果是不同的。为什么python编译器要使用不同的操作码去表示相同的代码呢,具体的原因不得而知,不过从上面三个函数来看,如果python编译器可以确定在局部变量里不会出现该变量,则使用`LOAD_GLOBAL`,如果确定一定在局部变量里出现,则使用`LOAD_FAST`,如果啥都确定不了,那就使用`LOAD_NAME`吧(听起来python编译器好像在做优化?)

还有一个问题就是如果函数里有可变参数编译器怎么处理?简单的实验显示应该可变参数对编译器在这个问题上没任何影响。

总结:操作码真是一个好东西

你可能感兴趣的:(python函数参数)