Python装饰器详解

769 阅读2分钟

高阶函数

满足如下任一条件都是高阶函数:

  • 可以接受函数作为参数
  • 可以将函数作为返回值返回
########## 接受函数作为参数 ##########
f = abs

def add(x, y, f):
    return f(x) + f(y)

print(add(-1, -2, f))

########## 将函数作为返回值返回 ##########
def hello():
    print('Hello World')

def wrapper(func):
    return func

f = wrapper(hello)
f()

闭包(Closure)

先来看一下变量的作用域:

  • L:local 函数内部作用域
  • E:enclosing 函数内部与内嵌函数之间(函数定义的变量可以被内嵌函数使用)
  • G:global 全局作用域
  • B:build-in 内置作用域(解析器自动导入的成员)

变量作用域查找过程的优先级是:LEGB(L>E>G>B)。

def func(lst):
    def in_func():
        return len(lst)
    return in_func

f = func([1,2,3])
print(f())

像这种内层函数引用了外层函数的变量(参数也算变量),然后返回内层函数的情况,称为闭包。

装饰器

如果我们定义了一个函数想在运行时动态增加功能,又不想改动函数本身的代码,如何解决这个问题呢?我们想到了高阶函数,高阶函数可以接收函数作为参数,可以返回函数,那么我们是否可以接收一个函数,对其包装,然后返回一个新函数,这样就可以解决这个问题了,如下所示:

def f1(x):
    return x * 2
# fn就是一个用高阶函数实现的一个装饰器函数
def fn(f):
    def new_fn(x):
        print('call ' + f.__name__ + '()')
        return f(x)
    return new_fn
# 调用
f1 = fn(f1)
print(f1(5))

Python内置的@语法简化了装饰器的调用,如下所示:

@fn
def f1(x):
    return x * 2

上面定义的是一个不带参数的装饰器函数,然后看下带参数的装饰器如何定义:

def log(prefix):
    def log_decorator (f):
        def wrapper(*args, **kw):
            print('[' + prefix + ']' + ' call ' + f.__name__ + '()')
            return f(*args, **kw)
        return wrapper
    return log_decorator

@log('NOTICE')
def f1(x):
    return x * 2

print(f1(5))

所以带参数的装饰器函数先返回一个新函数,然后让这个函数接收f1()函数作为参数并再返回新函数。

装饰器就是高阶函数、嵌套函数、闭包三者的结合。