专业人士笔记”系列目录:
创帆云:Python成为专业人士笔记--强烈建议收藏!每日持续更新!zhuanlan.zhihu.comnonlocal变量
Python 3添加了一个名为nonlocal的新关键字, nonlocal 关键字将使变量的声明覆盖到它的上级范围(如父、子函数关系)。你可以在PEP 3104中阅读有关它的所有内容。通过几个代码示例可以很好地说明这一点:
#这个counter函数会返回一个迭代器,每调用一次值+1
def counter():
num = 0
def incrementer():
num += 1
return num
return incrementer
#调用函数
c=counter();
print(c())
如果您尝试运行这段代码,您将收到一个UnboundLocalError报错,因为num变量在最内层的函数(incrementer)中进行了赋值,但是对这个内层函数来说,它并不知道num变量已经在它的父函数(counter)中已经申明过了,如果我们加一个nonlocal关键词,如下:
def counter():
num = 0
def incrementer():
nonlocal num
num += 1
return num
return incrementer
c = counter()
c() # 输出: 1
c() # 输出: 2
c() # 输出: 3
#这里正常输出了,通过nonlocal关键词,申明num变量为整个counter函数内都可用
基本上,nonlocal 关键词将允许你在上级范围内分配变量,但不允许在全局范围内分配,所以你不能在我们的计数器(counter)函数中使用nonlocal,因为那样它会试图分配给一个全局范围(因为counter没有父函数了)
尝试一下,你将很快得到一个SyntaxError语法错误
相反,必须在嵌套函数中使用nonlocal,这一点尤其要注意!
全局变量
直接看一段代码:
x = 'Hi' #函数外申明的,这个是全局变量
def read_x():
print(x)
# x 对全局变量进行了使用
read_x() # 打印出 Hi
def read_y():
print(y)
# y 试图对全局变量进行了使用,但之前没有定义过y这个全局变量
read_y() # 报错,因为y这个变量未定义
def read_y():
y = 'Hey'
#这里的 y 是定义的全局变量
print(y)
# 打印出 y 的值
read_y() #输出: ‘Hey’
局部变量
如果在函数中绑定了一个变量名,则默认情况下只能在函数中访问该变量名:
def foo():
a = 5
print(a) #如果调用函数,这里可以正常输出
print(a)
#在这里调用会直接报错,因为print这行代码是全局范围内的,其并不能访问foo函数内的变量a
del 命令
这个命令有几个相关但不同的形式
del v
如果v是一个变量,则命令del v将该变量从其作用域中删除,如下代码:
x = 5
print(x) # 输出: 5
del x
print(x) # NameError: name 'f' is not defined 变量名错误,变量f没有定义
请注意:del会触发变量在范围内删除变量的事件,这意味着除非显式声明(使用nonlocal或global关键字),否则del v将只对当前作用域的本地变量有效。如果要删除外部范围中的v,请在del v语句前加上nonlocal或global关键字
In all the following, the intention of a command is a default behavior but is not enforced by the language. A class
might be written in a way that invalidates this intention.
del v.name
此命令触发对v.__delattr__(name)的调用
其目的是使属性名不可用。例如 :
class A:
pass
a = A()
a.x = 7
print(a.x) # 输出: 7
del a.x
print(a.x) # 报错: AttributeError: 'A' object has no attribute 'x',即提示A这个类里面已经没有x这个属性了
del v
此命令触发对v.__delitem__(item)的调用。
其目的是使item项不再属于对象 v:
x = {'a': 1, 'b': 2}
del x['a']
print(x) # 输出: {'b': 2}
print(x['a']) # 报错: KeyError: 'a' 即该项不存在
del v[a:b]
This actually calls v.__delslice__(a, b).
其目的与上面描述的类似,但使用的是范围,而不是单个项。例如
x = [0, 1, 2, 3, 4]
del x[1:3]
print(x) # 输出: [0, 3, 4] 将1和2删除了
更多可参见Garbage Collection# del命令。
函数在查找变量名时跳过类作用域
类在定义时有一个类的局部作用域,但是类中的函数在查找变量名时不使用该作用域,这可能会导致一些出乎意料外的行为 :
a = 'global'
class Fred:
a = 'class' #将a在类里重新赋值,但注意这只是类 class 的作用域
b = (a for i in range(10)) #这是函数作用域,里面的a引用的是全局变量而不是类的变量,尤其要注意
d = a # 类作用域
e = lambda: a # 函数作用域 lambda是匿名函数
@staticmethod
或@classmethod,或其他常规实例方法
def g(): # 函数作用域
return a
print(Fred.a)
#输出:class
print(next(Fred.b))
#输出:global
print(Fred.d)
#输出:class
print(Fred.e())
#输出:global
print(Fred.g())
#输出: global
如果仍然不太理解这个范围如何工作,你可以直接把b和e整个打印出来试试
在类块中定义的变量名的范围仅限于类这个作用域;它不会扩展到函数里的代码块,这包括表达式生成器(如lambda这样的匿名函数或for表达式,因为它们是使用函数作用域实现的。这意味着下面的操作将失败 :
class A:
a = 42
b = list(a + i for i in range(10))
局部变量 与 全局变量 比较
什么是局部和全局作用域?
在代码中可以访问的所有Python变量都在局部作用域或全局作用域中,这里的解释是,局部作用域包括当前函数中定义的所有变量,全局作用域则还包括当前函数外部定义的变量。
foo = 1 #全局变量
def func():
bar = 2 #局部变量
print(foo)
#从全局作用域打印变量
print(bar)
# 从局部作用域打印变量
可以检查哪些变量在哪个范围内,内置locals()和globals()以字典的形式返回整个作用域。
foo = 1
def func():
bar = 2
print('局部变量',locals())
print('全局变量', globals())
#调用函数输出
func()
#输出内容:
局部变量 {'bar': 2}
全局变量 {'name': 'main', 'doc': None, 'package': None, 'loader': <_frozen_importlib_external.SourceFileLoader object at 0x7f920cc05710>, 'spec': None, 'annotations': {}, 'builtins': , 'file': '/home/python-study/main.py', 'cached': None, 'foo': 1, 'func': }
如果变量名冲突了会发生什么呢?
foo = 1
def func():
foo = 2
# 在局部范围内创建一个新的变量foo,全局foo不受影响
print(foo)
#输出 2
#这时全局变量foo仍然存在,并且值没有改变
print(globals()['foo'])
#输出 1
print(locals()['foo'])
#输出 2 #调用的局部变量
#调用函数看结果会发生什么,和上面看到的应该一样
func()
若要修改全局变量,请使用关键字global :
foo = 1
def func():
global foo
foo = 2
#这将修改全局foo,而不是创建一个局部变量
记住,默认函数体内定义的变量,是局部变量,而不会影响全局变量,即使变量名是同一个!
即一个变量永远不会在函数中一半是全局的,一半是局部的,反之亦然。
foo = 1
def func():
#这个函数有一个局部变量foo,因为它是在下面代码中定义的。foo在函数内是局部的,全局foo会被隐藏。
print(foo) # 引发UnboundLocalError,因为局部foo尚未初始化
foo = 7
print(foo) #正常输出变量值 7
print(globals()['foo']) #正常输出全局变量值 1
嵌套函数中:
函数中可能嵌套了许多层函数,但是在任何一个函数中,该函数只有一个局部作用域和全局作用域,没有两者同时存在的情况
foo = 1
def f1():
bar = 1
def f2():
baz = 2
#在这里,foo是一个全局变量,baz是一个局部变量
#print(locals().keys())
#输出:['baz']
print('bar' in locals())
#输出:False
print('bar' in globals())
#输出:False
def f3():
baz = 3
#print(bar)
#引用f1中的bar,使其进入f3的局部范围(闭包)
#print(locals().keys())
#输出['bar', 'baz']
print('bar' in locals())
#输出:True
print('bar' in globals())
#输出:False
def f4():
bar = 4
#一个新的局部变量 bar ,f1中的bar变量在这个范围内不可用
baz = 4
print(bar)
print(locals().keys())
#输出:['bar', 'baz']
print('bar' in locals())
#输出:True
print('bar' in globals())
#输出:False
global关键字 和 nonlocal关键字比较
这两个关键字都用于获得对非当前函数本地变量的写访问权
用global关键字声明变量名后,应该将该变量视为全局变量
foo = 0
#全局变量 foo
def f1():
foo = 1
#f1函数中,申明的一个新的局部变量foo
def f2():
foo = 2
#一个新的局部变量foo,其作用域在函数f2中
def f3():
foo = 3
#一个新的局部变量foo,其作用域在函数f3中
print(foo)
#输出值:3
foo = 30
#只修改了f3中的局部变量foo,其他的同名变量未受到影响
def f4():
global foo #注意:这里用global关键字申请了全局变量使用权
print(foo)
#输出:0 明显这个是全局变量的初始值
foo = 100
#这里修改的是,全局变量的值
另一方面,Python 3中提供的nonlocal变量(请参阅之前写的nonlocal变量章节)将一个局部变量从封闭的范围内提升到上一级函数作用域范围:
def f1():
def f2():
foo = 2
#在f2中申明了一个新的局部变量foo
def f3():
nonlocal foo # 用nonlocal变量申明了,从上一级函数(f2)中拿取该变量foo
print(foo)
#输出:2 这里引用了f2中的变量
foo = 20
#修改了f2中的变量foo的值!这里尤其要注意!
绑定事件
x = 5
x += 7
for x in iterable: pass
上面的每个语句都是绑定事件——x被绑定到用5表示的对象。如果该语句出现在函数中,则x默认绑定到本地函数;有关绑定语句列表,请参阅python官方文档中“语法”部分。
今天的分享就到这里,禁止转载,违者必究!