python中的装饰器

python中的对象

1
2
3
4
5
6
7
8
9
10
11
12
def apple():
print("there is an apple")

# 1. 函数对象
apple

# 2.执行apple函数
apple()

# 3.将变量obj指向apple对象,执行apple函数
obj = apple
obj()

注意这里apple 和 apple() 的区别

  1. apple(函数对象)
    定义 :apple 是函数对象的引用。
  • 在 Python 中,函数是第一类对象(First-class object),意味着它们可以被赋值给变量、作为参数传递、作为返回值返回等。
  • apple 本质上是一个指向函数对象的引用,它存储了函数的定义(包括函数名、参数列表、函数体等信息)。
  • 调用 type(apple) 返回 <class ‘function’>,说明 apple 是一个函数类型的对象。
  1. apple()(函数调用)
    定义 :apple() 表示调用 apple 函数。
  • 当在函数名后加上括号 () 时,Python 解释器会执行该函数的代码块。
    函数调用的过程包括:
    1. 创建一个新的作用域(局部命名空间)。
    2. 执行函数体中的代码。
    3. 如果函数中有 return 语句,则返回指定的值;否则默认返回 None。
    4. 在这个例子中,apple() 执行了函数体中的 print(“there is an apple”),因此打印了消息。

定义一个log函数能够记录日志

  • 直接在log函数中打印日志,并且执行传入的函数func
  • 但这样会将原来直接执行apple()改为需要执行log(apple)
1
2
3
4
5
6
7
8
def apple():
print("there is an apple")

def log(func):
print("this is log")
func()

log(apple)

保证继续apple()

1
2
3
4
5
6
7
8
9
def apple():
print("there is an apple")

def log(func):
print("this is log")
func()

apple = log(apple)
apple()

注意这里log(apple)是函数调用,会去执行log函数,由于该函数没有返回值,log(apple)返回为None,也就是appple = None, 再次执行appple()则会报错。

为了解决这个问题,正确的写法应该是在log中返回一个函数,而不是去执行该函数。

1
2
3
4
5
6
7
8
9
10

def apple():
print("there is an apple")

def log(func):
print("this is log")
return func

apple = log(apple)
apple()

假如apple函数需要传入参数

1
2
3
4
5
6
7
8
9
10
11
12
def apple(count):
if count == 1:
print("there is an apple")
elif count > 1:
print(f"there are {count} apples")

def log(func):
print("this is log")
return func

apple = log(apple(count))
apple()

按照之前的逻辑,直接像上面这样写肯定不行,因为apple(count)会直接执行该函数,那么log(apple(count))中log函数的参数便不再是函数了,而是apple(count)的结果。

同样的逻辑,需要将保证apple为一个函数,且需要能接受参数,则在原来的log里面先定义一个能接受任意参数的help函数,将它返回。

help函数里面应该写什么呢?

首先它需要记录log,并且根据传入的参数直接将其作为func函数的参数,并返回。当然在这个例子中,这里不返回直接执行也是可以的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def apple(count):
if count == 1:
print("there is an apple")
elif count > 1:
print(f"there are {count} apples")

def log(func):
def help(*arg, **args):
print("this is log")
return func(*arg, **args)
# func(*arg, **args) 在这个例子中直接执行也可以
return help

apple = log(apple)
apple(5)

装饰器

为了简化apple = log(apple)这个操作,python给出了一个简单的语法规则“@”将该步骤自动化,也就是“装饰器”。

注意这里装饰器需要先定义再使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def log(func):
def help(*arg, **args):
print("this is log")
return func(*arg, **args)
return help

@log
def apple(count):
if count == 1:
print("there is an apple")
elif count > 1:
print(f"there are {count} apples")

# apple = log(apple)

apple(5)

装饰器参数

如果装饰器也需要添加参数怎么办呢。那么在外层再封装一层函数即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def log(level):
def log_decorator(func):
def help(*arg, **args):
print(f"Logging level: {level}")
print("this is log")
return func(*arg, **args)
return help
return log_decorator

@log("info")
def apple(count):
if count == 1:
print("there is an apple")
elif count > 1:
print(f"there are {count} apples")

# apple = log(apple)

apple(5)

类装饰器

python基础

:::success
类装饰器本质是通过类的魔术方法__call__来实现上述函数装饰器的类似功能,但使用类装饰器最大的好处是可以保存实例对象(注意不是类)的历史数据。

:::

  • 类装饰器:类装饰器通过定义一个类来实现装饰器功能。通常需要实现 __init____call__ 方法:
    • __init__:初始化装饰器,接收被装饰的函数作为参数。
    • __call__:使类的实例可以像函数一样被调用,从而实现对目标函数的包装。

类装饰器的基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Decorator:
def __init__(self, func):
self.func = func # 保存被装饰的函数

def __call__(self, *args, **kwargs):
# 在调用原函数之前或之后添加额外逻辑
print("Before function call")
result = self.func(*args, **kwargs)
print("After function call")
return result

@Decorator
def my_function():
print("Function executed")

my_function()

输出:

1
2
3
Before function call
Function executed
After function call

类装饰器的工作原理

  1. 装饰器的执行时机
    • 当使用 @Decorator 装饰函数时,Python 会将被装饰的函数传递给装饰器类的构造函数(__init__)。
    • 被装饰的函数会被保存为类的实例属性(如 self.func)。
  2. 调用被装饰的函数
    • 当调用被装饰的函数时,实际上是调用了装饰器类的 __call__ 方法。
    • __call__ 方法中,可以执行额外的逻辑,然后调用原始函数。

类装饰器的应用场景

类装饰器适用于以下场景:

  1. 需要维护状态的装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class CountCalls:
def __init__(self, func):
self.func = func
self.call_count = 0 # 维护调用次数

def __call__(self, *args, **kwargs):
self.call_count += 1
print(f"Call count: {self.call_count}")
return self.func(*args, **kwargs)

@CountCalls
def say_hello():
print("Hello!")

say_hello() # 输出: Call count: 1 \n Hello!
say_hello() # 输出: Call count: 2 \n Hello!
- 类装饰器可以通过实例属性存储状态,而函数装饰器通常需要通过闭包实现类似功能。
  1. 复杂的装饰逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class LoggingDecorator:
def __init__(self, func):
self.func = func

def log(self, message):
print(f"[LOG] {message}")

def __call__(self, *args, **kwargs):
self.log("Before function call")
result = self.func(*args, **kwargs)
self.log("After function call")
return result

@LoggingDecorator
def add(a, b):
return a + b

print(add(3, 5)) # 输出: [LOG] Before function call \n [LOG] After function call \n 8
- 如果装饰器需要处理复杂的逻辑,类装饰器可以通过方法分解逻辑,使代码更清晰。
  1. 参数化的装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Repeat:
def __init__(self, times):
self.times = times # 装饰器参数

def __call__(self, func):
def wrapper(*args, **kwargs):
for _ in range(self.times):
func(*args, **kwargs)
return wrapper

@Repeat(times=3)
def greet(name):
print(f"Hello, {name}!")

greet("Alice")
# 输出:
# Hello, Alice!
# Hello, Alice!
# Hello, Alice!

和函数的过程类似。需要再外面再封装一层。实现过程为

  1. 实例化类对象,传入times参数为3
  2. 调用__call__方法,传入func函数

类装饰器与函数装饰器的对比

特性 类装饰器 函数装饰器
实现方式 使用类和 __call__ 方法 使用嵌套函数
状态管理 可以通过实例属性轻松管理状态 需要通过闭包管理状态
复杂逻辑分解 可以通过类的方法分解复杂逻辑 所有逻辑通常集中在一个函数中
适用场景 需要维护状态或逻辑较复杂的装饰器 简单装饰器

注意事项

  1. 确保实现 __call__ 方法
    • 类装饰器必须实现 __call__ 方法,否则无法像函数一样调用。
  2. 避免不必要的实例化
    • 如果装饰器不需要维护状态,函数装饰器可能更简单高效。
  3. 兼容性
    • 类装饰器和函数装饰器在功能上是等价的,选择哪种方式取决于具体需求。

//