装饰器是python中一个强大的功能,利用装饰器可以在不必改动原有函数的前提下增加一些功能,可以被用于日志记录、权限验证、调试测试、事务处理等。装饰器是闭包的一种形式,闭包是python函数编程的高级用法,学习闭包之前让我们先了解python函数的概念理解和高级用法。本篇文章会结合闭包的形式和装饰器的使用分为上下两篇。
概念理解
<1>变量可以指向函数,函数对象和执行函数的定义
def add(x,y):
return x+y
fn = add
fv = add(3,3)
print(add)
print(fn)
print(fv)
print(add(2,3))
print(fn(4,5))
执行结果:
<function add at 0x0000019ED6503840>
<function add at 0x0000019ED6503840>
6
5
9
解析:代码<1>中定义了一个add()函数,打印add可以输出函数对象,打印add()输出函数执行后的结果。同时变量fn可以指向函数add,通过变量fn()可以调用add()函数。变量fv指向了执行函数add(3,3),打印出函数执行后的结果。
python函数高级用法
<1>函数可以作为参数进行传递
def add(x, y, f):
return f(x) + f(y)
print(add(-5, 6, abs))
执行结果
11
<2>函数作为结果值返回
def sum(x,y):
def cal():
return x+y
return cal
f = sum(3,5)
print(f())
执行结果:
8
解析:代码<2>中f调用执行sum()函数,函数cal对象作为返回值,此时x+y并没有执行,f()调用执行cal()函数,打印计算x+y结果。f调用执行sum()函数,同时接收sum()函数的返回值,此时 f=cal
为了说明闭包和嵌套函数的不同,让我们先看下两段代码,同时引入函数变量作用域的概念。
<1>
x = 0 # 模块级别定义的全局变量
def outer():
x = 1 # 嵌套中父级函数的局部作用域变量
def inner():
x = 2 # 函数中定义的变量
print("local: x=",x)
inner()
print("enclosing: x=",x)
outer()
print("global: x=",x)
执行结果:
local: x= 2
enclosing: x= 1
global: x= 0
解析:代码<1>即平常使用的函数嵌套,python允许出现同名变量,具有相同命名标识的变量在同一函数体中或具有函数嵌套关系,则不同作用域的变量各自代表不同的对象。 如果函数嵌套使用时,内部的inner函数需要传入参数该如何实现,我们来看接下来的代码。
<2>
def outer(x):
def inner(y):
print(x + y)
return inner
f = outer(5)
f(10)
解析:代码<2>中函数inner作为结果返回。若调用某函数时,该函数将其内部定义的函数作为返回值,则所返回的函数称为闭包。f调用执行outer()函数,同时接收outer()函数的返回值,此时 f=inner,f(10)打印输出15(5+10=15)
归纳:函数中,谁调用执行函数,同时接收执行函数的返回值。闭包的特点在于从外部能向内部的函数传递参数。
闭包的注意点
闭包引用了外部函数的局部变量,外部函数的局部变量没有及时释放,消耗内存。看下面的代码:
<1>
def counter():
s = 0
def inner():
nonlocal s # 声明父级函数的局部变量
s += 1
return s
return inner
f = counter()
print(f())
print(f())
执行结果
1
2
<2>
def count():
lists = []
def inner(x=0):
lists.append(x)
return len(lists)
return inner
f = count()
print(f(),f(),f())
执行结果
1 2 3
<3>
def get_avg():
data = []
def add_number(value):
data.append(value)
return sum(data)/len(data)
return add_number
f = get_avg()
print(f(100))
print(f(200))
执行结果
100.0
150.0
解析:返回函数引用的变量可以被修改。
利用闭包实现装饰器
<1>示例代码
def add(x,y):
return x+y
print(add(2,3))
执行结果:
5
如果此时我们需要在函数执行前打印出函数的参数,又不想改变原来的代码,应该怎么做呢?可以使用装饰器去实现。
<2>
def decorator(f):
def wrapper(x,y):
print("参数1为:%s,参数2为:%s"%(x,y))
return f(x,y)
return wrapper
@decorator
def add(x,y):
return x+y
print(add(2,3))
执行结果:
5
这种语法结构就是装饰器,遵循了代码编写的开放封闭原则,即已经实现的功能代码不允许被修改,但可以被扩展。虽然在这个原则是用的面向对象开发,但是也适用于函数式编程。
关于装饰器的内部原理和高级用法,我们在python之装饰器下篇介绍。