1. 介绍

Python中,装饰器是一种高级语言特性,它可以用于修改现有函数或类的行为,在不改动这个函数的前提下,扩展这个函数的功能。比如计算函数耗时、给函数加缓存等。

2. 内置装饰器

2.1 @classmethod

Python不像其他语言,可以在类中通过关键字声明静态方法,而是通过装饰器@classmethod来实现,具体实例如下:

# -----------------------类文件:demo/myClass.py ----------
class Book:
bookName: str = "西游记"

# 使用装饰器
@classmethod
def echoBookName(cls):
print("书名: {} ".format(cls.bookName))

# ------------------------- 运行 -------------------------
from demo.myClass import Book

if __name__ == '__main__':
Book.echoBookName()

# ------------------------- 输出 -------------------------
书名: 西游记

@注: 带装饰类的方法,会隐式的将类当做第一个参数,传递给方法,调用时无须实例化。

2.2 @staticmethod

@staticmethod也是代表的静态方法,与@classmethod区别是,它和类没有绑定,不能像上个示例一样直接访问类的属性:

# -----------------------类文件:demo/myClass.py ----------
class Book:
...
@staticmethod
def echoPrice():
print("书的价格: 32.89 ")

# ------------------------- 运行 -------------------------
from demo.myClass import Book

if __name__ == '__main__':
Book.echoPrice()

# ------------------------- 输出 -------------------------
书的价格: 32.89

下面演示,尝试访问类属性:

# -----------------------类文件:demo/myClass.py ----------
class Book:
...
# 这里使用的非类静态方法装饰
@staticmethod
def echoBookName2(cls):
print("书名: {} ".format(cls.bookName))

# ------------------------- 运行 -------------------------
from demo.myClass import Book

if __name__ == '__main__':
Book.echoBookName2()

# ------------------------- 输出 -------------------------
Traceback (most recent call last):
File "/Users/liuqh/ProjectItem/PythonItem/fast-use-ai/test/localTest.py", line 47, in <module>
Book.echoBookName2()
TypeError: Book.echoBookName2() missing 1 required positional argument: 'cls'

2.3 @property

这个装饰器的功能,可以让我们像访问类属性一样,访问方法。比如访问stu.info(),可以写成stu.info,而不用带括号。如下示例:

# -----------------------类文件:demo/myClass.py ----------
class Student:
name: str = ""
age: int = 18

def __init__(self, name: str, age: int):
self.name = name
self.age = age

@property
def info(self):
return "Student name:{} age:{}".format(self.name, self.age)

# ------------------------- 运行 -------------------------
from demo.myClass import Student

if __name__ == '__main__':
stu = Student("张三", 20)
print("stu.info: ", stu.info)

# ------------------------- 输出 -------------------------
stu.info: Student name:张三 age:20

@property也可以和deleter、getter、setter结合一起使用,下面是示例是和setter结合,对私有属性的封装;

# -----------------------类文件:demo/myClass.py ----------
class Account:
__money: float = 0.00

@property
def money(self):
return self.__money

# 设置金额
@money.setter
def money(self, money: float):
self.__money = money

# ------------------------- 运行 -------------------------
from demo.myClass import Account

if __name__ == '__main__':
a = Account()
print("设置前-金额: ", a.money)
# 设置金额
a.money = 100.99
print("设置后-金额: ", a.money)

# ------------------------- 输出 -------------------------
设置前-金额: 0.0
设置后-金额: 100.99

# ------------------------- 不设置@money.setter时 -------------------------
Traceback (most recent call last):
File "/Users/liuqh/ProjectItem/PythonItem/fast-use-ai/test/localTest.py", line 50, in <module>
a.money = 100.99
^^^^^^^
AttributeError: property 'money' of 'Account' object has no setter

2.4 @wrapper

当使用装饰器之后,原函数的一些信息会被装饰器函数覆盖,为了解决这类问题,可以使用@wrapper来修复这个问题,@wrapper在模块functools下,用之前先导入.

不使用@wrapper示例:

# ------------------------- 函数定义 -------------------------  
# 定义装饰器
def wrapperDemo(func):
def execFunc(*args, **kw):
res = func(*args, **kw)
return res

return execFunc


# 使用函数装饰器
@wrapperDemo
def test():
print("test func run ok")


# ------------------------- 运行 -------------------------
if __name__ == '__main__':
print("执行函数名称: ", test.__name__)

# ------------------------- 输出-------------------------
执行函数名称: execFunc

通过上面示例可以看出,虽然我们打印的是test.__name__,希望输出的是test,结果却是:execFunc

使用@wrapper示例:

只需要改写装饰器函数,加上@functools.wraps(func)

# ------------------------- 函数定义 -------------------------  
# 先导入这个模块
import functools
# 修改装饰器
def noWrapper(func):
## 这里加上wraps
@functools.wraps(func)
def execFunc(*args, **kw):
res = func(*args, **kw)
return res

return execFunc

...
# ------------------------- 输出-------------------------
执行函数名称: test

3. 自定义装饰器(无参)

3.1 定义语法

# func只被装饰的函数
def 装饰器名称(func):
# 加上wraps,防止装饰器副作用
@functools.wraps(func)
# tmpFunc是临时函数名,可以自定义
def tmpFunc(*args, **kw):
# 函数执行前处理......
res = func(*args, **kw)
# 函数执行后处理......
return res

return tmpFunc

3.2 使用示例

# ------------------------- 函数定义 ------------------------- 
# 定义函数装饰器
def useTime(func):
@functools.wraps(func)
def execFunc(*args, **kw):
# 定义开始时间
beginTime = time.time()
print("函数执行前: ", beginTime)
res = func(*args, **kw)
# 计算耗时
ut = time.time() - beginTime
print("函数耗时: %s 秒" % int(ut))
return res

return execFunc


# 使用函数装饰器
@useTime
def test():
time.sleep(3)
print("test func run ok")

# ------------------------- 运行 -------------------------
if __name__ == '__main__':
test()

# ------------------------- 输出 -------------------------
函数执行前: 1692287796.785186
test func run ok
函数耗时: 3

4.自定义装饰器(有参)

4.1 定义语法

def 装饰器名称(arg):
# ---下面这层,可以理解是整个无参数层---
def tmpfunc(func):
@functools.wraps(func)
def execFunc(*args, **kw):
res = func(*args, **kw)
return newRes
return execFunc

return tmpfunc

4.2 使用示例

# ------------------------- 函数定义 ------------------------- 
def haveArgs(arg):
def tmp(func):
@functools.wraps(func)
def execFunc(*args, **kw):
res = func(*args, **kw)
newRes = "arg:{} | {}".format(arg, res)
return newRes

return execFunc

return tmp

# 使用函数装饰器
@haveArgs("hello world")
def test():
return "test func run ok"

# ------------------------- 运行 -------------------------
if __name__ == '__main__':
r = test()
print("r: ", r)

# ------------------------- 输出 -------------------------
r: arg:hello world | test func run ok

5. 自定义类装饰器

# ------------------------- 定义装饰器:demo/cacheClass.py  ------------------------- 

import functools

class Cache:
@classmethod
def setCache(cls, cacheKey):
def tmpFunc(func):
@functools.wraps(func)
def do(*args, **kw):
res = func(*args, **kw)
print("设置缓存>>>> key:{} value:{}".format(cacheKey, res))
return res

return do

return tmpFunc


# ------------------------- 运行 -------------------------
from demo.cacheClass import Cache

# 使用函数装饰器
@Cache.setCache(cacheKey="add_key_compute")
def compute(a: int, b: int) -> int:
return a + b

if __name__ == '__main__':
r = compute(1, 9)
print("r: ", r)


# ------------------------- 输出 -------------------------
设置缓存>>>> key:add_key_compute value:10
r: 10