理解Python函数装饰器

理解Python装饰器:从函数包装到魔法揭秘 将横切关注点(日志、缓存、权限)与业务逻辑解耦通过组合而非继承扩展程序行为构建可复用的功能模块实现声明式编程风格理解装饰器的工作原理,就像掌握了Python的"魔法咒语",能在不修改原函数的情况下,为其披上各种功能外衣。这种设计模式不仅提升了代码的可维护性,更让Python的灵活特性发挥得淋漓尽致。 阅读详情

旧文更新后重发。
Python中的函数装饰器是一种能在不修改函数的前提下给函数添加额外功能的写法。

什么是装饰器

一个函数修改需求

假设已经存在一个函数:

import random

def func_a():
    print("I'm func_a, get a random number %s" % random.random())

现在想给函数加一句显示 ,最简单的写法是直接在原函数里加上新代码 :

import random

def func_a():
    print("I'm new code")
    print("I'm func_a, get a random number %s" % random.random())

不修改原函数的写法

某些情况下我们不好直接修改原函数的代码(实际场景可能是这个函数是别人写的,或者函数逻辑很复杂,不好直接修改,或者同时存在很多类似的函数需要加这个功能),这时候可以再写一个函数,先实现要添加的功能,再运行需要修改的函数并返回结果:

import random

def func_a():
    print("I'm func_a, get a random number %s" % random.random())

def new_func_a():
    print("I'm new code")
    func_a()

new_func_a()

输出:

>>> I'm new code
>>> I'm func_a, get a random number 0.582413545375

上面的写法在只有一个函数的情况下可以,但是如果大量函数都需要添加相同功能, 那就要写多个包含print("I'm new func")的新函数,这样写代码的效率太低。

那如果把要修改的函数作为新函数的参数,让新函数来运行新代码呢:

# -*- coding: utf-8 -*-
import random

def func_a():
    print("I'm func_a, get a random number %s" % random.random())

def func_b():
    print("I'm func_b, get a random number %s" % random.random())

def add_new_code(func): # 目前仅考虑无参数函数的情况
    print("I'm new code")
    func()

new_func_a = add_new_code(func_a)
new_func_b = add_new_code(func_b)

new_func_a()
new_func_b()

乍看符合需求,但实际运行却出了异常:

I'm new code
I'm func_a, get a random number 0.664406488687
I'm new code
I'm func_b, get a random number 0.66229251997
Traceback (most recent call last):
  File "Untitled 24.py", line 17, in <module>
    new_func_a()
TypeError: 'NoneType' object is not callable

在调用new_func_a()时抛出异常,提示new_func_a是NoneType。原来在调用add_new_code(func_a)时两行print代码已经运行完了,赋给new_func_a的值其实是add_new_code的返回值,没有写返回值的情况下返回值为None。

问题找到了,应该让add_new_code返回一个函数(而不是函数运行结果):

def add_new_code(func):
    print("I'm new code")
    '''
    这里是func而不是func()
    func()是调用并返回运行结果,即 new_func_a 获取的仍然只是单次运行的值,无法作为函数调用
    返回func则是将原函数赋给 new_func_a,这样new_func_a 才是一个函数
    '''
    return func

再实际运行一下得到结果:

I'm new func
I'm new func
I'm func_a, get a random number 0.489578986299
I'm func_b, get a random number 0.878320346664

结果顺序好像不太对,为什么呢?

问题与上面的问题本质相同,print("I'm new code")是在调用add_new_code时运行的,而不是在new_func_a运行,即赋给new_func_a的值其实还是原来的func_a,并没有添加上新代码。

现在我们来梳理一下add_new_code要实现的功能:函数作为参数,返回一个新函数,新函数需要运行新代码及原函数两部分。

我们先来写新函数(即应该被add_new_code返回的函数):

def new_func(): # 不能带参数
    print("I'm new code")
    return func()

注意这里new_func不能带参数,因为新函数应与原函数接收的参数保持一致。

要返回的函数已经有了,现在要解决的问题就是:这里的func从哪来?

实现装饰器

解决办法是Python中的闭包(即内部函数使用外部函数作用域的值)。写法如下:

def add_new_code(func):
    def new_func():
        print("I'm new code")
        return func()
    return new_func

new_func_a = add_new_code(func_a)
new_func_b = add_new_code(func_b)

new_func_a()
new_func_b()

运行结果:

I'm new code
I'm func_a, get a random number 0.406357569132
I'm new code
I'm func_b, get a random number 0.414812846958

结果正是我们需要的。这种写法就是装饰器。

实际项目中一般不愿意为一个小改动就改一次函数名,特别是不影响原函数实际功能的改动。上面的代码可以优化一下:

func_a = add_new_code(func_a)

即把原函数名指向装饰后的新函数,这样调用原函数的代码也不需要修改了。

Python为装饰器提供了更方便的写法(语法糖):

# -*- coding: utf-8 -*-
import random

def add_new_code(func):
    def new_func():
        print("I'm new code")
        return func()
    return new_func

# 在原函数前加上@add_new_code 相当于func_a = add_new_code(func_a)
@add_new_code  
def func_a():
    print("I'm func_a, get a random number %s" % random.random())

@add_new_code
def func_b():
    print("I'm func_b, get a random number %s" % random.random())

func_a()
func_b()

Python 同时还为装饰器提供了保留原函数元信息(比如func_a.__name__获取的函数名等)的方式:

from functools import wraps

def add_new_code(func):
    @wraps(func)  # 在内部函数前加上@wraps装饰器
    def new_func():
        print("I'm new code")
        return func()
    return new_func

这就是标准的装饰器写法了。

不同需求下的装饰器

原函数带参数

前面考虑的都是原函数不带参数的情况,如果带参数则要修改一下装饰器函数:

from functools import wraps

def add_new_code(func):
    @wraps(func)  # 在内部函数前加上@wraps装饰器
    def new_func(*args, **kwargs):
        print("I'm new code")
        return func(*args, **kwargs)
    return new_func

*args, **kwargs确保原函数不管是什么参数此装饰器都适用。

带参数的装饰器

有时候装饰器函数需要根据情况不同运行不同代码,每种情况都单独写一个装饰器会出现太多重复代码(Don’t repeat yourself):

from functools import wraps

def add_new_code_one(func):
    @wraps(func)
    def new_func(*args, **kwargs):
        print("I'm new code")
        print("I'm new code one")
        return func(*args, **kwargs)
    return new_func

def add_new_code_two(func):
    @wraps(func)
    def new_func(*args, **kwargs):
        print("I'm new code")
        print("I'm new code two")
        return func(*args, **kwargs)
    return new_func

要在一个装饰器函数里根据情况运行不同代码就要把不同情况作为一个参数传递给装饰器函数。但是add_new_code已经接收了func作为参数,新参数要怎么传呢?

考虑装饰过程的实际代码:

func_a = add_new_code(func_a)

如果改成这样:

func_a = add_new_code(arg)(func_a)

即add_new_code(arg)返回的才是装饰后的函数。

按照这种思路,装饰器最外层的函数要接收的就应该是不同情况这个参数:

from functools import wraps

def add_new_code(arg):
    def decorate(func):
        @wraps(func)
        def new_func(*args, **kwargs):
            print("I'm new code")
            print("Get a arg: %s" % arg)
            return func(*args, **kwargs)
        return new_func
    return decorate

即在外部函数外再套一层外部函数,主要是为了让最里层的函数能同时使用func和arg两个参数。

装饰原函数的语法糖写法如下:

@add_new_code('wrapper for func_a') 
def func_a():
    print("I'm func_a, get a random number %s" % random.random())

@之后的部分仍然是一个把被装饰函数作为参数的函数。

那为什么不用另一种写法:

func_a = add_new_code(func_a)(arg)

按这种写法装饰器函数应该这么写:

from functools import wraps

def add_new_code(func):
    def decorate(arg):
        @wraps(func)
        def new_func(*args, **kwargs):
            print("I'm new code")
            print("Get a arg: %s" % arg)
            return func(*args, **kwargs)
        return new_func
    return decorate

def func_a():
    print("I'm func_a, get a random number %s" % random.random())

func_a = add_new_code(func_a)("wrapper for func_a")
func_a()

运行结果与前面的结果相同。

虽然运行结果相同但这种写法没有@语法糖的支持,只能手写add_new_code(func_a)("wrapper for func_a"),并且与标准写法相比没有优势,此处写出来只为让读者对比以理解装饰器的本质。

将类定义为装饰器

装饰器可以装饰的实际上是可调用对象(函数就是最常见的可调用对象),对于类来说可调用对象需要实现__call__方法。类作为装饰器的写法如下:

import random
from functools import wraps

class Profiled:
    def __init__(self, func):
        self.func = func
        self.call_count = 0 # 装饰后的函数运行次数
        self = wraps(func)(self) # wraps装饰器不使用@的写法

    def __call__(self, *args, **kwargs):
        self.call_count += 1
        print("I'm new code")
        print("new func running count: %s" % self.call_count)
        return self.func(*args, **kwargs)

@Profiled    
def func_a():
    print("I'm func_a, get a random number %s" % random.random())

func_a()
func_a()
print(func_a.__name__) # 检查元信息

输出:

I'm new code
new func running count: 1
I'm func_a, get a random number 0.710760549208
I'm new code
new func running count: 2
I'm func_a, get a random number 0.507024218664
func_a

由于类实例的初始化和运行过程分离,可以实现这种运行计数功能。

不过类装饰器常见的用法是与描述器结合,关于描述器可参考本人的另一篇文章。

示例

下面两个来自《Python Cookbook》,都是实际项目中常见的需求:

import time
from functools import wraps
def timethis(func):
    '''
    输出函数运行时间的装饰器
    '''
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end-start)
        return result
    return wrapper
from functools import wraps
import logging

def logged(level, name=None, message=None):
    """
    为函数添加日志。
    level参数是日志等级
    message参数是日志记录的额外信息
    """
    def decorate(func):
        logname = name if name else func.__module__
        log = logging.getLogger(logname)
        logmsg = message if message else func.__name__

        @wraps(func)
        def wrapper(*args, **kwargs):
            log.log(level, logmsg)
            return func(*args, **kwargs)
        return wrapper
    return decorate
VS Code + Codex 在 WSL 中使用的正确姿势 Codex 登录只能在本地完成。文件仍可通过 UNC 路径访问 WSL → 本地 Codex 就能照常工作。避免在 WSL 内重复安装/登录 Codex 扩展。 阅读详情

相关推荐

Cadence SKILL 语言实战指南:从函数列表到高效自动化脚本

本文是Cadence SKILL语言实战指南,旨在帮助芯片设计工程师从掌握核心函数列表出发,高效构建自动化脚本。文章详细解析了数据处理、数据库访问、流程控制及错误处理四类关键函数,并通过版图层批量修改与参数化单元生成两大实战场景,演示如何将函数组合成解决实际问题的自动化方案,从而显著提升Virtuoso环境下的工作效率。

beta5的博客 705

Python 装饰器为什么要双层嵌套及业务场景

一个普通的装饰器写法: def log(func): def wrapper(*args, **kwargs): print('被调用的函数'.format(func.__name__)) return func(*args, **kwargs) return wrapper 此时有个疑问,为什么里面要嵌套wrapper函数? 原因: 如果没有嵌套,实际上装饰器返回的要么是原函数的定义,要么根本不是函数,也就是说函数根本没有被装饰。 即使碰巧得到了想要的结

adorable_的博客 667

kettle从入门到精通 第九十二课 ETL之kettle 使用Kettle的Carte对外发布读写接口

场景:使用kettle实现将查询结果返回给客户端,也就是说kettle暴露查询接口供外围系统调用。前提必须是使用carte服务才可以提供接口供外部系统调用。具体实操方法如下:1、设计转换根据具体需求设计转换,主要用到的步骤有获取变量(从请求参数中获取变量)、json输入(解析入参),json 输出将结果转换为json格式输出到客户端。如下图所示:2、接收参数使用获取变量步骤来接收接口请求参数,变量通过${}方法进行获取,如下图${PARAM}则是获取PARAM参数,将参数赋值为req字段。

zhangjin1222的博客 954

【Python】装饰器与内部函数简单理解

1、内部函数,顾名思义,是函数内部定义的函数,其作用是封装逻辑,使函数逻辑更为清晰。 def outer(): inner(): 业务逻辑 return inner 2、为何return inner而不是return inner()呢? 初学者可能都有此一问,我们知道,运行一个函数就是:func(),然后func会得出结果。同理,如果return inner...

用代码构建世界 668

解读Autoware.Universe规划模块:Behavior Path Planner

Behavior Path Planner是一个基于行为生成不同形态路径的路径规划器,负责生成 基于交通状况的路径(path), 车辆可以移动的可行驶区域(drivable area)(在路径消息中定义), 将被发送到车辆接口的转向信号(turn signal) 命令。

策 5491

三维重建Fusion中的TSDF算法通俗描述

看个视频 https://www.youtube.com/watch?v=8M_-lSYqACo 这是mobilefusion的视频,大家先了解一下,看到在三维重建的时候外面有个长方体包围盒了吗? 数据准备: 我们不同角度拍摄的RGB图、深度图和相应的pose.txt。 步骤: 1.建立长方体包围盒 要建立一个长方体包围盒。让所有的三维点都在这个长方体里面。 假设z方向垂直相机。 很明显,x,y方...

qq_30339595的博客 1万+

Python装饰器的定义和使用

1.装饰器的定义 装饰器:给已有函数增加额外的功能的函数,本质上是一个闭包函数 特点: 1.不修改已有函数的源代码 2.不修改已有函数的调用方式 3.给已有函数增加额外的功能 4.代码执行时先解析装饰器 # @Author : Kant # @Time : 2022/1/23 18:06 ''' 装饰器:给已有函数增加额外的功能的函数,本质上是一个闭包函数 特点: 1.不修改已有函数的源代码 2.不修改已有函数的调用方式 3.给已有函数...

KK_2018的技术博客_程序员不止是程序员 1230

074.多任务学习:在YOLO基础上同时进行分割、关键点检测

本文探讨了在YOLOv5模型中实现检测、分割和关键点多任务的技术方案。通过分层特征抽取策略(检测用深层、分割用浅深融合、关键点用中层)解决特征共享问题;采用不确定性加权方法自动平衡多任务损失;设计了支持三种标签的数据流处理方式;并分享了推理部署时的后处理技巧。实践表明,合理的多任务设计可以提升主任务性能,但需权衡标注成本和推理效率。最终方案选择在训练时引入分割作为辅助任务,推理时仅保留检测和关键点,实现了检测mAP提升2.3%的优化效果。

2603_95664987的博客 45

Python装饰器详解

详细介绍了Python装饰器语法的有关内容与使用技巧,希望对大家有所帮助。

千山暮彩 1万+

科研级YOLOv8 Neck革新:HS-FPN模块赋能,让特征融合更高效且参数量直降100W的实战指南

在文件夹下新建HS_FPN.py,将上述和HS_FPN类的代码写入其中。这一步是为了让YOLOv8的代码架构能识别我们的自定义Neck模块。

FJN110的博客 153

Android 64位支持的坑:诡异的文件名问题

最近在尝试发布我的Android应用程序的更新时,遇到了一个令人困惑的64位错误。本文将详细讲解这个问题的产生原因及解决方法,供大家参考。

m0_74337424的博客 123

python函数装饰器

参考文献: 【1】Python 函数装饰器 | 菜鸟教程

灰太狼的小秘密 4429

基于STM32L4XX、HAL库的DP83822 以太网收发器(PHY)驱动程序设计

一、简介:DP83822是德州仪器(TI)推出的一款低功耗、高性能10/100Mbps以太网物理层收发器(PHY)。它具有以下主要特性:支持10BASE-T和100BASE-TX以太网标准低功耗设计,适合嵌入式应用MII/RMII接口,方便与微控制器连接内置自检和诊断功能支持自动协商和手动配置工业级温度范围(-40°C至+85°C)

zhusl6688的博客 526

python函数装饰器(装饰器类及装饰器函数)的理解

python函数装饰器(装饰器类及装饰器函数)的理解 文章目录python函数装饰器(装饰器类及装饰器函数)的理解函数作为函数装饰器一个简单的“执行后打印”装饰器执行流程分析@wraps的作用类作为函数装饰器使用类改写“执行后打印”装饰器为装饰器添加参数 函数作为函数装饰器 一个简单的“执行后打印”装饰器执行流程分析 如果要实现一个装饰器功能,即先运行某个函数,运行后,将函数的返回值使用print打印出来,用函数作为函数装饰器的实现为: from functools import wraps def pri

O_1CxH的博客 706

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

在python中、对于函数定义代码的理解_python中如何理解装饰器代码?

长文预警,【最浅显易懂的装饰器讲解】能不能专业地复制题目?配上代码,问题分段。我来给提主配上问题的代码。正式回答:1:如何理解return一个函数,它与return一个值得用法区别在哪?敲黑板,"python中,一切都是对象"。值是对象,函数也是对象。上图,num是int类的实例对象,funcobj是function类的一个实例对象。所以返回一个值和返回一个函数并没有什么不同,本质都是返回一个对象...

weixin_28834765的博客 358

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

深入理解Python装饰器:如何优雅地为函数打补丁

Python装饰器提供了一种灵活而强大的方式来为函数添加新功能或修改现有功能。通过合理地使用装饰器,我们可以在不修改函数内部代码的情况下,优雅地为函数打补丁,从而满足不断变化的需求。同时,我们也需要注意装饰器的使用方法和注意事项,以确保代码的健壮性和可维护性。👇热门内容👇python使用案例与应用_安城安的博客-优快云博客软硬件教学_安城安的博客-优快云博客Orbslam3&Vinsfusion_安城安的博客-优快云博客网络安全_安城安的博客-优快云博客教程_安城安的博客-优快云博客。

anananajiushiwo的博客 930

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

给python入门者的帮助,关于函数和装饰器的理解。

最近发现几年前写的一篇关于python函数return的一些理解,又被查看了,而且评论中表示得到了帮助。但是惭愧的是,那篇理解是我刚学python没多久的个人理解,由于储备的认识不足,经验不足,思考不足,许多理解是不恰当的,或者是错的,所以今天重新写一篇关于python函数的理解。这篇理解主要是帮助刚入门的朋友尽快理解和接收函数,实际上也会有许多不那么准确的比喻和描述,因为一旦涉及到完全准确的底层,就会变得晦涩难懂,就会在解决这个问题之前,遇到更多问题,这显然对学习是不利的。

try_test_python的博客 942

利用口诀理解python函数的作用域,闭包和装饰器

内容来源于:https://www.bilibili.com/video/BV1ZJ411y7Te?from=search&seid=5018137173127254008 Part 1 展示函数的作用域 ‘’’ 三句口诀理解Python的函数作用域机制 1、作用域,是栋楼,下楼套上楼 2、读变量,往下搜,一直到一楼 3、改变量,莫下楼,除非你放狗(global) ‘’’ """示例1:展示1和2""" def secondFloor(): print(msg)

mayanyun2013的专栏 234

理解python函数装饰器@decorator

文章目录一、储备知识点二、理解装饰行为 函数装饰器是python有别于其他语言的主要特征之一,这也是充分发挥了动态语言的优势,可以使我们在编写程序时更加简洁。但最开始接触函数装饰器有点难以理解,本文尽量用简单的语言来理解函数装饰器。 一、储备知识点 在理解函数装饰器之前,最好储备并理解了一下Python的基础知识: 自定义函数。包括函数参数、实参、形参、位置参数、关键字参数、不定长的列表类参数和...

pyjavacsql的博客 229

理解python装饰器——函数装饰器、类装饰器、执行顺序

装饰器本质上就是一个Python函数,它可以让其它函数在不需要做任何代码变动的前提下增加额外功能。装饰器的返回值也是一个函数对象。 装饰器经常用于有切面需求的场景,比如:插入日志、性能检测、事务处理、缓存、权限校验等场景。有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。 概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。 下面是一个简单的装饰器。@use_loggi...

WWtianxiang的博客 308

Python 进阶教程 函数装饰器的理解

以下是原文内容 from functools import wraps def logit(logfile='out.log'): def logging_decorator(func): @wraps(func) def wrapped_function(*args, **kwargs): log_string = func.__name__ + " was called" print(log_string)

老六 256

深入理解 Python 装饰器:提升函数的能力

python中的装饰器

威桑的博客 1215

24.一文理解Python函数装饰器

装饰器在 Python 使用如此方便都要归因于 Python 的函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。 装饰器(Decorators)是Python的一个重要部分;装饰器是修改其他函数的功能的函数,有助于让我们的代码更简洁。hi()

m0_57656758的博客 536

理解Python 返回函数和装饰器

函数也是对象 返回函数 装饰器 理解装饰器 * args是一个数组,代表所有参数,** kwargs是一个字典,代表所有有默认值的关键字参数。比如: def foo(name, age=None, height=None): print("I am %s, age %s, height %s" % (name, age, height)) * args 代表 name类似的参数...

afunyusong的专栏 744

python 装饰器、内部函数、闭包简单理解

python 装饰器、内部函数、闭包简单理解 python内部函数、闭包共同之处在于都是以函数作为参数传递到函数,不同之处在于返回与调用有所区别。 1、python内部函数 python内部函数示例: ?12345678def test(*args):&amp;amp;amp;amp;nbsp;&amp;amp;amp;amp;nbsp;&amp;amp;amp;amp;nbsp;&amp;amp;amp;am

百雨的博客 191

python装饰器函数执行后日志_python 装饰器理解

在理解装饰器之前,先应该对闭包有个概念:所谓闭包,就是将组成函数的语句和这些语句的执行环境打包在一起时得到的对象,它的主要作用是封存上下文。这一特性可以巧妙的被用于现有函数的包装,从而为现有函数添加功能,这就是装饰器。装饰器的本质与作用装饰器(Decorator)的本质是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,装饰器的返回值也是一个函数对象。它经常用于有...

weixin_39648539的博客 230
上一篇: Python 的列表推导式
下一篇: Python2 异步网络请求库比较
XXERR
博客等级 码龄14年 44粉丝 52原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值