Python 中的 @ 符号是如何工作的,装饰器在实际项目中的巧妙应用

Python 中的 @ 符号是如何工作的

Python 中的 @ 符号是一个非常强大而又灵活的功能,它代表一个叫做"装饰器"的"语法糖"。在本文中,我们将一步步地了解它的工作原理,并通过示例代码加深理解,不改变原有函数代码的情况下,就能为它添加新功能。

一、基本概念

在 Python 中,@ 符号通常用于函数定义之前,它被称为"装饰器"。一个最简单的例子如下:

@print
def say_hello():print("Hello, world!")say_hello()

当我们运行这段代码时,输出将是:

<function say_hello at 0x7f6a1c0c8940>

这是怎么回事?原来,@print 其实是将 say_hello 函数"装饰"或"包装"了一层 print 函数。换句话说,say_hello = print(say_hello) 被隐式地执行了。

二、工作原理

现在让我们更深入地探讨一下装饰器的内部机制。装饰器的工作原理可以概括为以下几个步骤:

  1. 定义装饰器函数

  2. 将装饰器应用于目标函数

  3. 在运行时替换目标函数

让我们通过一个例子来演示这个过程:

def uppercase(func):def wrapper(*args, **kwargs):result = func(*args, **kwargs)return result.upper()return wrapper@uppercase
def say_hello(name):return f"hello, {name}"print(say_hello("Alice"))  # HELLO, ALICE
  1. 定义装饰器函数 uppercase。这个函数接受一个函数 func 作为参数,并返回一个新的函数 wrapper

  2. 使用 @uppercase 语法将 uppercase 装饰器应用于 say_hello 函数。这实际上是将 say_hello 函数传递给 uppercase 函数,并将返回值重新赋值给 say_hello

  3. 当我们调用 say_hello("Alice") 时,实际上调用的是 wrapper 函数,而不是原始的 say_hello 函数。wrapper 函数会调用原始的 say_hello 函数,并对其返回值进行大写转换。

通过这个过程,我们成功地在不改变 say_hello 函数本身的情况下,扩展了它的功能。这就是装饰器的核心机制。

三、带参数的装饰器

有时,我们可能需要为装饰器添加参数。这可以通过嵌套装饰器来实现:

def repeat(n):def decorator(func):def wrapper(*args, **kwargs):result = func(*args, **kwargs)return result * nreturn wrapperreturn decorator@repeat(3)
def say_hello(name):return f"hello, {name}"print(say_hello("Alice"))  # hello, Alice hello, Alice hello, Alice

在这个例子中,repeat 函数是一个"装饰器工厂",它返回一个装饰器函数 decoratordecorator 函数接受原始函数 func 作为参数,并返回一个新的 wrapper 函数。wrapper 函数在内部调用 func,并将其返回值重复 n 次。

通过 @repeat(3) 语法,我们将 say_hello 函数"装饰"到了 repeat(3) 中,从而使得 say_hello 函数的返回值被重复 3 次。

装饰器在实际项目中的巧妙应用

一、 日志记录器

目标:自动记录函数调用信息。

from datetime import datetimedef log_decorator(func):def wrapper(*args, **kwargs):print(f"{datetime.now()} - Calling {func.__name__}")result = func(*args, **kwargs)print(f"{datetime.now()} - Finished {func.__name__}")return resultreturn wrapper@log_decorator
def say_hello(name):return f"Hello, {name}"print(say_hello("World"))

这段代码就像是给函数装了个小尾巴,自动告诉我们它何时被叫醒,何时完成任务。

二、性能测试

用途:测量函数执行时间。

import timedef timer_decorator(func):def wrapper(*args, **kwargs):start_time = time.time()result = func(*args, **kwargs)end_time = time.time()print(f"{func.__name__} took {end_time - start_time:.4f} seconds")return resultreturn wrapper@timer_decorator
def slow_function():time.sleep(1)slow_function()

这个装饰器是性能优化的得力助手,帮你找出代码中的“慢跑者”。

三、 参数验证

应用场景:确保输入参数符合要求。

def positive_number_decorator(func):def wrapper(number):if number < 0:raise ValueError("Number must be positive.")return func(number)return wrapper@positive_number_decorator
def square(number):return number ** 2print(square(5))  # 正确
# print(square(-5))  # 将抛出异常

它像门卫一样,只允许符合条件的数字通过。

四、 缓存结果

优点:加速重复计算。

from functools import lru_cache@lru_cache(maxsize=128)
def fibonacci(n):if n <= 1:return nelse:return (fibonacci(n-1) + fibonacci(n-2))print(fibonacci(30))  # 计算一次,下次直接返回结果

对于耗时的计算,缓存简直是神来之笔,大大提升了效率。

五、权限检查

场景:在Web应用中检查用户权限。

def admin_required(func):def wrapper(user):if user.role != 'admin':return "Access denied!"return func(user)return wrapper@admin_required
def delete_user(user):return f"Deleted user {user.username}"print(delete_user(User(role='user')))  # Access denied!

装饰器是实施访问控制的简洁方式。

六、计数统计

功能:跟踪函数调用次数。

class CountCalls:def __init__(self, func):self.func = funcself.calls = 0def __call__(self, *args, **kwargs):self.calls += 1return self.func(*args, **kwargs)@CountCalls
def greet():return "Hi there!"print(greet())  # Hi there!
print(f"Called {greet.calls} times")  # 看看调用了几次

简单而强大,适合监控函数的活跃度。

七、异常处理

作用:统一处理函数异常。

def exception_handler(func):def wrapper(*args, **kwargs):try:return func(*args, **kwargs)except Exception as e:print(f"Error in {func.__name__}: {e}")return wrapper@exception_handler
def risky_business():return 1 / 0risky_business()  # 优雅地捕获并显示异常

它让程序更加健壮,不怕小错误搞砸一切。

八、参数默认值设定

好处:灵活设置默认参数,增加函数灵活性。

def with_default_value(default):def decorator(func):def wrapper(*args, **kwargs):if 'value' not in kwargs:kwargs['value'] = defaultreturn func(*args, **kwargs)return wrapperreturn decorator@with_default_value("Hello")
def greet_with_value(value):return valueprint(greet_with_value())  # 使用默认值
print(greet_with_value("World"))  # 自定义值

这样的装饰器让你的函数更加用户友好。

九、时间限制

适用:防止函数运行过久。

import signalclass TimeoutException(Exception):passdef timeout(seconds):def decorator(func):def wrapper(*args, **kwargs):def handler(signum, frame):raise TimeoutException(f"Function {func.__name__} timed out after {seconds} seconds")signal.signal(signal.SIGALRM, handler)signal.alarm(seconds)try:result = func(*args, **kwargs)finally:signal.alarm(0)  # Disable alarmreturn resultreturn wrapperreturn decorator@timeout(3)
def slow_computation():import timetime.sleep(4)try:print(slow_computation())
except TimeoutException as e:print(e)

对于可能无限期运行的任务,这是个生命线。

十、类方法转换

应用:在类中轻松实现静态方法或类方法。

def class_method_decorator(func):def wrapper(cls, *args, **kwargs):return func(cls, *args, **kwargs)return classmethod(wrapper)class MyClass:@class_method_decoratordef say_hello(cls, name):return f"{cls.__name__} says Hello, {name}"print(MyClass.say_hello("World"))  # 类方法的优雅应用

它让你在类设计上更加灵活,无需直接修改类定义。

进阶技巧与实战建议

一、嵌套装饰器

嵌套装饰器允许你组合多个装饰器的功能,为函数添加多重特性。比如,结合日志记录和性能测试:

@log_decorator
@timer_decorator
def complex_operation():# 某些复杂的计算pass

这样,你可以在一个函数上同时实现日志记录和时间测量。

二、参数化的装饰器

有时,你可能需要根据不同的情况调整装饰器的行为,这就是参数化装饰器的用武之地:

def repeat(times):def decorator(func):def wrapper(*args, **kwargs):for _ in range(times):func(*args, **kwargs)return wrapperreturn decorator@repeat(3)
def say_hi():print("Hi!")say_hi()  # 输出 "Hi!" 三次

三、 避免过度使用

虽然装饰器很强大,但过度使用会使代码难以理解和维护。确保每个装饰器都有明确且必要的用途。

四、文档字符串

为你的装饰器添加文档字符串,说明其用途和使用方法,提高代码的可读性和可维护性。

五、调试友好

在装饰器中加入适当的日志或打印语句,帮助调试时追踪函数调用流程。

实战案例分析

假设你正在开发一个API服务,其中需要对请求数据进行验证、计时响应时间和异常处理。你可以设计如下装饰器组合:

from flask import Flask, request
app = Flask(__name__)@validate_request_data  # 假设这是验证请求数据的装饰器
@timer_decorator
@exception_handler
def handle_request():data = request.json# 处理逻辑return "Request processed successfully."@app.route('/api', methods=['POST'])
def api_endpoint():return handle_request()if __name__ == "__main__":app.run(debug=True)

在这个例子中,装饰器帮助我们以模块化的方式实现了请求处理的核心逻辑,使得代码既干净又功能丰富。

结语

掌握装饰器意味着打开了Python编程的一个新世界。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[K8S]一、Flink on K8S

Kubernetes | Apache Flink 先编辑好这5个配置文件&#xff0c;然后再直接执行 kubectl create -f ./ kubectl get all kubectl get nodes kubectl get pods kubectl get pod -o wide kubectl get cm -- 获取所有的configmap 配置文件 kubectl logs pod_name -- 查看…

SpinalHDL之VHDL 和 Verilog 生成

本文作为SpinalHDL学习笔记第十六篇&#xff0c;记录使用SpinalHDL代码生成Verilog/VHDL代码的方法。 SpinalHDL学习笔记总纲链接如下&#xff1a; SpinalHDL 学习笔记_spinalhdl blackbox-CSDN博客 目录&#xff1a; 1.从 SpinalHDL 组件生成 VHDL 和 Verilog 2.生成的 VHD…

Leetcode(经典题)day3-双指针

验证回文串 125. 验证回文串 - 力扣&#xff08;LeetCode&#xff09; 直接双指针对比就行。 public boolean isPalindrome(String s) {char[] arr s.toLowerCase().toCharArray();int l0,rarr.length-1;while(l<r){while (l<r&&!ischar(arr[l])){l;}while (…

链接追踪系列-00.es设置日志保存7天-番外篇

索引生命周期策略 ELK日志我们一般都是按天存储&#xff0c;例如索引名为"zipkin-span-2023-03-24"&#xff0c;因为日志量所占的存储是非常大的&#xff0c;我们不能一直保存&#xff0c;而是要定期清理旧的&#xff0c;这里就以保留7天日志为例。 自动清理7天以前…

在html中使用vue.js的component

由于vue.js不依赖于dom元素&#xff0c;所以在body中引入就行&#xff0c;在head中引入会在渲染dom前加载&#xff0c;影响页面加载速度。 var vm new Vue({ el: "#app", data: { price: "$10", }, });在vue实例中data可以是一个对象&#xff0c;也…

Java二十三种设计模式-工厂方法模式(2/23)

工厂方法模式&#xff1a;设计模式中的瑞士军刀 引言 在软件开发中&#xff0c;工厂方法模式是一种常用的创建型设计模式&#xff0c;它用于处理对象的创建&#xff0c;将对象的实例化推迟到子类中进行。这种模式不仅简化了对象的创建过程&#xff0c;还提高了代码的可维护性…

如何预防最新的baxia变种勒索病毒感染您的计算机?

引言 在当今数字化时代&#xff0c;网络安全威胁层出不穷&#xff0c;其中勒索病毒已成为企业和个人面临的重大挑战之一。近期&#xff0c;.baxia勒索病毒以其高隐蔽性和破坏性引起了广泛关注。本文将详细介绍.baxia勒索病毒的特点、传播方式&#xff0c;并给出相应的应对策略…

【Python】深入了解 Gunicorn:一个高效的 Python WSGI 服务器

我白天是个 搞笑废物 表演不在乎 夜晚变成 忧伤怪物 撕扯着孤独 我曾经是个 感性动物 小心地感触 现在变成 无关人物 &#x1f3b5; 张碧晨/王赫野《何物》 在开发和部署 Python Web 应用时&#xff0c;选择一个高效的 WSGI 服务器非常重要。Gunicorn&…

QT VTK 简单测试工程

目录 1 目录结构 2 文件源码 3 运行结果 4 报错及处理 使用编译好的VTK库进行测试 1 目录结构 2 文件源码 Pro文件 QT core guigreaterThan(QT_MAJOR_VERSION, 4): QT widgetsCONFIG c17# You can make your code fail to compile if it uses deprecated APIs. #…

今天我们来聊Java IO模型,BIO、NIO、AIO三种常见IO模型

一、写在开头 很久没更新喽&#xff0c;最近build哥一直在忙着工作&#xff0c;忙着写小说&#xff0c;都忘记学习自己的本职了&#xff0c;哈哈&#xff0c;不过现在正式回归&#xff01; 我们继续学习Java的IO相关内容&#xff0c;之前我们了解到&#xff0c;所谓的IO&#…

工作笔记 5 Post请求 密码加密器PasswordEncoder 小程序登录

1.微信小程序登录 1.1小程序登录流程图 1.2使用sa-token完成登录 参考csdn这位老哥的http://t.csdnimg.cn/oRgvI sa-token是一款轻量级的安全框架 1.2.1首先引入sa-token依赖 <dependency><groupId>cn.dev33</groupId><artifactId>sa-token-spring…

java日常开发中常用的集合工具类方法归总(java8 stream)

1、创建map集合的方式 方式1&#xff1a; Map<String, Object> map new HashMap<>(); map.put("a", "test"); map.put("b", "since"); 方式2&#xff1a; Map<String, Object> map2 new HashMap<>() {{…

【云原生】Prometheus整合Alertmanager告警规则使用详解

目录 一、前言 二、Altermanager概述 2.1 什么是Altermanager 2.2 Altermanager使用场景 三、Altermanager架构与原理 3.1 Altermanager使用步骤 3.2 Altermanager工作机制 3.3 Altermanager在Prometheus中的位置 四、Altermanager部署与接入Prometheus 4.1 Altermana…

ConfigMap-secrets-静态pod

一.ConfigMap 1.概述 ConfigMap资源&#xff0c;简称CM资源&#xff0c;它生成的键值对数据&#xff0c;存储在ETCD数据库中 应用场景&#xff1a;主要是对应用程序的配置 pod通过env变量引入ConfigMap&#xff0c;或者通过数据卷挂载volume的方式引入ConfigMap资源 官方解释…

UniVue@v1.4.0版本发布:运行时调试器RuntimeDebuger

GitHub仓库 发布版本仓库&#xff1a;https://github.com/Avalon712/UniVue 开发版本仓库&#xff1a;https://github.com/Avalon712/UniVue-Develop UniVue拓展框架UniVue源生成器仓库&#xff1a;https://github.com/Avalon712/UniVue-SourceGenerator 更新说明 在没有调…

【Python】Windows系统 pip 换源方法(永久 / 临时)

pip 换源 首先国内较好的镜像地址有&#xff1a; 清华&#xff1a;https://pypi.tuna.tsinghua.edu.cn/simple 阿里云&#xff1a;http://mirrors.aliyun.com/pypi/simple 豆瓣&#xff1a;http://pypi.douban.com/simple 腾讯&#xff1a;http://mirrors.cloud.tencent.com/…

Mysql数据表的约束(下)

3.默认值约束(default) 与非空约束的命令一致,因为都属于列级约束,因此只需将not null改为default 默认值即可 删除默认值约束: 4.主键约束(primary key) 表示给一张表格设置了一个唯一标识,为了更快的去通过唯一的数据去准确的查找到每一条记录,一半咱们在创建表…

如何在Java中处理空集合和空指针

文章目录 概要示例代码详细解释如下正确处理集合的技巧总结 概要 在Java开发中&#xff0c;我们经常需要处理集合&#xff08;如ArrayList&#xff09;的情况。为了确保程序的稳定性和正确性&#xff0c;我们必须妥善处理空集合和空指针。 示例代码 public class Main {publ…

【Linux】基于环形队列RingQueue的生产消费者模型

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 环形队列的概念及定义 POSIX信号量 RingQueue的实现方式 RingQueue.hpp的构建 Thread.hpp Main.cc主函数的编写 Task.hpp function包装器的使用 总结 前言…

关于Kafka Topic分区和Replication分配的策略

文章目录 1. Topic多分区2. 理想的策略3. 实际的策略4. 如何自定义策略 1. Topic多分区 如图&#xff0c;是一个多分区Topic在Kafka集群中可能得分配情况。 P0-RL代表分区0&#xff0c;Leader副本。 这个Topic是3分区2副本的配置。分区尽量均匀分在不同的Broker上&#xff0c…