在python中装饰器的使用

一、前言

参考:https://pythonhowto.readthedocs.io/zh-cn/latest/decorator.html#id19
https://liaoxuefeng.com/books/python/functional/decorator/index.html

二、基本知识

2.1 闭包

闭包是嵌套函数,内部函数可以访问外部函数作用域的变量,他有两个显著特点:

  1. 嵌套函数
  2. 内部函数引用了外部函数作用域变量,并非全局变量
def outer():
    message = "Hello"
    def inner():  # 内部函数
        print(message)  # 访问外部变量
    return inner

closure = outer()
closure()  # 输出: Hello

2.2 装饰器

在python中,装饰器是一种增强现有函数行为的工具,在不修改原函数代码的前提下为函数动态增加新功能。

装饰器主要是利用了函数是一等公民,可以赋值给变量、作为参数传递或作为返回值的这一特性。

装饰器的工作原理:遇到类似@ decorator 语法时,会将被装饰的函数作为参数传递给装饰器函数,装饰器函数会返回一个新的函数来代替原始函数。

三、内置装饰器

3.1 @property

将方法转换为属性,实现安全访问和计算属性。如果不设置setter方法就是可读属性

class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        return self._radius  # 直接通过 obj.radius 访问
    
    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError("半径不能为负")
        self._radius = value

c = Circle(5)
print(c.radius)  # 输出: 5 (直接像属性一样调用)
c.radius = 10    # 触发 setter 方法

3.3 @classmethod

定义类方法,第一个参数为类本身cls,无需创建实例即可通过类直接调用

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    @classmethod
    def from_string(cls, date_str):  
        year, month, day = map(int, date_str.split("-"))
        return cls(year, month, day)

date = Date.from_string("2023-10-01")  # 通过类方法创建实例

3.4 @staticmethod

定义静态方法,无需实例或类参数

class MathUtils:
    @staticmethod
    def add(a, b):
        return a + b

print(MathUtils.add(2, 3))  # 输出: 5 (直接通过类调用)

四、自定义装饰器

4.1 装饰器函数

4.1.1 无参装饰器

函数不带参数时可使用下面方式:

def my_decorator(func):  # 被装饰的函数
    def wrapper():
        print("Before function call")
        func()
        print("After function call")
    return wrapper

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

say_hello()
# 输出:
# Before function call
# Hello!
# After function call

除了使用@语法糖,也可以手动装饰:

# 手动装饰
def say_hello():
    print("Hello!")

decorated_func = my_decorator(say_hello)
decorated_func()

处理带参数的函数时可以使用*arg*kwargs来接受任意参数

def decorator(func):    # 被装饰的函数
    def wrapper(*args, **kwargs):   # 函数参数
        print("Decorator work")
        return func(*args, **kwargs)
    return wrapper

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

print(add(2, 3))  # 输出: Decorator work → 5

4.1.2 含参装饰器

如果要在装饰器传参,需要三层嵌套函数:

def repeat(n_times):  # 装饰器参数
    def decorator(func):  # 被装饰的函数
        def wrapper(*args, **kwargs):  # 函数参数
            for _ in range(n_times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(n_times=3)
def greet(name):
    print(f"Hello {name}")

greet("Alice")
# 输出 3 次: Hello Alice

4.1.3 类方法装饰器

类方法的函数装饰器和函数的函数装饰器类似

def decorator(func):    
    def wrapper(*args, **kwargs):
        print("Decorator work")
        return func(*args, **kwargs)
    return wrapper

class TestDecorator():
    @decorator
    def add(self,a, b):
        return a + b

obj=TestDecorator()
print(obj.add(2, 3))  
# Decorator work
# 5

4.2 类装饰器

4.2.1 无参装饰器

类装饰器使用__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.calls} of {self.func.__name__}")
        return self.func(*args, **kwargs)

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

say_hello()  # 输出: Call 1 of say_hello → Hello!
say_hello()  # 输出: Call 2 of say_hello → Hello!

4.2.2 含参装饰器

装饰器类的参数需要通过__init__方法传递,所以被装饰的函数就只能在__call__方法中传入,为了把函数的参数传入,就在__call__方法中再封装一层

class CountCalls:
    def __init__(self, arg0):
        self.arg0 = arg0
        self.calls = 0

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            self.calls += 1
            print(f"Call {self.calls} of {func.__name__}, {self.arg0}")
            return func(*args, **kwargs)
        return wrapper

@CountCalls(arg0=0)
def say_hello():
    print("Hello!")

say_hello()  # 输出: Call 1 of say_hello, 0 → Hello!
say_hello()  # 输出: Call 2 of say_hello, 0 → Hello!

4.3 保留原函数信息

装饰器会覆盖原函数的元信息,需要使用functools.wraps来保留:

from functools import wraps

def decorator(func):
    @wraps(func)  # 关键点
    def wrapper(*args, **kwargs):
        """Wrapper docstring"""
        print("Decorator action")
        return func(*args, **kwargs)
    return wrapper

@decorator
def original():
    """Original docstring"""
    pass

print(original.__name__)  # 输出: original
print(original.__doc__)   # 输出: Original docstring

4.4. 多个装饰器

使用多个装饰器时,执行顺序按靠近函数的顺序依次执行

@decorator1    # 后执行
@decorator2    # 先执行
def my_func():
    pass
# 等价于: decorator1(decorator2(my_func))

五、实践

日志记录:

from functools import wraps

def log_execution(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Executing {func.__name__}...")
        result = func(*args, **kwargs)
        print(f"Finished executing {func.__name__}")
        return result
    return wrapper

@log_execution
def hello(name):
    print(f"hello {name}")

hello("Lucy")
# Executing hello...
# hello Lucy
# Finished executing hello

重试机制:

import time
from functools import wraps

def retry(max_attempts=3, delay=1):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            attempts = 0
            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"尝试 {attempts+1}/{max_attempts} 失败: {e}")
                    attempts += 1
                    time.sleep(delay)
            raise RuntimeError("所有重试均失败")
        return wrapper
    return decorator

@retry(max_attempts=2)
def call_unstable_api():
    import random
    if random.random() < 0.8:
        raise ConnectionError("API 不可用")
    return "成功"

call_unstable_api()  # 最多重试 2 次
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值