1. 什么是Python装饰器
Python装饰器(Decorator)是Python语言中的一种特殊语法,可以修改函数或类的行为。装饰器本身是一个函数,它可以接受一个函数作为参数,并返回一个新函数。在新函数调用原函数之前或之后,装饰器会添加一些额外的功能。Python装饰器与Java、C++等语言中的装饰器并不相同,它是基于函数式编程的思想实现的,可以理解为“给函数贴标签”。
1.1 装饰器的作用
Python装饰器的作用非常广泛,可以用于:
统计函数执行时间、函数调用次数等
缓存函数执行结果,避免重复计算
检查函数执行的前提条件、后续操作等
添加日志、异常处理等功能
扩展函数功能,如加上权限认证、方法重载等
1.2 Python装饰器的基本语法
Python装饰器的基本语法如下:
@decorator
def func():
pass
其中“@decorator”是装饰器的名称,它可以是函数或类,也可以是带参数的函数。
2. Python装饰器的实现
2.1 装饰器修饰无参数函数
下面我们来看一个简单的装饰器实现,它可以用于输出函数调用的时间:
import time
def log_time(func):
def wrapper():
start = time.time()
func()
end = time.time()
print(f'function {func.__name__} takes {end - start:.6f}s')
return wrapper
@log_time
def hello():
print('hello world')
hello()
运行结果如下:
hello world
function hello takes 0.000000s
可以看到,程序先执行了“hello()”函数,然后又执行了装饰器函数“wrapper()”,最后在“wrapper()”函数中输出了函数执行的时间。
这个装饰器的实现中,“log_time()”函数接受一个函数作为参数,返回一个新函数“wrapper()”,在“wrapper()”函数中先记录函数开始执行的时间“start”,然后调用原函数“func()”,最后记录函数结束执行的时间“end”,并计算出执行时间,输出到控制台。装饰器函数“log_time()”可以接受任何没有参数的函数作为参数,比如“hello()”函数。
2.2 装饰器修饰有参数函数
如果原函数带有参数,那么装饰器需要做一些特殊处理才能正确地传递参数。下面我们来看一个修饰有参数函数的装饰器实现:
import time
def log_time(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f'function {func.__name__} takes {end - start:.6f}s')
return result
return wrapper
@log_time
def say_hello(name):
time.sleep(1) # 模拟函数执行过程
print(f'hello, {name}')
say_hello('Tom')
运行结果如下:
hello, Tom
function say_hello takes 1.000271s
这个装饰器函数中多了两个参数“*args”和“**kwargs”,它们是Python中的特殊语法,分别表示任意数量的位置参数和关键字参数。在新函数“wrapper()”中,我们先接收所有的参数,然后调用原函数时用“*args”和“**kwargs”展开参数列表。这样就可以正确地处理原函数的参数了。
2.3 带参数的装饰器
有些装饰器本身需要接收参数,比如一个用于检查用户权限的装饰器,需要指定哪些用户有权限执行该函数。这时可以再套一个闭包,让装饰器函数返回一个新的装饰器函数,这个新函数才是真正的装饰器。下面我们来看一个带参数的装饰器实现:
def check_permission(username):
def decorator(func):
def wrapper(*args, **kwargs):
if username == 'admin':
result = func(*args, **kwargs)
return result
else:
raise Exception('Permission denied')
return wrapper
return decorator
@check_permission(username='admin')
def show_secret():
print('42 is the answer')
show_secret()
运行结果如下:
42 is the answer
这个装饰器函数中定义了三层函数,第一层“check_permission()”函数返回一个新的装饰器函数“decorator()”,它接收一个函数作为参数,返回一个新函数“wrapper()”作为装饰器。在新函数“wrapper()”中,首先检查输入的用户名是否为“admin”,如果是,则继续执行原函数“func()”,否则抛出权限错误。使用时,需要在装饰器前面指定参数“username”,比如“@check_permission(username='admin')”。
2.4 类装饰器
除了函数外,Python中还支持使用类作为装饰器,只需要定义一个带有“__call__()”方法的类即可。这个方法接收一个函数作为参数,并返回一个新函数。下面我们来看一个类装饰器实现:
class CountCalls:
def __init__(self, func):
self.func = func
self.calls = 0
def __call__(self, *args, **kwargs):
self.calls += 1
print(f'call {self.func.__name__} {self.calls} times')
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print('hello world')
say_hello()
say_hello()
运行结果如下:
call say_hello 1 times
hello world
call say_hello 2 times
hello world
这个类定义了“__init__()”和“__call__()”两个方法,“__init__()”方法接收一个函数作为参数,并将其保存为实例变量。“__call__()”方法接收函数的参数,调用原函数“self.func()”,并输出函数调用次数。这个类装饰器在“say_hello()”函数被调用时,会自动记录函数的调用次数并输出到控制台。
3. Python装饰器的高级应用
3.1 缓存函数执行结果
有些函数计算量比较大,调用频繁,为了提高性能,可以将计算结果缓存起来,下次调用时直接返回缓存结果。下面我们来看一个缓存函数执行结果的装饰器实现:
def memoize(func):
memo = {}
def wrapper(*args):
if args in memo:
print('get result from cache')
return memo[args]
else:
print('execute function')
result = func(*args)
memo[args] = result
return result
return wrapper
@memoize
def fibonacci(n):
if n < 2:
return n
else:
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10))
print(fibonacci(11))
运行结果如下:
execute function
55
execute function
get result from cache
89
这个缓存装饰器函数中定义了一个字典“memo”作为缓存,它保存函数的输入输出结果。在新函数“wrapper()”中,我们首先检查输入参数是否存在缓存中,如果存在,则直接返回缓存结果,否则调用原函数计算结果,并保存到缓存中。该装饰器使用时只需要在原函数前面添加“@memoize”即可。
3.2 检查函数参数类型
有时为了保证代码质量和安全性,需要检查函数的输入参数类型是否符合预期。下面我们来看一个检查函数参数类型的装饰器实现:
def check_type(*types):
def decorator(func):
def wrapper(*args, **kwargs):
for i, arg in enumerate(args):
if type(arg) != types[i]:
raise TypeError(f'Wrong argument type for {func.__name__}: {arg}')
for key, value in kwargs.items():
if type(value) != types[len(args) + list(kwargs.keys()).index(key)]:
raise TypeError(f'Wrong argument type for {func.__name__}: {value}')
return func(*args, **kwargs)
return wrapper
return decorator
@check_type(str, int, bool)
def my_func(name, age, has_job):
print(f'{name} is {age} years old, and {"has" if has_job else "has not"} a job')
my_func('Tom', 30, True)
my_func('Jerry', '18', False)
运行结果如下:
Tom is 30 years old, and has a job
Traceback (most recent call last):
File "decorators.py", line 22, in <module>
my_func('Jerry', '18', False)
File "decorators.py", line 8, in wrapper
raise TypeError(f'Wrong argument type for {func.__name__}: {arg}')
TypeError: Wrong argument type for my_func: 18
这个装饰器函数中定义了两层函数,“check_type()”函数返回一个装饰器函数“decorator()”,它接收一个函数作为参数,返回一个新函数“wrapper()”作为装饰器。在新函数“wrapper()”中,我们首先遍历所有的输入参数,并检查参数类型是否与指定类型相同,如果不相同,则抛出类型错误异常。该装饰器使用时需要指定所有参数的类型,比如“@check_type(str, int, bool)”。
3.3 线程安全的单例模式
线程安全的单例模式是一种经典的设计模式,在多线程环境下保证只会有一个实例对象被创建,从而避免了线程安全性问题。下面我们来看一个线程安全的单例模式装饰器实现:
import threading
def synchronized(func):
func.lock = threading.Lock()
def wrapper(*args, **kwargs):
with func.lock:
if not hasattr(func, 'instance'):
func.instance = func(*args, **kwargs)
return func.instance
return wrapper
@synchronized
class Singleton:
def __init__(self, name):
self.name = name
def say_hello(self):
print(f'hello, I am {self.name}')
instance1 = Singleton('Tom')
instance2 = Singleton('Jerry')
print(instance1 is instance2)
instance1.say_hello()
运行结果如下:
True
hello, I am Tom
这个装饰器函数中定义了两层函数,“synchronized()”函数返回一个装饰器函数“wrapper()”,它接收一个类作为参数,并返回一个新类作为装饰器。在新类中,我们使用“hasattr()”方法检查是否已经创建实例对象,并加锁保证线程安全。如果实例对象不存在,则调用原来的“__init__()”方法创建新的实例对象,否则直接返回已有的实例对象。使用该装饰器时只需要在类前面添加“@synchronized”即可。
4. 总结
Python装饰器是一种强大的编程工具,它可以让我们在不改变原有代码的基础上,为函数或类增加一些新的功能。使用装饰器可以提高代码的复用性、可维护性和可读性,也可以让程序更高效、更健壮、更安全、更易于扩展。但是,装饰器也有一些缺点,比如会使程序过于复杂、运行速度变慢、调试困难等,应该尽量避免过度使用。应该了解Python装饰器的基本语法和常用技巧,掌握装饰器的灵活应用,才能更好地运用装饰器来提升程序的质量和效率。