函数作用域

当一个程序在使用变量名时,python创建,改变或查找变量名都是在所谓的命名空间(一个保存变量的地方)中进行的。当我们谈论到搜索变量名对应于代码的值的时候,作用域这个术语指的就是命名空间。

Python将一个变量名被赋值的地点关联为一个特定的命名空间。在代码中给一个变量赋值的地方决定了这个变量将存在于哪个命名空间,也就是它可见的范围。

除打包代码外,函数还为程序增加了一个额外的命名空间层:在默认情况下,一个函数的所有变量名都是与函数命名空间相关联的。这意味着:

一个在def内定义的变量名能被def内的代码使用。不能在函数的外部引用这样的变量名。

def之中的变量名与def之外的变量名并不冲突,即使是相同的变量名。

 

一个变量的作用域总是由在代码中被赋值的地方所决定,并且与函数调用完全没关系。

不同地方分配变量:

def内赋值,被定位在这个函数之内

嵌套的def中赋值,对于嵌套的函数来说,它是非本地的

def之外赋值,它就是整个文件全局的。

 

作用域法则:

函数定义了本地作用域,而模块定义的是全局作用域。

内嵌的模块是全局作用域。每个模块都是一个全局作用域。对于外部的全局变量就成为一个模块对象的属性,但是在模块中能够像简单的变量一样使用。

全局作用域的作用范围仅限于单个文件。

每次对函数的调用都创建了一个新的本地作用域。

赋值的变量名除非声明为全局变量或非本地变量,否则均为本地变量。如果需要给一个在函数内部却位于模块文件顶层的变量名赋值,需要在函数内部通过global语句声明。如果需要给位于一个嵌套的def中的名称命名,从python 3.x开始可以通过在一条nonlocal语句中声明它来做到。

所有其他的变量名都可以归纳为本地,全局或内置的。

 

注意,原处改变对象并不会把变量划分为本地变量,实际上只有赋值才可以。例如,变量名L在模块顶层赋值为列表。函数内执行L.append(x)语句并不会把L划分为本地变量,而L = X却可以。修改一个对象并不是对一个名称赋值。

 

变量名解析:LEGB原则

在函数中使用为认证的变量名,python搜索4个作用域:

本地作用域(Local function),之后是上一层结构中的def或lambda的本地作用域(Enclosing function locals), 之后是全局作用域(Global),最后是内置作用域(Built-in),并且在第一处找到的地方停下来。

 

全局声明和非本地声明将赋值的变量名映射到模块文件内部的作用域

 

global: 一个命名空间的声明。它告诉python函数打算生成一个或多个全局变量名。

全局变量名:(总结)

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

全局变量如果是在函数内被赋值的话,必须经过声明

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

 

global允许我们修改一个模块文件的顶层的一个def之外的名词。nonlocal语句几乎是相同的,但它应用于嵌套的def的本地作用域内的名称,而不是嵌套的模块中的名称。

x = 88    # global x
def func():
    global x    # 这里增加一个global声明,以便在def之内的X能够引用在def之外的x
    x = 99
y, z = 1, 2    # global variables in module
def all_global():
    global x    # declare global assigned
    x = y+z

一些程序委任一个单个的模块文件去定义所有的全局变量。

全局变量在并行线程中在不同的函数之间成为了共享内存,所有扮演了通信工具的角色。

最小化全局变量:最好尽可能的避免使用全局变量(试试通过传递函数然后返回值来代替一下)。

最小化文件间的修改:在文件间进行通信的最好的办法是通过调用函数,传递参数,然后得到返回值:

# first.py
x = 99
def setx(new):
    global x
    x = new

# second.py
import first
first.setx(88)    # 推荐方法

# second.py
import first
first.x = 88    # 不推荐,这种情况好的话导致代码不灵活,坏的话引发bug

 

嵌套作用域的细节:

对于一个函数:

一个引用(x)首先在本地(函数内)作用域查找变量名x;之后会在代码的语法上嵌套了的函数中的本地作用域,从内到外查找;之后查找当前的全局作用域;最后再内置作用域内。全局声明将会直接从全局作用域进行搜索。

默认情况下,一个赋值(x = value)创建或改变了变量名x的当前作用域。如果x在函数内声明为全局变量,它将会创建或改变变量名x为整个模块的作用域。另一方面,如果x在函数内声明为nonlocal,赋值会修改最近的嵌套函数的本地作用域。

 

工厂函数

根据要求的对象,这种行为有时也叫闭合或工厂函数--一个能够记住嵌套函数作用域的变量值的函数。尽管类是最适合记忆状态的,像这样的函数也提供了一种替代的解决方法

>>> def maker(N):
	def action(X):
		return X** N
	return action

>>> f = maker(2)
>>> f(3)
9
>>> f(4)
16
>>> g = maker(3)
>>> g(3)
27
>>> f(3)    # 记住了N=2
9
>>> 

这是一种高级的技术,除了拥有函数式编程背景的程序员,以后在实际使用中也不会常常见到。另一方面,嵌套的作用域常常被lambda函数创建表达式使用--因为他们是表达式,它们几乎总是嵌套在一个def中。此外,函数嵌套通常用作装饰器--在某些情况下,它是最合理的编码模式。

嵌套作用域和lambda:

>>> def func():
	x = 4
	action = (lambda n: x**n)
	return action

>>> x = func()
>>> x(2)
16

 

作用域与带有循环变量的默认参数相比较:

如果lambda或者def在函数中定义,钱塘府在一个循环之中,并且嵌套的函数引用了一个上层作用域的变量,该变量被循环所改变,所有在这个循环中产生的函数将会有相同的值--最后一次循环中完成时被引用变量的值。

>>> def makeActions():
	acts = []
	for i in range(5):                       
		acts.append(lambda x: i ** x)    # Try to remember each i
	return acts

>>> acts = makeActions()
>>> acts[0](2)    # All remember same last i
16
>>> acts[2](2)    # All remember same last i
16
>>> acts[4](2)    # All remember same last i
16
>>> 
>>> def makeActions():
	acts = []
	for i in range(5):
		acts.append(lambda x, i=i: i ** x)    # Remember current i
	return acts

>>> acts = makeActions()
>>> acts[0](2)
0
>>> acts[2](2)
4
>>> acts[4](2)
16

 

nonlocal

nonlocal和global一样,声明了将要在一个嵌套的作用域中修改的名称。和global的不同之处在于,nonlocal应用于一个嵌套的函数的作用域中的一个名称,而不是所有def之外的全局模块作用域;而且在声明nonlocal名称的时候,它必须已经存在于该嵌套函数的作用域中--即nonlocal只允许对嵌套的函数作用域中的名称赋值,并且把这样的名称的作用域查找限制在嵌套的def.

global和nonlocal语句都在某种程度上限制了查找规则:

global使得作用域查找从嵌套的模块作用域开始,并且允许对那里的名称赋值。如果名称不存在于该模块中,作用域查找继续到内置作用域,但是,对全局名称的赋值总是在模块的作用域中创建和修改他们。

nonlocal限制作用域查找只能是嵌套的def,要求名称已经在那里,并且允许对他们赋值。作用域查找不会继续到全局或内置作用域。

# 引用没问题
>>> def tester(start):
	state = start
	def nested(label):
		print(label, state)
	return nested

>>> F = tester(0)
>>> F('spam')
spam 0
# 直接赋值不行
>>> def tester(start):
	state = start
	def nested(label):
		print(label, state)
		state += 1
	return nested

>>> F = tester(0)
>>> F('spam')
Traceback (most recent call last):
  File "<pyshell#48>", line 1, in <module>
    F('spam')
  File "<pyshell#45>", line 4, in nested
    print(label, state)
UnboundLocalError: local variable 'state' referenced before assignment

# nonlocal声明后可以进行赋值
>>> def tester(start):
	state = start
	def nested(label):
		nonlocal state
		print(label, state)
		state += 1
	return nested

>>> F = tester(0)
>>> F('spam')
spam 0

# 嵌套函数没state,nonlocal不能进行声明
>>> def tester(start):
	def nested(label):
		nonlocal state
		state = 0
		print(label, state)
		state += 1
	return nested
SyntaxError: no binding for nonlocal 'state' found

# global声明不要求state已经存在
# global don't have to exist yet when declared
>>> def tester(start):
	def nested(label):
		global state
		state = 0    # This creates the name in the module now
		print(label, state)
		state += 1
	return nested

# nonlocal 限制作用域查找仅为嵌套的def
>>> spam = 99
>>> def tester():
	def nested():
		nonlocal spam    # Must be in a def, not the module
		state = 0
		print('current:',spam)
		spam += 1
	return nested
SyntaxError: no binding for nonlocal 'spam' found
>>> 

全局、非本地、类和函数属性都提供了状态保持的选项。全局只支持共享的数据,类需要OOP的基本知识,类和函数属性都允许在嵌套函数自身之外访问状态。通常,你的程序的最好的工具取决于程序的目的。

 

Learning Python, Fourth Edition, by Mark Lutz.

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值