通俗讲解python 装饰器

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装饰器的基本语法和常用技巧,掌握装饰器的灵活应用,才能更好地运用装饰器来提升程序的质量和效率。

免责声明:本文来自互联网,本站所有信息(包括但不限于文字、视频、音频、数据及图表),不保证该信息的准确性、真实性、完整性、有效性、及时性、原创性等,版权归属于原作者,如无意侵犯媒体或个人知识产权,请来电或致函告之,本站将在第一时间处理。猿码集站发布此文目的在于促进信息交流,此文观点与本站立场无关,不承担任何责任。

后端开发标签