如何避开变量作用域的陷阱

浏览: 1245

这是菜鸟学python的第20篇原创文章


阅读本文大概需要6分钟


讲这个topic之前,我们先来说一个例子,这是我好几年前刚开始学python的时候,

一个很nice的老外给我讲的例子,很有意思:

x=99
def func1():
global x
x=88
def func2():
global x
x=77


大家觉得x最后的是值到底是多少:88还是77,先思考一下,最后我来揭晓谜底

python的变量跟其他语言一样,分为全局变量和局部变量,这个概念比较好理解,我们来看看python中是如何实现的:

1.全局变量与局部变量

x=100
def func():
print ('Inside func: x is {}'.format(x))
func()
print 'x is still:{}'.format(x)
>>
Inside func: x is 100
x is still:100


这个比较好理解x是全局变量,作用域是整个文件,函数内部是可以引用的.接着看下面一个例子:

x=100
def func():
x=10#多了这一行
print ('Changed local x to :{}'.format(x))
func()
print 'x is still:{}'.format(x)
>>>
Changed local x to :10
x is still:100 


大家看在函数外部有一个变量x,在函数内部也有一个变量x,这两个变量虽然名字长的一样,但是是完全不同的:

  • 函数内部的是本地变量,它的生命周期只在函数内部,出了函数就结束了,

  • 而x在函数外部模块文件中声明的(python一个文件也叫一个模块),是全局变量,不会被函数里面的局部变量影响,所以最后print的x还是100,


有人要问了,有的时候我需要让这个全局变量在函数里面处理,改变它的值,肿么办,这个python早就考虑到了,往下看~~

2.全局变量声明:

x=100
def func():
global x #注意加了一个global 关键字,表示x是全局作用域
print 'x is :{}'.format(x)
x=10
print ('Changed local x to :{}'.format(x))
func()
print 'Value of x:{}'.format(x)
>>>

x is :100
Changed local x to :10
Value of x:10


这个函数内部多了一个global关键字,结果就差很多:

  • 原因在于x被声明为函数内的全局变量,通过global这个语句是自己明确地映射到了模块的作用域

  • 函数内对x重新赋值x=10,会改变函数外x的值,所以最后print x是10



全局变量简单说就是这3点:

  • 全局变量是位于模块文件内部的顶层的变量名

  • 全局变量如何是在函数内被改变的话,一定要用global

  • 全局变量名在函数内部不经过声明也可以被引用


3.函数内的变量解析原则

有的书上叫LEGB法则,其实讲白了就是下面4个过程,当在函数中使用没有声明过的变量时,python的搜索顺序是:

  • 先是在函数内部的本地作用域(L)  

  • 然后是在上一层的函数的本地作用域(E)

  • 然后是全局作用域(G)

  • 最后是内置作用域(B)

简单说就是从局部到中央,好比你找一个人,村里找不到找乡->乡里找不到找市里->市里找不到找到全国档案局

我们来一一解释一下这些原则

1).本地函数

在函数内部(def或者lambda)通过任何方式赋值的,而且没有在该函数内声明为全局变量的变量名

2).上层函数的本地作用域

python函数是支持嵌套,而且多层嵌套,当你在最里层的函数找不到这个变量的时候,会往上一层的函数找,一层一层由内往外找,举个例子

def f1():
x=100
def f2():
print x 
f2()
print f1()
>>
100
None 
#因为f2()打印了之后没有return,对没有return的函数就默认返回None

我来解释一下:

  • def定义了一个f1()函数,里面又嵌套了一个f2()函数,这个def生成了一个函数并将其赋值给变量名f2

  • f2是f1的本地作用域内的一个本地变量,可以把f2看做一个临时函数,仅仅在f1内部执行的过程中存在.

  • f2函数干了一件事打印x,当在f2()内部找不到的时候,就通过LEGB法则往上找,f1()里面找到了x.


3).全局(模块)

在模块文件的顶层赋值的变量名,或者在该文件中的def生成的名为全局变量的变量名(函数内global声明的变量)


4).内置的作用域

这个很多初学者不明白,啥内置,内置了什么,其实很简单,python在运行之前会自动的引用一个内置模块,叫做__builein__,这是python的一个标准库模块,直接import进来,可以用dir(__builein__)看一下,里面都是预定义的一些变量名


是不是看到了很多熟悉的面孔(type,sum.sorted,open),对的那些就是内置的变量名,前面3种方法都找不到了就会去内置作用域这个列表里面.

换句话时候若你本地变量有一个跟内置变量一样的,就会被本地变量覆盖 

def hider():
sum='newsum'
return sum([1,2,3])
print hider()
>>TypeError: 'str' object is not callable

就是因为LEGB法则,本地的sum变量把内置作用域的sum变量覆盖了

下面总结一下:

开头的例子的答案其实X的值不是88也不是77而是都有可能因为不确定你先调用那个函数,变量的值取决于函数调用的顺序,而函数自身是任意顺序进行排列的,所以88,77都有可能,看那个函数最后调用.

这会导致很难debug,你必须要跟踪整个程序的控制流程,这其实就引出了另外一个话题,全局变量有相关性,用全局变量来记忆状态信息太复杂,最后是通过面向对象的方法,用类进行封装.(关于python中的类我们后续的文章会讲)


好了函数里的变量解析就讲到这里啦,希望能给初学者一些启发,若有什么不懂的,也可以留言跟我探讨交流.

最后说一下,原创不易,希望大家能够给点支持,欢迎转发,留言,也是对我的一点鼓励和动力.

也欢迎更多喜欢Python的同学关注 菜鸟学python,一起来学python吧 长按下方的二维码即可关注.


推荐 0
本文由 菜鸟学Python 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

0 个评论

要回复文章请先登录注册