学习装饰器前,我们先了解三个概念:作用域、函数(嵌套)、闭包。
作用域
作用域简单说就是一个变量的命名空间。代码中变量被赋值的位置,就决定了哪些范围的对象可以访问这个变量,这个范围就是命名空间。python赋值时生成了变量名,当然作用域也包括在内。
在函数外,一段代码最始开所赋值的变量,它可以被多个函数引用,这就是全局变量;
在函数内定义的变量名,只能被函数内部引用,不能在函数外引用这个变量名,这个变量的作用域就是局部的,也叫它为局部变量 ;
如果函数内的变量名与函数外的变量名相同,也不会发生冲突。
x = 66 def func(): x = 88
x = 66这个赋值语句所创建的变量X,作用域为全局变量;
x = 88这个赋值语句所创建的变量X,它的作用域则为局部变量,只能在函数func()内使用。
尽管这两个变量名是相同的,但它的作用域为它们做了区分。作用域在某种程度上也可以起到防止程序中变量名冲突的作用。
在 Python 函数中会创建一个新的作用域。也就是说,当在函数体中遇到变量时,Python 会首先在该函数的命名空间中寻找变量名。Python 有几个函数用来查看命名空间。下面来写一个简单函数来看看局部变量和全局变量的区别。
>>> a_string = "This is a global variable" >>> def foo(): ... print locals() ... >>> print globals() {'__builtins__': <module '__builtin__' (built-in)>, 'a_string': 'This is a global variable', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x00000000022DDAC8>, '__doc__': None} >>> foo() {}
内建函数 globals 返回一个包含所有 Python 能识别变量的字典。
调用了 foo 函数,在函数中打印局部变量的内容。从中可以看到,函数 foo 有自己单独的、此时为空的命名空间。
总结:
1、变量的作用域由代码被赋值的位置所决定
2、变量可以在3个不同地方,对应3种不同作用域:
- 一个变量在函数内赋值,它的作用范围被定位在函数之内;
- 变量在函数外赋值,它作用域就是当前整个文件的全局变量;
- 当变量是在一个嵌套的函数中赋值时,对于这个嵌套的函数来说,这个变量是非本地的。
函数(嵌套)
Python中一个与众不同的语法就是可以嵌套函数,所谓嵌套,并不像其他语言中的在一个函数中调用另一个函数,而是在定义一个函数的时候,函数体里还能定义另一个函数。
内函数可以访问外函数的作用域,但不能重新赋值;外部函数不能访问内部函数的作用域。
例:
def foo(): # 定义函数foo(), m = 3 # 定义变量m=3; def bar(): # 在foo内定义函数bar() n = 4 # 定义局部变量n=4 print m + n # m 相当于函数bar()的全局变量 bar() # foo()函数内调用函数bar()
闭包
定义:如果一个内部函数里,对在外部作用域(单不是在全局作用域)的变量进行引用,那么内部函数就被认为闭包(closure)。
def outer(): x = 1 def inner(): print(x) # 调用外部变量 return inner # 函数名作为返回值 # 执行inner函数的两种方式 outer()() func = outer() func() # 相当于在外部执行inner函数
inner就是相对于outer的闭包函数
用途:
1、当闭包执行完后,仍然能够保持住当前的运行环境。
# 比如说,如果你希望函数的每次执行结果,都是基于这个函数上次的运行结果。我以一个类似棋盘游戏的例子来说明。 # 假设棋盘大小为50*50,左上角为坐标系原点(0,0),我需要一个函数,接收2个参数,分别为方向(direction),步长(step), # 该函数控制棋子的运动。棋子运动的新的坐标除了依赖于方向和步长以外,当然还要根据原来所处的坐标点,用闭包就可以保持住这个棋子原来所处的坐标。 origin = [0, 0] # 坐标系统原点 legal_x = [0, 50] # x轴方向的合法坐标 legal_y = [0, 50] # y轴方向的合法坐标 def create(pos=origin): def player(direction, step): # 这里应该首先判断参数direction,step的合法性,比如direction不能斜着走,step不能为负等 # 然后还要对新生成的x,y坐标的合法性进行判断处理,这里主要是想介绍闭包,就不详细写了。 new_x = pos[0] + direction[0]*step new_y = pos[1] + direction[1]*step pos[0] = new_x pos[1] = new_y # 注意!此处不能写成 pos = [new_x, new_y],原因在上文有说过 return pos return player player = create() # 创建棋子player,起点为原点 print player([1, 0], 10) # 向x轴正方向移动10步 print player([0, 1], 20) # 向y轴正方向移动20步 print player([-1, 0], 10) # 向x轴负方向移动10步
输出为
2、闭包可以根据外部作用域的局部变量来得到不同的结果,这有点像一种类似配置功能的作用,我们可以修改外部的变量,闭包根据这个变量展现出不同的功能。比如有时我们需要对某些文件的特殊行进行分析,先要提取出这些特殊行。
def make_filter(keep): def the_filter(file_name): file = open(file_name) lines = file.readlines() file.close() filter_doc = [i for i in lines if keep in i] return filter_doc return the_filter
如果我们需要取得文件"result.txt"中含有"pass"关键字的行,则可以这样使用例子程序
filter = make_filter("pass") filter_result = filter("result.txt")
参考博客:http://www.cnblogs.com/JohnABC/p/4076855.html
装饰器
概念:装饰器本质是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值也是一个函数对象。
它经常用于有切面需求的场景,比如:插入日志、性能检测、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
==>装饰器的作用就是为已经存在的对象添加额外的功能。
普通装饰器
import time def show_time(func): # 这里外加一层函数目的就是给所需功能函数提供一变量,该形参变量就是对应需要添加功能的函数名 def get_time(): start_time = time.time() func() end_time = time.time() print("Spend %s"%(end_time-start_time)) return get_time def index(): print("hello Wolrd") time.sleep(3) index = show_time(index) index()
其中的show_time就是装饰器,将想要调用的函数包裹在里面,通过变量的形式来进行传递,func()表示调用的原函数。
Python为了表示更加方便,为我们提供了语法糖,将上述index()=show_time(index) =封装=> @show_time
import time def show_time(func): def get_time(): start_time = time.time() func() end_time = time.time() print("Spend %s"%(end_time-start_time)) return get_time @show_time def index(): print("hello Wolrd") time.sleep(3) index()
提示:
- 这里需要注意的问题: index=show_time(index)其实是把get_time引用的对象引用给了index,而get_time里的变量func之所以可以用,就是因为get_time是一个闭包函数。
- Python装饰器如此方便归功于Python函数能够像普通对象一样作为参数传递给其他函数,函数名作为变量、返回值、定义在另外一个函数内。
含参装饰器
import time def show_time(func): def get_time(x, y): start_time = time.time() func(x, y) end_time = time.time() print("Spend %s"%(end_time-start_time)) return get_time @show_time def index(x, y): # 这里添加参数 print("hello Wolrd") time.sleep(3) print(x + y) index(1,3)
分析:
- 先来解封@show_time ==>index = show_time(index)
- 执行函数index(x, y),即执行函数get_time(x, y)
- 与此同时show_time(func)传递的是index(x, y)函数本身,因此对应的func(x, y)同样要带有对应的参数
动态参数装饰器
import time def show_time(func): def wrapper(*args, **kwargs): start_time = time.time() func(*args, **kwargs) end_time = time.time() print('spend %s' % (end_time-start_time)) return wrapper @show_time # add=show_time(add) def add(*args, **kwargs): time.sleep(1) sum = 0 for i in args: sum += i print(sum) add(1, 2, 3, 4)
带参数的装饰器
import time def time_logger(flag=0): # 新添加的外层函数 def show_time(func): # 原外层函数 def wrapper(*args, **kwargs): # 功能函数 start_time = time.time() func(*args, **kwargs) # 调用函数 end_time = time.time() print('spend %s' % (end_time - start_time)) if flag: print('将这个操作的时间记录到日志中') return wrapper return show_time @time_logger(1) def add(*args, **kwargs): time.sleep(1) sum = 0 for i in args: sum += i print(sum) add(1, 2, 3)
根据上面代码,@time_logger(1) 和@time_logger(0)结果不同,@time_logger(1)的结果比@time_logger(0)结果多输入一行内容“将这个操作的时间记录到日志中”。
分析:
- 调用time_logger()函数返回的闭包函数的函数名:show_time,此时结果为:@show_time,time_logger()中的参数就是为内部函数提供可用变量
- 下层解封就是和前面的过程一样
多层装饰器
def makebold(fn): def wrapper(): return "<b>" + fn() + "</b>" return wrapper def makeitalic(fn): def wrapper(): return "<i>" + fn() + "</i>" return wrapper @makebold # 装饰器1 @makeitalic # 装饰器2 def hello(): return "hello Siffre" print hello()
执行结果
分析:
- 从最里面的装饰器依次向外执行
- 前面的装饰器函数名都会作为下一个装饰器函数的参数
- 结果是多个装饰器的组合