Python-装饰器(Decorator)详解

在python中,函数是一等公民,意味着函数可以像其他对象一样被赋值、传递参数、作为返回值等。装饰器的基本语法是使用@符号将一个函数作为参数传递给另一个函数(即装饰器)。被装饰的函数在被调用时,实际上会执行装饰器函数返回的新函数‌。简单来说,装饰器就是一个返回函数的函数。使用简洁,直接在函数定义上方加上 @decorator 语法糖。

1装饰器的基本概念

装饰器是为了解耦代码的逻辑、提高代码的复用性,并且让函数和类的行为在不修改其原始代码的前提下得以扩展。能有效地将某些附加行为抽象出来,使得主业务逻辑(例如函数本身)保持简洁和专注。装饰器本质上是一个接受函数作为参数并返回一个新函数的函数。它常用于:

  • 在函数执行前后添加额外的行为。
  • 修改函数的输入或输出。
  • 为函数增加一些元数据。

装饰器的应用场景非常广泛,例如:日志记录、权限检查、性能计时、缓存等。装饰器的引入可以解决以下几个问题:

代码复用:装饰器可以将某些跨多个函数或类的功能(如日志、权限校验、缓存等)提取出来,以便于复用,避免重复代码。

增强代码的可维护性:装饰器使得附加功能与主功能分离,从而使代码更清晰,容易理解与维护。通过装饰器,修改或增加功能时无需修改原有函数或类的实现。

关注点分离(Separation of Concerns):装饰器让你可以将不同的功能分开处理,将与业务逻辑无关的操作(如日志记录、性能监控等)从核心业务代码中剥离出来。

灵活性和扩展性:使用装饰器可以灵活地动态修改函数或方法的行为,而无需修改函数本身的代码。

2装饰器的使用

2.1 基本结构

最简单的装饰器形式如下:

def decorator(func):
    def wrapper():

        print("Before function call")

        func()  # 调用原函数

        print("After function call")

    return wrapper

# 使用装饰器
@decorator
def say_hello():
    print("Hello!")

# 调用装饰后的函数
say_hello()

解释:

  • decorator 是一个装饰器函数,它接收一个函数 func 作为参数。
  • wrapper 是一个新的函数,它在调用原函数 func() 之前和之后添加了自定义的行为。
  • @decorator 语法实际上是 say_hello = decorator(say_hello),即用装饰器修改 say_hello 函数。

输出:

Before function call

Hello!

After function call

2.2 带参数的装饰器

装饰器可以接受参数。为了让装饰器支持带参数的函数,我们需要在 wrapper 函数中使用 *args 和 **kwargs 来传递参数。

示例:

def decorator(func):
    def wrapper(*args, **kwargs):

        print("Before function call")

        result = func(*args, **kwargs)

        print("After function call")

        return result

    return wrapper

@decorator
def add(a, b):

    return a + b

result = add(3, 5)

print("Result:", result)

解释:

  • wrapper 使用 *args 和 **kwargs 传递给被装饰函数,这样它就可以处理带有任意参数的函数。
  • 在 wrapper 中,原函数 func(*args, **kwargs) 被调用,并且它的结果被返回。

输出:

Before function call

After function callResult: 8

2.3 装饰器的嵌套

装饰器可以嵌套使用,即一个函数可以同时被多个装饰器修饰。

示例:多个装饰器

def decorator1(func):
    def wrapper(*args, **kwargs):

        print("Decorator 1 - Before function call")

        result = func(*args, **kwargs)

        print("Decorator 1 - After function call")

        return result

    return wrapper

def decorator2(func):
    def wrapper(*args, **kwargs):

        print("Decorator 2 - Before function call")

        result = func(*args, **kwargs)

        print("Decorator 2 - After function call")

        return result

    return wrapper

@decorator1
@decorator2
def say_hello():

    print("Hello!")

say_hello()

解释:

  • 装饰器的顺序是从下到上的,即 @decorator2 首先应用,然后是 @decorator1。
  • 调用 say_hello() 时,会先执行 decorator2,然后是 decorator1。

输出:

Decorator 2 - Before function call

Decorator 1 - Before function call

Hello!

Decorator 1 - After function call

Decorator 2 - After function call

2.4 使用 functools.wraps 保持原函数的元数据

当我们使用装饰器时,装饰器内部的 wrapper 函数会替代原始函数。这意味着原函数的名称、文档字符串(docstring)等信息会丢失。为了避免这种情况,可以使用 functools.wraps 来保持原函数的元数据。

示例:使用 functools.wraps

import functools

def decorator(func):    
    @functools.wraps(func)
    def wrapper(*args, **kwargs):

        print("Before function call")

        result = func(*args, **kwargs)

        print("After function call")

        return result

    return wrapper

@decorator
def say_hello():
    """This is the say_hello function."""

    print("Hello!")

print(say_hello.__name__)  # 输出:say_hello

print(say_hello.__doc__)  # 输出:This is the say_hello function.

解释:

  • @functools.wraps(func) 保证了装饰器不改变原函数的名称、文档字符串等元数据。
  • 如果没有使用 wraps,则 say_hello.__name__ 会返回 wrapper,而不是 say_hello。

2.5 带参数的装饰器

有时我们可能希望为装饰器传递参数(例如动态调整装饰器的行为)。为了实现这一点,我们需要使用三层嵌套函数

示例:

def decorator_with_args(arg):

    def decorator(func):

        def wrapper(*args, **kwargs):

            print(f"Decorator argument: {arg}")

            return func(*args, **kwargs)

        return wrapper

    return decorator

@decorator_with_args("Hello")
def greet(name):

    print(f"Greetings, {name}!")

greet("Alice")

解释:

  • decorator_with_args 是一个接受参数的装饰器,它返回一个装饰器函数 decorator。
  • decorator 是实际的装饰器,它接受一个函数 func,并在 wrapper 函数中添加新的功能。
  • @decorator_with_args("Hello") 等价于 greet = decorator_with_args("Hello")(greet)。

输出:

Decorator argument: Hello

Greetings, Alice!

2.6 类方法和静态方法的装饰器

在类中使用装饰器时,需要注意装饰器的作用对象(类方法、实例方法、静态方法)。对于实例方法,装饰器会多一个 self 参数,对于类方法会有 cls 参数。

示例:装饰器作用于实例方法

class MyClass:
    def decorator(func):
        def wrapper(self, *args, **kwargs):

            print("Before calling instance method")

            result = func(self, *args, **kwargs)

            print("After calling instance method")

            return result

        return wrapper

    @decorator
    def greet(self, name):

        print(f"Hello, {name}!")

obj = MyClass()

obj.greet("Alice")

示例:装饰器作用于类方法和静态方法

class MyClass:    
    @staticmethod
    def decorator(func):

        def wrapper(*args, **kwargs):

            print("Before calling static method")

            result = func(*args, **kwargs)

            print("After calling static method")

            return result

        return wrapper

    @decorator    
    @staticmethod
    def greet(name):

        print(f"Hello, {name}!")

MyClass.greet("Alice")

3、python内置装饰器

常见的有:

  1. @staticmethod - 用于定义静态方法。
  2. @classmethod - 用于定义类方法。
  3. @property - 将方法转化为属性(getter)。
  4. @functools.lru_cache - 用于缓存函数结果,提升性能。
  5. @functools.wraps - 保留被装饰函数的元数据。
  6. @abstractmethod - 定义抽象方法。
  7. @property.setter - 为属性添加 setter 方法。
  8. @property.deleter - 为属性添加 deleter 方法。

3.1 @staticmethod

@staticmethod 装饰器用于将一个方法定义为静态方法。静态方法不需要访问实例 (self) 或类 (cls),它通常用于一些与类本身相关但不需要访问实例属性或方法的功能。

示例:

class MyClass:    

    @staticmethod
    def greet(name):

        print(f"Hello, {name}!")

# 静态方法不需要实例化类
MyClass.greet("Alice")

输出:

Hello, Alice!

3.2 @classmethod

@classmethod 装饰器用于将方法定义为类方法。类方法接收类本身作为第一个参数(通常命名为 cls),而不是实例。它通常用于操作类的属性或提供一些与类本身相关的功能。

示例:

class MyClass:

    count = 0

    @classmethod
    def increment_count(cls):

        cls.count += 1

        print(f"Count: {cls.count}")

MyClass.increment_count()  # 调用类方法

输出:

Count: 1

3.3 @property

@property 装饰器用于将方法变为属性,这意味着该方法的调用方式就像访问属性一样,而不需要显式调用方法。常用于定义只读属性,或者为属性添加 getter 和 setter 方法。

示例:

class Circle:

    def __init__(self, radius):

        self._radius = radius

    @property
    def radius(self):

        return self._radius

    @property
    def area(self):

        return 3.14 * self._radius ** 2

c = Circle(5)
print(c.radius)  # 直接访问属性
print(c.area)  # 直接访问属性

输出:

5

78.5

3.4 @functools.lru_cache

@lru_cache 是 functools 模块提供的一个装饰器,用于对函数的结果进行缓存。对于相同的参数,lru_cache 会返回缓存中的结果,从而避免重复计算,提升函数的效率。LRU 代表 "Least Recently Used"(最少使用)。

示例:

import functools

@functools.lru_cache(maxsize=None)
def fibonacci(n):

    if n < 2:

        return n

    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(35))

#@lru_cache 将自动缓存 fibonacci 函数的结果,避免重复计算,提高性能。

3.5 @functools.wraps

@wraps 是 functools 模块中的一个装饰器,用于确保被装饰函数的元数据(如函数名、文档字符串等)在装饰后保持不变。当我们编写装饰器时,通常会使用 @wraps 来避免装饰器修改原函数的名称和文档字符串。

示例:

import functools

def decorator(func):    
    @functools.wraps(func)
    def wrapper(*args, **kwargs):

        print("Before function call")

        return func(*args, **kwargs)

    return wrapper

@decorator
def greet(name):
    """This is the greet function"""

    print(f"Hello, {name}!")

print(greet.__name__)  # 输出:greet
print(greet.__doc__)   # 输出:This is the greet function

输出:

Before function call

Hello, Alice!

@functools.wraps 保证了 greet 函数的名称和文档字符串没有被 wrapper 函数覆盖。

3.6 @abstractmethod

@abstractmethod 是 abc 模块提供的装饰器,用于声明一个方法为抽象方法。抽象方法不能直接在基类中实现,而必须在子类中实现。它通常用于定义抽象基类(ABC,Abstract Base Classes)。

示例:

from abc import ABC, abstractmethod

class Shape(ABC):    
    @abstractmethod
    def area(self):
        pass

class Circle(Shape):

    def __init__(self, radius):

        self.radius = radius

    def area(self):

        return 3.14 * self.radius ** 2

# 创建实例时,必须实现抽象方法
circle = Circle(5)
print(circle.area())

输出:

78.5

3.7 @property.setter

@property.setter 装饰器用于给 @property 装饰器定义的属性添加 setter 方法。通过 setter 方法,可以控制属性值的设置。

示例:

class Circle:

    def __init__(self, radius):

        self._radius = radius

    @property
    def radius(self):

        return self._radius

    @radius.setter
    def radius(self, value):

        if value <= 0:

            raise ValueError("Radius must be positive.")

        self._radius = value

circle = Circle(5)
print(circle.radius)  # 访问属性

circle.radius = 10  # 设置属性
print(circle.radius)  # 访问更新后的属性

输出:

5

10

3.8 @property.deleter

@property.deleter 装饰器用于定义删除属性的方法,允许通过 del 删除对象的属性。

示例:

class Circle:
    def __init__(self, radius):

        self._radius = radius

    @property
    def radius(self):

        return self._radius

    @radius.deleter
    def radius(self):

        print("Deleting radius")

        del self._radius

circle = Circle(5)
print(circle.radius)  # 访问属性
del circle.radius     # 删除属性

输出:

5

Deleting radius

3.9 @staticmethod 与 @classmethod 的组合使用

有时我们会将 @staticmethod 和 @classmethod 与其他装饰器结合使用。例如,可以在类方法中使用 @classmethod 与 @staticmethod 组合,在方法中执行类和实例的操作。

4、 装饰器的实际作用示例

以下是一些装饰器常见的应用场景以及它们如何影响代码行为。

4.1 日志记录

假设你需要为多个函数添加日志记录功能,可以通过装饰器来避免重复编写日志记录代码。

def log_decorator(func):

    def wrapper(*args, **kwargs):

        print(f"Calling function {func.__name__} with arguments {args} and keyword arguments {kwargs}")

        result = func(*args, **kwargs)

        print(f"Function {func.__name__} returned {result}")

        return result

    return wrapper

@log_decorator
def add(a, b):

    return a + b

@log_decorator
def multiply(a, b):

    return a * b


add(1, 2)

multiply(3, 4)

输出:

Calling function add with arguments (1, 2) and keyword arguments {}

Function add returned 3

Calling function multiply with arguments (3, 4) and keyword arguments {}

Function multiply returned 12

装饰器使得日志记录功能从业务逻辑中分离开来,你只需要为函数加上 @log_decorator 装饰器,而不需要在每个函数中重复编写日志相关代码。

4.2 性能监控

如果你希望为多个函数监控执行时间,可以通过装饰器轻松实现:

import time

def time_decorator(func):
    def wrapper(*args, **kwargs):

        start_time = time.time()

        result = func(*args, **kwargs)

        end_time = time.time()

        print(f"Function {func.__name__} took {end_time - start_time} seconds to execute")

        return result

    return wrapper

@time_decorator
def slow_function():

    time.sleep(2)

@time_decorator
def fast_function():

    time.sleep(0.5)

slow_function()
fast_function()

输出:

Function slow_function took 2.002345085144043 seconds to execute

Function fast_function took 0.5001745223999023 seconds to execute

装饰器自动记录了函数的执行时间,避免了在每个函数内部手动写 time.time() 计算执行时间的代码。

4.3 权限验证

在 web 开发中,常常需要在处理请求之前验证用户的权限。这时可以使用装饰器来为多个视图函数或 API 接口加上权限检查。

def permission_required(func):
    def wrapper(*args, **kwargs):

        if not has_permission():

            raise PermissionError("You do not have permission to access this resource.")

        return func(*args, **kwargs)

    return wrapper

def has_permission():

    # 假设检查某个用户是否有权限
    return False

@permission_required
def sensitive_data():

    return "This is sensitive data."

try:

    sensitive_data()

except PermissionError as e:

    print(e)

输出:

You do not have permission to access this resource.

装饰器为 sensitive_data 函数增加了权限检查逻辑。这样,后续所有需要权限验证的函数都可以通过添加 @permission_required 来使用该功能。

4.4 缓存

常见的缓存装饰器可以缓存函数的返回值,避免重复计算:

from functools import lru_cache

@lru_cache(maxsize=None)  # 使用内置的缓存装饰器
def expensive_function(x):

    print(f"Calculating {x}...")

    return x * 2

print(expensive_function(4))  # 计算并缓存结果
print(expensive_function(4))  # 从缓存中获取结果

输出:

Calculating 4...

8

8

第一次调用时计算并缓存,第二次调用直接返回缓存结果,节省了计算时间。

装饰器是 Python 中的一种用于修改函数或方法行为的高级特性,功能强大且灵活,这里例举了一些装饰器的原理、应用和代码示例。感兴趣的小伙伴欢迎留言进一步探讨。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/938526.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构_拓扑排序

拓扑排序 &#xff08;所有点按照先后顺序排序&#xff09; 1.先找到入度为0的点&#xff0c;记录之后&#xff0c;删除这个点和它的出边&#xff1b; 2.若有两个可选&#xff0c;随便选择一个 例 a的入度为0,选a [a] 随便选一个 [a,e] 再找入度为0的点 再选c 最后选d 拓…

MinerU:PDF文档提取工具

目录 docker一键启动本地配置下载模型权重文件demo.py使用命令行启动GPU使用情况 wget https://github.com/opendatalab/MinerU/raw/master/Dockerfile docker build -t mineru:latest .docker一键启动 有点问题&#xff0c;晚点更新 本地配置 就是在Python环境中配置依赖和…

redis集群安装部署 redis三主三从集群

redis集群安装部署 redis三主三从集群 1、下载redis2、安装redis集群 三主三从3、配置redis开机自启动3.1、建立启动脚本3.2、复制多份redis启动脚本给集群使用3.3、添加可执行权限3.4、配置开机自启动 1、下载redis 本次redis安装部署选择当前最新的稳定版本7.4.1 下载链接: …

Fiddler简单使用

Fiddler使用方法 1.作用 接口测试&#xff0c;发送自定义请求&#xff0c;模拟小型的接口测试定位前后端bug&#xff0c;抓取协议包&#xff0c;前后端联调构建模拟测试场景&#xff0c;数据篡改&#xff0c;重定向弱网测试&#xff0c;模拟限速操作&#xff0c;弱网&#xf…

203.PyQt5_QTreeWidget_项处理_树形结构

课 程 推 荐我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)教程合集 👈👈…

6.2 MapReduce工作原理

MapReduce工作原理涉及将大数据集分割成小块并行处理。Map任务读取数据块并输出中间键值对&#xff0c;而Reduce任务则处理这些排序后的数据以生成最终结果。MapTask工作包括读取数据、应用Map函数、收集输出、内存溢出时写入磁盘以及可选的Combiner局部聚合。ReduceTask工作则…

【线性代数】理解矩阵乘法的意义(点乘)

刚接触线性代数时&#xff0c;很不理解矩阵乘法的计算规则&#xff0c;为什么规则定义的看起来那么有规律却又莫名其妙&#xff0c;现在参考了一些资料&#xff0c;回过头重新总结下个人对矩阵乘法的理解&#xff08;严格来说是点乘&#xff09;。 理解矩阵和矩阵的乘法&#x…

Win11安装安卓子系统WSA

文章目录 简介一、启用Hyper-V二、安装WSA三、安装APKAPK商店参考文献 简介 WSA&#xff1a;Windows Subsystem For Android 一、启用Hyper-V 控制面板 → 程序和功能 → 启用或关闭 Windows 功能 → 勾选 Hyper-V 二、安装WSA 进入 Microsoft Store&#xff0c;下拉框改为 …

Tree-of-Counterfactual Prompting for Zero-Shot Stance Detection

论文地址&#xff1a;Tree-of-Counterfactual Prompting for Zero-Shot Stance Detection - ACL Anthologyhttps://aclanthology.org/2024.acl-long.49/ 1. 概述 立场检测被定义为对文本中立场态度的自动推断。根据 Biber 和 Finegan (1988) 的定义&#xff0c;立场包含两个主…

轻松上手:使用 Vercel 部署 HTML 页面教程

&#x1f600; 在学习前端的过程中&#xff0c;部署项目往往是一个令人头疼的问题。然而&#xff0c;Vercel 为我们提供了一个便捷且免费的解决方案。 Vercel 是一个强大的云平台&#xff0c;专门用于前端项目的部署和托管。它不仅支持多种前端框架和静态网站生成器&#xff0…

QT从入门到精通(二) ——信号与槽机制

Qt 的信号与槽机制&#xff08;Signal and Slot&#xff09;是 Qt 框架 中用于对象间通信的核心机制之一。它允许对象之间进行松耦合的事件驱动式通信&#xff0c;尤其适合 GUI 应用程序 中的事件处理。 1. 基本概念 信号 (Signal) 当对象的状态发生变化时&#xff0c;它会发…

数据结构:Win32 API详解

目录 一.Win32 API的介绍 二.控制台程序(Console)与COORD 1..控制台程序(Console): 2.控制台窗口坐标COORD&#xff1a; 3.GetStdHandle函数&#xff1a; &#xff08;1&#xff09;语法&#xff1a; &#xff08;2&#xff09;参数&#xff1a; 4.GetConsoleCursorInf…

kubeadm_k8s_v1.31高可用部署教程

kubeadm_k8s_v1.31高可用部署教程 实验环境部署拓扑图**部署署架构****Load Balance****Control plane node****Worker node****资源分配&#xff08;8台虚拟机&#xff09;**集群列表 前置准备关闭swap开启ipv4转发更多设置 1、Verify the MAC address and product_uuid are u…

鸿蒙元服务项目实战:备忘录UI页面开发

前言 之前写过一篇关于元服务项目的上架流程&#xff0c;为了更好的了解及开发元服务&#xff0c;准备从0到1简单开发一个小项目&#xff0c;也希望能够帮助到刚刚介入到鸿蒙开发的同学&#xff0c;具体项目呢&#xff0c;也是十分的简单&#xff0c;就是一个小巧的备忘录项目&…

Linux-ubuntu点LED灯C语言版

​ 一&#xff0c;C语言点灯 1.寄存器配置 设置为SVC模式&#xff0c;复用寄存器设置GPIO1-IO003,设置电气属性&#xff0c;设置为输出模式。 2.软件 汇编语言对模式设置&#xff0c;并且将堆栈指针指向主程序&#xff1a; .global _start_start: /*设置为svr模式 */mrs …

SLM510A系列——24V,15到150mA单通道可调电流线性恒流LED驱动芯片

SLM510A 系列产品是单通道、高精度、可调电流线性恒流源的 LED 驱动芯片&#xff0c;在各种 LED 照明产品中非常简单易用。其在宽电压输入范围内&#xff0c;能保证极高的输出电流精度&#xff0c;从而在大面积的光源照明中&#xff0c;都能让 LED 照明亮度保持均匀一致。 由于…

【JavaEE】网络(2)

一、网络编程套接字 1.1 基础概念 【网络编程】指网络上的主机&#xff0c;通过不同的进程&#xff0c;以编程的方式实现网络通信&#xff1b;当然&#xff0c;我们只要满足进程不同就行&#xff0c;所以即便是同一个主机&#xff0c;只要是不同进程&#xff0c;基于网络来传…

【Java数据类型学习——String】

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” 文章目录 打印字符串长度的两种方法字符串String的比较1.用于比较引用的对象是否指向同一个内存地址2.用equa…

基于Spring Boot的校园部门资料管理系统

一、系统背景与目的 随着信息技术的飞速发展&#xff0c;校园信息化建设成为必然趋势。学校各部门在日常工作中积累了大量的资料&#xff0c;包括教学资料、学生档案、科研成果、行政文件等。传统的纸质资料管理方式存在效率低、易丢失、难以检索等问题&#xff0c;无法满足现…

STL 剖析

STL 六大组件 「STL 六大组件的交互关系」 Container 通过 Allocator 取得数据储存空间Algorithm 通过 Iterator 存取 Container 内容Functor 可以协助 Algorithm 完成不同的策略变化Adapter 可以修饰或套接 Functor、Iterator 配置器(allocator) 配置器&#xff1a;负责空间…