python装饰器

1. 什么是装饰器

python具有函数式编程的特征,譬如lambdamapfilterreduce等高阶函数。在函数式编程中,函数是一等公民,即“一切皆函数”

2. 函数对象

  1. def square(n):
  2. return n*n
  3. func = square
  4. print(func) # <function square at 0x000002255042C1E0>
  5. 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. 装饰器模式

装饰器模式同样是一种结构型模式,其核心是为了解决由继承引发的“类型爆炸”问题。

“类型爆炸”——子类可以扩展父类的功能,随着业务复杂性的不断增加,子类不断变多。

装饰器模式就是一种以代替继承的技术,即无需通过继承增加子类就可以扩展父类的功能,同时不改变原有的结构。