0.前言
最近复习Python基础,本文的内容是Python学习手册中文第四版的学习笔记,其实只要记住LEGB原则就行了。(吐槽下这本书,翻译得不太好,有些代码缩进都不对)
1.Python作用域基础
Python中的变量名在第一次赋值时已经创建,并且必须经过赋值之后才能够使用。在代码中给一个变量赋值的地方决定了这个变量存在于哪个命名空间,也就是他的可见范围。Python中并不是所有的语句块中都会产生作用域,只有当变量在Module(模块)、Class(类)、def(函数)中定义的时候,才会有作用域的概念。在if-elif-else、for-else、while、try-except\try-finally等关键字的语句块中并不会产成作用域。
变量可以在3个不同的地方分配,分别对应3种不同的作用域:
----如果变量在一个def内赋值,他就被定位在这个函数之内(local);
----如果变量在一个嵌套的def种赋值,对于嵌套的函数来说,他是非本地的(nonlocal);
----如果在def外赋值,他就是整个文件全局的(global)。
x=99 #创建了一个全局变量
def func():
x=88 #创建了一个本地变量,仅在def内可见
#尽管这两个变量名都是x,但是他们的作用域可以把他们区别开来
作用域法则
内嵌的模块是全局作用域:
每一个模块都是一个全局作用域。对于外部的全局变量就成为一个模块对象的属性,但是在一个模块中能够像简单的变量一样使用。(我被这句话的翻译迷到了,这都啥)
全局作用域的作用范围仅限于单个文件:
这里的全局是指在一个文件的顶层的变量名进对于这个文件内部的代码而言是全局的。在Python中没有一个单个的,无所不包的情景文件的全局作用域。(全局变量对应到模块)
每次对函数的调用都创建了一个新的本地作用域
也就是说,将会存在由哪个函数创建的变量的命名空间。
赋值的变量名除非声明为全局变量(global)或者非本地变量(nonlocal),否则均为本地变量
默认情况下,所有函数定义内部的变量名是位于本地作用域内的。
所有其他的变量名都可以归纳为本地,全局或者内置的
注意:一个函数内部的任何类型的赋值都会把一个名称划定为本地的,包括‘=’赋值语句,import中的模块名称,def中的函数名称,函数参数名称等。并且,修改变量并不会把变量划分为本地的,只有对变量名赋值才可以。
变量名解析:LEGB原则
L-Local(funtion),表示本地作用域,或叫局部作用域
E-Enclosing function locals,表示def或lambda的本地作用域,或叫嵌套作用域
(E在Python3通过nonlocal语句解决,所以也可以看作LNGB)
G-Glocal(module),表示全局作用域
B-Built-in(Python),表示内置作用域
对于def语句:
变量名引用分为三个作用域进行查找,首先是本地,之后是函数内(如果有),之后是全局,最后是内置。
默认情况下,变量名赋值会创建或者改变本地变量。
全局声明(global)和非本地声明(nonlocal)将赋值的变量名映射到模块文件内部的作用域。
作用域实例
#假设在一个模块文件中有如下代码: #global scope x=99 def func(y): #local scope z=x+y return z func(1) #其中,x和func为global,y和z为local
内置作用域
内置作用域仅仅是一个名为_builtin_的内置模块,但是必须import builtins后才能使用内置的作用域。由于LEGB法则,Python最后将自动搜索这个模块。
以使用zip为例可以用两种方式:
1.直接使用zip;
2.import builtins,然后使用builtins.zip。
2.global语句
全局变量如果是在函数内被赋值的话,必须经过global声明,但全局变量名不经过声明也可以引用。
x=88
def func():
global x
x=99
func() #x=99
#示例2
y,z=1,2
def all_global():
global x
x=y+z
#如果不适用global,x会被因为赋值而被定为local变量
#如果x在函数运行前不存在,赋值语句将在模块中创建x这个变量
其他访问全局变量的方法
#testfile.py
var =99
def local():
var=0 #本地
def global1():
global var
var+=1 #全局
def global2():
var=0 #本地
import testfile #导入本模块的名字
testfile.var+=1 #全局
def global3():
import sys
glob=sys.modules['testfile']
glob.var+=1 #全局
3.作用域和嵌套函数
#示例1:
x=99
def func_out():
x=88
def func_in():
print(x) #通过LEGB规则找到func_out作用域的x
func_in()
func_out()
#这个嵌套作用于查找在嵌套的函数已经返回后也是有效的。
#示例2:
def func_out():
x=88
def func_in():
print(x)
return func_in
action=func_out()
action() #print 88
工厂函数(也叫闭合)
能够记住嵌套作用域的变量值的函数,尽管那个作用域已经不存在。
def maker(N):
def action(M):
return N**M
return action
f=maker(10)
f(2) #10**2
嵌套作用域与lambda表达式
def func():
x=10
action=(lambda n:x**n)
return action
x=func()
print(x(2)) #10**2
任意作用域的嵌套
作用域可以做任意的嵌套,但是只有内嵌的函数(而不是类)会被搜索:
def f1():
x=99
def f2():
def f3():
print(x)
f3() #正确
#f3() #错误
f2()
f1()
4.nonlocal语句
Python3引入了新的nonlocal语句,他只在函数内有意义。
nonlocal应用
#示例1-没有nonlocal
def tester(start):
state=start
def nested(label):
print(label,state)
#state+=1 #默认情况不允许修改嵌套作用域中的名称(这里指tester作用域中的)
return nested
f=tester(0)
f('test 1')
f('test 2')
#示例2-使用nonlocal
def tester(start):
state=start
def nested(label):
nonlocal state
print(label,state)
state+=1 #使用了nonlocal就可以修改tester作用域的state变量了
return nested
f=tester(0)
f('test 1')
f('test 2')
边界情况
和global不同,当执行一条nonlocal语句时,该变量名必须已经在嵌套的def中赋值过。其次,nonlocal限制作用域查找仅为嵌套的def。
x=99
def tester():
def nested():
# nonlocal x #错误,只能用于嵌套的def,这里是tester
print(x)
x+=1
return nested
与全局共享状态
可以在Python2.6中实现nonlocal效果的一种方法,把状态变量移到全局作用域。
def tester(start):
global state
state=start
def nested(label):
global state
print(label,state)
state+=1
return nested
f=tester(0)
f('f()')
存在问题1,有全局变量冲突的隐患。
存在问题2,每次调用tester,state都会重置。
使用类的状态
可以在Python2.6中实现nonlocal效果的另一种方法。
class tester:
def __init__(self,start):
self.state=start
def nested(self,label):
print(label,self.state)
self.state+=1
f=tester(0)
f.nested('f.nested()')
也可以用重载__call__的方式(在c++中也叫仿函数)
class tester:
def __init__(self,start):
self.state=start
def __call__(self,label):
print(label,self.state)
self.state+=1
f=tester(0)
f('f()')
使用函数属性的状态
也可以使用函数属性实现nonlocal相同的效果
def tester(start):
def nested(label):
print(label,nested.state)
nested.state+=1
nested.state=start
return nested
f=tester(0)
f('f()')
5.结语
Python的语法知识还是挺多的,虽然只是调下库实现下功能可以忽略很多东西 ,但是想要写出更符合Python风格的代码还是需要深入学习,就像C++里写带类的C和写现代的C++还是区别很大的。