【Python学习】python学习手册--第十七章 作用域

Python作用域

在Python程序中使用变量的时候,都是在所谓的命名空间中进行,命名空间也是存放变量的地方,变量的作用域就是指的命名空间。变量在代码中被赋值时的位置(嵌套深度)就决定了该变量的命名空间(即该变量能被访问到的范围)。Python将一个变量名被赋值的地点关联为一个命名空间。
函数为Python程序增加了一个额外的命名空间层,即在默认情况下,一个在函数内部声明的变量,只能在函数内部被使用:

  • 一个在def函数内部被声明的变量,在默认情况下,只能在函数内部使用。
  • def之中的命名空间与def之外的命名空间是不重复的,即使有相同的变量名,它们之中的变量也不会冲突。

作用域法则

  • 内嵌的模块是全局作用域,创建于顶层的变量,都是在该模块中的全局作用域中。对于一个外部的模块,需要像使用对象属性一样,使用外部模块的变量。也可以说是两个模块的命名空间是不一样的,没有包含关系,是并列的。在使用时,变量名要由模块文件来区分命名空间(如sys.path),要精确指出使用的模块,才能使用该模块中的变量。
  • 全局作用域的作用范围仅限于单个文件。这里所说的“全局”是指单个模块文件中的顶层变量是对于该文件内部的代码而言是全局的。
  • 每次对函数的调用都创建了一个新的本地作用域。函数的创建定义了一个函数作用域,而函数的调用会创建一个新的本地作用域,创建的函数可能会被调用多次,每调用一次都会创建一个新的本地作用域。
  • 赋值的变量名除非声明为全局变量(global)或非本地变量(nonlocal),否则默认全部为本地变量。在没有任何作用域声明语句使用的情况下,函数内部声明的变量全部为函数本地变量。
  • 函数内部的变量名查找会按照顺序查找作用域:本地变量->全局变量->内置变量

需要注意的是,在原处修改对象并不会改变变量的作用域,实际上只有对变量名赋值才可以。

变量名解析:LEGB原则

作用域可以总结为以下三点:

  • 变量名的引用分为以下三个作用域,按照先后顺序依次查找:本地->函数内(如果嵌套的函数)->全局->内置。
  • 默认情况下,变量名赋值创建或改变的是本地的变量。
  • 全局声明(global)和非本地(nonlocal)声明将赋值的变量名映射到模块文件内部的作用域。
    这里写图片描述

内置作用域

内置作用域是定义在一个内置的模块中,名为builtins,在Python内部,查找变量名时会自动搜索这个模块中的变量,因此我们不需要在程序中明文导入该模块。由于LEGB作用域查找法则,先查找的作用域内部的变量,在变量名相同的情况下,很有可能覆盖存在于后续要查找的作用域内的变量。

global语句

global是声明一个全局变量的声明语句。全局变量有以下特征:

  • 全局变量是位于模块文件内部的顶层的变量名
  • 全局变量如果要在函数内部被赋值的话,就必须要声明(global语句)
  • 全局变量名在函数的内部不经过声明也可以被引用

global语句包含global关键字,后面跟着一个或多个变量名,这些变量名被声明之后,它们就存在于文件模块的全局作用域中。

>>> x=123
>>> y=456
>>> def func():
...   global z            #在函数内部将z声明为全局变量
...   z=999
... 
>>> func()
>>> x+y+z                #这样z即可在文件模块中使用,z已经成为全局变量
1578
>>> def func():
...   global z=3              #使用global时,不能将它与赋值语句一起使用,要先声明,再使用
  File "<stdin>", line 2
    global z=3
            ^
SyntaxError: invalid syntax
>>> 

全局变量应该较少使用,在函数中多使用本地变量,降低函数的耦合性,全局变量让程序变得更不容易理解,函数存在的意义是为了完成某项功能与任务,如果全局变量用得过多,这种函数会对模块造成不小影响,所以应避免过多在函数中声明全局变量。
全局变量有时是为了在退出函数时,保存某些信息,在下次调用时使用。全局变量在并行线程中在不同函数之间是一块共享的内存,所以有些时候全局变量充当了在不同函数之间通信的工具

嵌套函数

在一个函数f1中嵌套着另外一个函数f2对象,而且该函数对象f2只能被嵌套的函数f1使用,f2是一个临时函数,只在f1函数执行过程中存在,并且只对f1可见:

>>> def f1(x):     
...    print(x)    
...    def f2(x):       #f2函数嵌套在f1函数中
...      print(x+1)
...    f2(x)
... 
>>> f2(2)              #f2函数只在f1函数中可见
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'f2' is not defined
>>> f1(2)         #调用f1会调用f2函数
2
3                 #调用f2后打印出的值
>>> 

工厂函数

工厂函数:一个可以记住嵌套作用域的变量值的函数。

>>> def maker(N):
...   def func(x):
...     return x**N
...   return func   #返回一个内嵌的函数对象
... 
>>> f=maker(3)  #内嵌函数记住了整数3,即被嵌套的函数内部的变量N,尽管这时只是返回的内嵌函数,maker函数已经退出
>>> f(3)       #计算3^3,函数对象f记住了内嵌函数中使用的变量3,
27
>>> 

外层的maker函数就像是工厂函数一样,用最初传递进去的值,创建了一个函数并返回。

嵌套作用域内的存储的变量,是在嵌套函数被调用的时候才开始去查找特定作用域中的变量。

>>> def f1():             
...     act=[]
...     for i in range(5):
...        act.append(lambda x: x+i)
...     return act
... 
>>> l=f1()
>>> l[0](1)         #按照嵌套的函数内的逻辑,列表中的函数应该是不一样的基数
5                   #但是嵌套函数在调用的时候才去作用域中查找变量,所以列表中的函数都会返回相同的值
>>> l[1](1)
5
>>> l[2](1)
5
>>> l[3](1)
5
>>> l[4](1)
5
>>> 

如果要达到构建不同函数的效果,就要让变量传递到内嵌函数中,成为内嵌函数中的变量,这样列表中的函数就可以在各自的内嵌函数作用域中去查找各自的变量。

>>> def f1():                       
...     act=[]                      
...     for i in range(5):          
...        act.append(lambda x,i=i: x+i)    #将参数传入内嵌函数中,使得内嵌函数的作用域中有各自不同的变量
...     return act
... 
>>> l=f1()
>>> l[0](1)
1
>>> l[1](1)
2
>>> l[2](1)
3
>>> l[3](1)
4
>>> l[4](1)
5
>>> 

nonlocal语句

nonlocal语句是global的近亲,nonlocal语句只是修改嵌套函数作用域内的变量,而不是本地变量也不是全局变量。
语法结构:

>>> def func():
...       x=12
...       def func2():
...           nonlocal x     #这里声明了变量x是函数func()中的x
...           x=13           #并把x的值改为13
...       func2()
...       print(x)           #这里会输出13
... 
>>> func()
13
>>> x=14
>>> x
14
>>> func()
13
>>> x           #并没有修改全局中的变量
14
>>> def func2():                
...   nonlocal x          #如果声明nonlocal时,该函数外层并没有嵌套函数,会报错
...   x=3333
...   print(x)
... 
  File "<stdin>", line 2
SyntaxError: no binding for nonlocal 'x' found
>>>  

当运行到nonlocal语句时,nonlocal声明的变量必须在一个嵌套的def函数内提取被定义过。 global声明意味着声明的变量已经在全局变量中被定义过。nonlocal声明意味着声明的变量在嵌套的函数中被定义过(只能出现在嵌套的函数中,即使是全局变量也不行)。在一般没有nonlocal声明的情况下,Python是不允许修改嵌套函数中的变量的。这种情况下就要使用nonlocal语句

>>> def func():
...     x=3
...     def func2():
...         print(x)
...         x=4             #不允许直接修改,只允许访问。需要修改时,使用nonlocal语句
...         print(x)
...     func2()
...     print(x)
... 
>>> func()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in func
  File "<stdin>", line 4, in func2
UnboundLocalError: local variable 'x' referenced before assignment
>>> 
>>> def func():
...     x=3
...     def func2():
...         nonlocal x      #声明为外层func()函数中的x变量
...         print(x)
...         x=4
...         print(x)
...     func2()
...     print(x)
... 
>>> func()
3
4
4
>>> 

与global语句类似,nonlocal在一些状态下也是用来记录函数调用退出后的一些信息。

小结

全局,非本地,类和函数都提供了保持函数状态的选项。在默认情况下,变量的作用域取决于变量被赋值的位置,还可以使用global,nonlocal语句来让变量的作用域更具有多样性。需要保存某个变量的状态时,就要考虑到将其所在的命名空间也保存下来,工厂函数就是这样的道理,当将一个工厂函数赋值给一个变量名(更准确的说是函数名)时,这个函数名就保存着相应的命名空间,相应的也能记录下命名空间下的内嵌函数和变量名

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值