python装饰器
1. 什么是装饰器
python具有函数式编程的特征,譬如lambda、map、filter和reduce等高阶函数。在函数式编程中,函数是一等公民,即“一切皆函数”
2. 函数对象
def square(n):return n*nfunc = squareprint(func) # <function square at 0x000002255042C1E0>print(func(2)) # 4
我们直接将一个函数赋值给一个变量,此时该变量表示的是一个函数对象的实例。函数对象——将这个对象像函数一样使用,所以当它带括号和参数时,表示立即调用一个函数;当它不带括号和参数时,表示一个函数。
· 使用函数作为参数
def sum_square(f,m,n):
return f(m)+f(n)
print(sum_square(square,3,4)) # 25
· 使用函数作为返回值
def square_wrapper():
def square(n):
return n * n
return square
wrapper = square_wrapper()
print(wrapper(5)) # 25
python中存在函数对象这样的类型,可以让我们像使用普通对象一样使用函数。那么我们可以将函数推广到普适对象适用的所有场合,即考虑让函数作为参数和返回值,因为普通对象都具备这样的能力。
让函数作为返回值和参数,这不仅是函数式编程中高阶函数的基本概念,而且是闭包、匿名方法和lambda等特性的理论基础。在示例中sum_square()和square_wrapper()分别展示了函数作为参数和返回值的可能性
def outer(m):
n = 4
def inner():
return m + n
return inner
func = outer(5)
print(func()) # 9
内函数修改外函数局部变量
def outer(m):
n = [10]
def inner():
n[0] += 1
return m + n[0]
return inner
func = outer(5)
print(func()) # 16
print(func()) # 17
# 若n为int不可变对象 例:10 报错
# UnboundLocalError: local variable 'n' referenced before assignment
对于python,这里的outer()函数和inner()函数分别被称为外函数和内函数,变量n的定义不在inner()函数内部,因此变量n称为inner()函数的环境变量。在python中,一个函数及其环境变量就构成了闭包(Closure)。
关于闭包:
1. 外函数返回了内函数的引用,即我们调用`outer()`函数时返回的是`inner()`函数的引用
2. 外函数将子集的局部变量绑定到内函数,其中变量n的目的就是展示如何在内函数中修改环境变量
3. 调用内函数意味着发生出、入栈,不同的是每次调用都共享一个闭包变量。
3. 装饰器decorator
装饰器decorator是一种高级python语法,装饰器可以对一个函数、方法或者类进行加工。装饰器使用简单,难点主要在于如何去写一个装饰器。首先看装饰器如何工作。
from functools import reduce
def decorator_print(func):
def wrapper(*arg):
print(arg)
return func(*arg)
return wrapper
@decorator_print
def sum(array):
return reduce(lambda x, y: x + y, array)
data = [1, 3, 5, 7, 9]
print(sum(data)) # ([1, 3, 5, 7, 9],)
# 25
我们注意到装饰器可以用def定义,装饰器接受一个函数对象作为参数,并返回一个新的函数对象。装饰器通过名称绑定,让同一个变量名指向一个新返回的函数对象,这样就达到修改函数对象的目的。在使用装饰器时,我们通常会在新函数内部调用旧函数,以保留旧函数的功能,这正是“装饰”一词的由来。在定义好装饰器后,就可以使用@语法,其实际意义是将被修饰对象作为参数传递给装饰器函数,然后将装饰器函数返回的函数对象赋给原来的被修饰对象。装饰器可以实现代码的可复用性,即我们可以用同一个装饰器修饰多个函数,以便实现相同的附加功能。
在上一个实例中,我们定义了一个decorator_print()的装饰器函数吗,它负责对一个函数func进行修饰,在调用函数func以前执行print语句,进而可以帮助我们调试函数中的参数,通过@语法可以让我们使用一个名称去绑定一个函数对象。它的调用过程可以分解为
sum = decorator_print(sum)
print(sum)
接下来我们再来写一个统计代码执行时长的装饰器decorator_timer()
import timeit
def decorator_timer(func):
def wrapper(*args, **kwargs):
t1 = timeit.default_timer()
func(*args, **kwargs)
t2 = timeit.default_timer()
print(f'{func.__name__}() has been running for {round(t2-t1,3)}s.')
return func
return wrapper
@decorator_timer
def run():
res = 0
for i in range(1000000000):
res += 1
print(res)
return res
#或者
def decorator_timer(funcname=''):
def decorator(func):
def wrapper(*arg, **kwargs):
t1 = timeit.default_timer()
res = func(*arg, **kwargs)
t2 = timeit.default_timer()
print(f'{func.__name__}() has been running for {round(t2-t1,3)}s.')
return res
return wrapper
return decorator
@decorator_timer('xx')
def xx():
pass
装饰器同样可以对类进行修饰。python标准库中提供了诸如classmethod、staticmethod、property等类修饰器。
4. 装饰器与设计模式
装饰器可以对函数、方法和类进行修改,同时保证原有功能不受影响。这自然而然地与面向切面编程(AOP, Aspect Oriented Program)联系起来,AOP的核心思想是以非侵入的方式,在方法执行前后插入代码片段,以此来增强原有代码的功能。AOP通常通过代理模式(static/dynamic)来实现,而如此同时,在Gof提出的“设计模式”中有一种设计模式被称为装饰器模式。这两种模式有着相似性和内在联系。
5. 代理模式
代理模式,属于23种设计模式种的结构性模式,核心是为真实对象提供一种代理来控制该对象的访问。在这里我们提到了真实对象,这就要首先引出代理模式种的三种角色:
· **抽象对象**:通过接口或者抽象类声明真实角色实现的业务方法
· **代理对象**:实现抽象对象,是真实对象的代理,通过真实角色的业务逻辑方法来实现抽象方法
· **真实对象**:实现真实角色,定义真实角色所要实现的业务逻辑,供代理角色调用
通过UML图我们可以发现,代理模式通过代理对象隐藏了真实对象,实现了调用者对真实对象的访问控制,即调用者无法直接接触到真是对象。
6. 装饰器模式
装饰器模式同样是一种结构型模式,其核心是为了解决由继承引发的“类型爆炸”问题。
“类型爆炸”——子类可以扩展父类的功能,随着业务复杂性的不断增加,子类不断变多。
装饰器模式就是一种以代替继承的技术,即无需通过继承增加子类就可以扩展父类的功能,同时不改变原有的结构。
