Skip to main content

Python 装饰器 Decorator

· 4 min read
DDdl5A

https://www.bilibili.com/video/BV1Ah8jeQEeV

预备知识

  1. Python 中绝大多数元素都是对象 Object
  2. 类和函数也不例外,变量名都可以被重新赋值
def hello():
print('Hello')

def world():
print('World')

# 可以给 hello 赋值成另外一个函数 world
hello = world
hello()

# 甚至可以给 hello 赋值成 1
hello = 1

装饰器的形成

hello 赋值成 world1 显然没有太多实际意义,我们能不能利用函数变量可以再被复制特性,做点更有意义的事情呢?Python 的设计师们想到的是对 hello 进行一些包装,增加一些 hello 原本没有的功能,比如记录函数运行的时间:

def timing(f):
return f

def hello():
print('Hello, World')

hello = timing(hello)
hello()

上面这样写等于什么都没有做,hello -> f -> hello, 要想扩展 hello 函数的功能,得延迟对 f 的调用,那就需要一个 wrapper 函数去包裹 f,同时也就形成了一个闭包:

import time

def timing(f):
def wrapper():
return f()
return wrapper

def hello():
print('Hello, World')

# hello 函数指向 wrapper 函数
hello = timing(hello)
hello()

有了 wrapper 函数,我们就可以来实现 timing 函数的真正功能了:

import time

def timing(f):
def wrapper():
start_time = time.time()
result = f() # 将 f 的返回值先存起来,最后再 return 出来,这样看上去 timing 函数并没有影响 hello 函数原本的返回值
print(f'Total time: {time.time() - start_time}')
return result
return wrapper

def hello():
print('Hello, World')

hello = timing(hello)
hello()

所以,Python 针对这种情况,推出了一个语法糖 @:

import time

def timing(f):
def wrapper():
start_time = time.time()
# 将 f 的返回值先存起来,最后再 return 出来
# 这样看上去 timing 函数并没有影响 hello 函数原本的返回值
result = f()
print(f'Total time: {time.time() - start_time}')
return result
return wrapper

@timing # 等同于 hello = timing(hello)
def hello():
print('Hello, World')

hello()

支持参数

如何让 timing 装饰器支持有参数的函数?

import time

def timing(f):
def wrapper(*args, **kwargs): # 用可变参数和关键字参数,让 wrapper 支持所有参数组合
start_time = time.time()
result = f(*args, **kwargs)
print(f'Total time: {time.time() - start_time}')
return result
return wrapper

@timing
def hello():
print('Hello, World')

hello()

def greet(name):
print(f'Hello {name}')

greet('kimi')

装饰类

import time

def timing(f):
def wrapper(*args, **kwargs): # 用可变参数和关键字参数,让 wrapper 支持所有参数组合
start_time = time.time()
result = f(*args, **kwargs)
print(f'Total time: {time.time() - start_time}')
return result
return wrapper

@timing # 等同于 Foo = timing(Foo)
class Foo:
def __init__(self):
pass

Foo()