文章目录
Python中变量作用域
1. 全局变量和局部变量
-
局部变量
- 定义在函数内部或者作为函数的形参的变量就是局部变量
- 局部变量只能在函数的内部使用,首次对变量进行赋值的时候是创建了局部变量,
再次赋值的本质就是改变了这个变量的绑定关系 - 形参类型的局部变量是函数的运行开始时可见,运行结束时消耗,函数外部不可访问
-
代码示例
-
全局变量
- 定义在函数的外部,模块内的变量称为全局变量
- 模块内的所有的函数都可以访问全局变量
- 在函数的内部只能直接访问,不能直接赋值
- 如果在函数内部为全局变量赋值,必须加上global全局变量声明,否则会被当成一个新的局部变量的创建
Global语句说明
- 告诉解释器,global声明的变量为模块级的作用域(全局作用域)
- 全局变量如果要在函数内部被赋值,则必须通过全局global语句声明
- 如果只是访问全局变量,则不需要global语句声明全局变量
- 不能先创建局部变量,再用global声明为全局变量
- global变量列表里的变量名不能出现在此作用域内的形参列表中
-
代码示例
-
globals() 和 locals()
globals()
返回一个字典,里面存放的是当前模块所有的全局变量,不只是你个人定义的locals()
也返回一个字典,里面存放的是当前模块中所有的局部变量,只包含个人定义的
g1, g2, g3 = 1, 2, 3 # 三个全局变量 def funcF(loc1, loc2): global g1, g2 g3 = 100 # 注意g3又是全局又是局部变量 loc4 = 200 # 局部变量 g1 = 11 # 全局变量值更新 g2 = 22 # 全局变量值更新 print("locals(): {}".format(locals())) print("globals(): {}".format(globals())) funcF(4, 5)
-
运行结果
locals(): { 'loc1': 4, 'loc2': 5, 'g3': 100, 'loc4': 200} globals(): { '__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001EE9AB36470>, '__spec__': None, '__annotations__': { }, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'E:/study_code/opencv_2021_07_study/python_core_study/P7_func_scope.py', '__cached__': None, 'g1': 11, 'g2': 22, 'g3': 3, 'funcF': <function funcF at 0x000001EE9AAEC268>}
结论
可以看到
g3
不只是全局变量,在局部变量的键值对中也是可以看到的.然后globals()返回的字典,不仅仅包含自己定义的全局变量的键值对,还包含模块内所有的全局变量
2. Python中的四个作用域原则(LEGB)
-
LEGB作用域说明
Local
局部作用域L
又叫本地作用域Enclosing
外部嵌套函数作用域E
Global
全局作用域G
Builtin
Python内置模块作用域B
-
一个变量名的查找顺序
Local > Enclosing > Global > Builtin
局部作用域 > 包裹这个函数的外部嵌套函数作用域 > 全局作用域 > 内置变量 -
nonlocal语句
- 告诉Python解释器,
nonlocal
语句声明的变量既不是全局变量,也不是局部变量,而是外部嵌套函数内的变量 - nonlocal只能再被嵌套的函数内部使用
- nonlocal变量将对外部嵌套函数作用域内的变量进行操作
- 当有两层的嵌套时,访问nonlocal变量只对最近一层的变量进行操作
- 形参中变量,不可以出现在nonlocal声明的变量列表中
- 告诉Python解释器,
闭包
1. 闭包概念
在嵌套的内部函数中引用了外部嵌套函数中变量的函数就形成了闭包,这个被引用的外部嵌套函数中的变量也被称为自由变量
-
闭包必须满足3个条件
- 必须是嵌套函数
- 内层嵌套函数必须引用了外层嵌套函数中的变量
- 外层嵌套函数的返回值必须是内层嵌套函数
-
作用
嵌套函数的内层函数可以使用外层函数的变量,即使外层函数返回了,或者被删除了.内层函数依然可以使用外层函数的这个变量
-
一个简单的闭包示例代码
2. 如何去判断是否是闭包
使用
__closure__
属性,如果它返回的是None,就不是闭包,如果有cell元素,就证明是闭包
注
- 嵌套定义: 定义在函数内部的嵌套函数,不能直接在全局内直接使用,必须要用外层嵌套函数返回
- 函数名的本质: 就是一个函数对象的变量名称,保存了函数所在的内存地址
装饰器函数
1. 装饰器函数概念
- 装饰器函数就是一个包装的函数,本质上是一个闭包函数
- 传入一个函数,在不修改原来的函数以及调用方式的前提下为原来的函数增加新的功能
- 装饰器函数中的内层函数中调用被装饰的函数,然后再被装饰的函数调用前后增加我们需要添加的功能代码,达到扩展其功能的目的
- 本质上就是函数名和函数地址的重新绑定
- 被装饰的函数看起来和原来的函数的名称是一样的,但是其在内存中已经修改了绑定关系,它已经绑定到我们装饰器函数的内层函数对应的地址上面去了
- 装饰器函数是用被装饰的函数作为参数来实现的,它其实是一个闭包函数
- 装饰器函数是一个嵌套函数,以外层函数的返回值其实就是被装饰过后的函数
2. 装饰器的简单示例
-
一个初始的函数
# 写一个函数计算不定个数的数据之和 def add_some_numbers(*args): return sum(args)
-
如果想要给上面的函数增加一个功能,即使统计下计算的时间,可能会这么去修改
import time # 写一个函数计算不定个数的数据之和 def add_some_numbers(*args): timeStart = time.time() res = sum(args) timeEnd = time.time() print("计算耗时: {}".format(timeEnd - timeStart)) return res
-
如果不想修改原来的代码,可以将这个函数传递到一个具有计时功能的函数中执行,例如下面这种方式
# 写一个函数计算不定个数的数据之和 def add_some_numbers(*args): return sum(args) def count_time(func,args): startTime = time.time() res = func(*args) endTime = time.time() print("计算结果: {},计算耗时: {}".format(res,endTime-startTime)) if __name__ == '__main__': f = add_some_numbers count_time(f,range(10000000))
这样做明显有一个缺点就是,以后每次使用add_some_numbers的时候,都要调用一次count_time,比较费劲
-
比较正确的做法就是使用装饰器函数
import time def deco(func): def wrapper(args): startTime = time.time() res = func(*args) endTime = time.time() print("计算结果: {},耗时: {}".format(res, endTime - startTime)) return res return wrapper # 如果一个函数被加上了@deco 符号,其实是一种这样的说明 #add_some_numbers = deco(add_some_numbers) @deco def add_some_numbers(*args): return sum(args) if __name__ == '__main__': add_some_numbers(range(1000000))
这里的
deco
函数就是原始函数的装饰器函数,它的参数是一个函数,返回的也是一个函数.其中作为参数的这个函数func()就在返回返回函数wrapper()的内部执行.其实@deco这个符号,就是一个赋值操作,相当于是改变了add_some_numbers
这个函数变量的绑定关系,等价于
add_some_numbers = deco(add_som_numbers)
=>wraper