声明:本文中大部分内容均在参考网址中,本人进行精简后,略加修改并加上一点自己的理解。
一、python中作用域
1.局部作用域,在函数内部或lambda、类中的全局局部变量中,调用函数时才会创建。每次调用都会创建一个新的本地作用域。调用结束后会销毁。(函数的参数也位于此作用域,这也解释了为什么不同函数,内部变量或参数名相同,并不引发冲突)。不可修改闭包作用域定义的变量,除非使用nonlocal语句。
2.闭包作用域(nonlocal),存在嵌套函数中,为其外层作用域。调用函数之后创建的变量不在此作用域。
3.全局作用域,顶层模块中所有函数外创建的全局变量。在函数内不可被修改,除非使用global语句,使用这些语句,也能在局部作用域创建全局变量。不过最好避免这种做法
4.内置作用域,builtins包,由python自动导入,一般为内置函数。如果担心其中命名被全局作用域覆盖,可显式写出。
与global不同,不能在嵌套或封闭函数之外使用nonlocal,也不能用nonlocal来创建变量。
二、搜索顺序问题(LEGB)
这里有两类情况需要区分:
2.1 在变量引用时,python的搜索顺序是按照从上向下的顺序搜索(如果有的话),这也就是为什么如果你命名变量和python内置变量冲突时,会导致覆盖,从而引发错误。
2.2 而在变量赋值时,则只考虑本地局部作用域,这是因为在python中赋值即定义。
abs = 20
abs(-15) # 由于内置函数名被覆盖,此时会出错
# 1.第一种办法
import builtins
builtins.abs(-15)
# 2.第二种办法
del abs # 从全局作用域删除其定义,因此可以访问内置作用域
abs(-15) # 15 正常执行
注意:闭包会从最内层搜索到最外层(多层嵌套情况)
前两个作用域保证你的变量不会在很远的地方被修改,方便调试和维护。
def scope_test():
spam2 = "outer2 spam"
def do_test():
def do_local():
spam = "local spam" # 局部变量
print("outer two:", spam2) # 最外层闭包作用域
print( do_local.__code__.co_varnames ) # 输出局部作用域变量
# print("after call:", spam3) # 调用函数之后创建的变量不在闭包作用域中
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam"
do_local()
spam3 = "after call create"
print("After local assignment:", spam)
do_nonlocal()
print("After nonlocal assignment:", spam)
do_global()
print("After global assignment:", spam)
do_test()
scope_test()
print("In global scope:", spam)
上述输出为:
outer two: outer2 spam
('spam',)
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
三、闭包(Closures)和闭包作用域(Enclosing)
Closures会返回一个函数,其作用相当于冻结一些参数,已供后续使用(类似存款)。经常用于一些需要延后执行的场景。partial()函数及装饰器就是使用这个技术实现的。
def scope_test1():
spam2 = "spam"
def do_test(): # 其调用时机被延后,因此定义在外层的变量都可使用
print(spam2)
print("After:", spam3)
spam3 = "after spam" # 在定义之后的变量也可使用
return do_test
mfun = scope_test1()
mfun()
其实据此可以分析出,Enclosing的变量必然是存储在do_test()函数中的,这些变量存储在func_name._ _ code _ _.co_freevars中
def scope_test1():
spam2 = "spam2"
def do_test():
print(spam2)
print("After:", spam3)
spam3 = "after spam3"
do_test()
spam4 = "spam4"
print( do_test.__code__.co_freevars ) # 属于闭包的自由变量
print( do_test.__closure__ ) # 这些元素是cell对象
print( do_test.__closure__[0].cell_contents ) # 这些元素具体的值
return do_test
mfun = scope_test1()
上述输出为:
spam2
After: after spam3
('spam2', 'spam3')
(<cell at 0x000001FADFAB1888: str object at 0x000001FADF997CA8>, <cell at 0x000001FADFAB1798: str object at 0x000001FADFC21030>)
spam2
从这再次验证了调用函数之后创建的变量不属于函数自由变量,因此在闭包中也就找不到该变量。
四、不符合LEGB的特例
4.1.列表生成式
[item for item in range(5)] # 类似局部作用域,一旦完成,内部变量销毁
# print( item ) # 会出错
# 与之对比
for item in range(5):
pass
print(item) # 输出4
4.2.exception variable
如下述代码,err变量只能在except代码块中使用,类似局部变量。而在except块中定义的其他变量是作为全局变量。要想在全局作用域使用err保存的信息,只能在except块中定义一个辅助变量ex,然后在外部引用ex变量。
mylist = [1, 2, 3]
try:
mylist[4]
except IndexError as err:
print(err) # list index out of range
ex = err # 这样就可以在外面使用此信息了
err = "change"
print(err) # change
new_var = 5
print(new_var) # 正常输出 5
print(err) # NameError: name 'err' is not defined
# -------------------------------
# 与之对比,with as语句并不会出现此问题
with open("score.txt", 'r') as f:
f.read()
print(f) # <_io.TextIOWrapper name='score.txt' mode='r' encoding='cp936'>
4.3.类和实例属性
class Myclass:
attr = 50
a = Myclass()
b = Myclass()
a.attr = 100 # 仅改变自身
print( b.attr ) # 50
Myclass.attr = 100 # 会改变所有实例的该属性
print( b.attr ) # 100
# 例2
class Myclass:
attr = 50 # class local scope
def __init__(self, var):
self.var = var # 其为实例属性 instance local scope
a = Myclass(40)
b = Myclass(60)
print(a.var) # 40
print(Myclass.var) # __init__根本没执行,AttributeError: type object 'Myclass' has no attribute 'var'
# 以下为两者区别
print(a.__init__) # <bound method Myclass.__init__ of <__main__.Myclass object at 0x000002B5D24F2BE0>>
print(Myclass.__init__) # <function Myclass.__init__ at 0x000002835EA687B8>
self是表示当前实例,也可从另一方面解释,即python中类的方法是与实例进行绑定的,因此没有实例就无法执行,除非是静态方法。
搜索顺序
1.搜索instance local scope,因此实例属性可覆盖类属性
2.class local scope
尽管类中也为局部作用域,但不适用闭包作用域,必须使用点。
实例属性覆盖类属性后,仍能通过类名.属性访问类属性。此时实例.属性返回的是实例属性。
class Myclass:
attr = 50
def __init__(self, var):
# print("attr ", attr ) # 类似闭包作用域做法,NameError
print("attr ", self.attr ) # 正确执行 或 使用Myclass.attr
a = Myclass(40)
参考
https://realpython.com/python-scope-legb-rule/#understanding-scope
https://docs.python.org/zh-cn/3/tutorial/classes.html
《流畅的python》