Python装饰器是什么?

简介: 装饰器是Python中用于动态修改函数、方法或类功能的工具,无需改变原代码。通过将函数作为参数传递并返回新函数,装饰器可以在原函数执行前后添加额外逻辑。例如,使用`@logger`装饰器可以打印函数调用日志,而`@timethis`则可用于计算函数执行时间。为了保持被装饰函数的元信息(如`__name__`和`__doc__`),可使用`functools.wraps`装饰器。此外,带参数的装饰器可通过嵌套函数实现,如`@timeitS(2)`,以根据参数条件输出特定信息。

引入装饰器

如果想在一个函数执行前后执行一些别的代码,比如打印一点日志用来输出这个函数的调用情况那应该怎么做呢?

python

代码解读

复制代码

#!/usr/bin/env python
# coding=utf-8

def logger(fn):									# 函数作为参数即fn可以为任何参数
    def wrap(*args, **kwargs):					# 可变参数args和kwargs
        print('call {}'.format(fn.__name__))	
        ret = fn(*args, **kwargs)				# 函数调用时的参数解构
        print('{} called'.format(fn.__name__))
        return ret								# 返回函数的返回值
    return wrap

def add(x, y):
    return x + y

logger_add = logger(add)
print(logger_add.__name__)
print(logger_add)
ret = logger_add(3, 5)
print(ret)

#输出结果:
wrap
<function logger.<locals>.wrap at 0x7fba35f4fe18>
call add
add called
8

也可以用以下方式来实现这种效果

python

代码解读

复制代码

@logger                                                                                  
def add(x, y):                                                                            
	return x + y                                                                         ret = add(3, 5)                                                                      
print(ret) 

# 输出结果:
call add
add called
8

这就是Python装饰器的一个简单使用

什么是装饰器?

装饰器是用于软件设计模式的名称。 装饰器可以动态地改变函数,方法或类的功能,而不必直接使用子类或改变被装饰的函数的源代码。Python装饰器是对Python语法的一种特殊改变,它允许我们更方便地修改函数,方法以及类。

当我们按照以下方式编写代码时:

python

代码解读

复制代码

@logger
def add(x, y):
	...

和单独执行下面的步骤是一样的:

scss

代码解读

复制代码

def add(x, y):
	...
logger_add = logger(add)

装饰器内部的代码一般会创建一个新的函数,利用*args**kwargs来接受任意的参数,上述代码中的wrap()函数就是这样的。在这个函数内部,我们需要调用原来的输入函数(即被包装的函数,它是装饰器的输入参数)并返回它的结果。但是也可以添加任何想要添加的代码,比如在上述代码中输出函数的调用情况,也可以添加计时处理等等。这个新创建的wrap函数会作为装饰器的结果返回,取代了原来的函数。

所以在Python中,装饰器的参数是一个函数, 返回值是一个函数的函数

装饰器的示例:计时处理

写一个装饰器,用来计算一个函数的执行时间

python

代码解读

复制代码

import time

def timethis(fn):
    def wrap(*args, **kwargs):
        start = time.time()
        ret = fn(*args, **kwargs)
        end = time.time()
        print(fn.__name__, end - start)
        return ret
    return wrap

如果要对add函数计时:

python

代码解读

复制代码

@timethis
def add(x, y):
    return x + y

ret = add(3, 5)
print(ret)

# 输出结果
add 1.9073486328125e-06
8

如果要对sleep函数计时:

python

代码解读

复制代码

@timethis
def sleep(x):
    time.sleep(x)

sleep(3)

# 输出结果
sleep 3.003262519836426

保存被装饰函数的元信息

什么是函数的元信息

比如装饰器的名称,装饰器的doc等等。我们可以使用dir函数列出函数的所有元信息:dir(sleep),输出结果如下

css

代码解读

复制代码

['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

可以看到有很多的元信息,我们比较常用的是__name____doc__这两个属性\

而且__doc__属性也就是函数的文档信息,可以通过help函数查看得到

为什么要保存被装饰函数的元信息

改写装饰器的应用1:计时处理中的sleep函数如下:

python

代码解读

复制代码

@timeit
def sleep(x):
    '''This function is sleep.'''
    time.sleep(x)

sleep(3)
print(sleep.__name__)
print(sleep.__doc__)

以上代码输出结果如下:

css

代码解读

复制代码

3.0032713413238525
wrap
None

可以发现sleep函数的__name__是wrap,而不是sleep,而__doc__属性为空,而不是sleep函数的docstring。也就是说经过装饰器装饰过后的函数的元信息发生了改变,这时候如果程序需要函数的元信息,那么就有问题了。

如何保存被装饰函数的元信息

方案1:手动给被装饰函数的元信息赋值

__name____doc__这两个属性为例

python

代码解读

复制代码

import time

def timeit(fn):
    def wrap(*args, **kwargs):
        start = time.time()
        ret = fn(*args, **kwargs)
        end = time.time()
        print(end - start)
        return ret
    wrap.__doc__ = fn.__doc__	# 手动赋值__doc__信息
    wrap.__name__ = fn.__name__	# 手动赋值__name__信息
    return wrap

@timeit
def sleep(x):
    '''This function is sleep.'''
    time.sleep(x)

if __name__ == "__main__":
    sleep(3)
    # print(dir(sleep))
    print(sleep.__name__)
    print(sleep.__doc__)

输出结果如下

bash

代码解读

复制代码

3.004547119140625
sleep
This function is sleep.

可以发现,__name____doc__这两个属性确实赋值成功了。

我们可以将元信息赋值的过程改写为函数,如下

python

代码解读

复制代码

import time


def copy_properties(src, dst):	# 将元信息赋值的过程改成函数copy_properties
    dst.__name__ = src.__name__
    dst.__doc__ = src.__doc__

def timeit(fn):
    def wrap(*args, **kwargs):
        start = time.time()
        ret = fn(*args, **kwargs)
        end = time.time()
        print(end - start)
        return ret
    copy_properties(fn, wrap)	# 调用copy_properties函数修改元信息
    return wrap

@timeit
def sleep(x):
    '''This function is sleep.'''
    time.sleep(x)

if __name__ == "__main__":
    sleep(3)
    # print(dir(sleep))
    print(sleep.__name__)
    print(sleep.__doc__)

这样修改后,同样可以解决问题。

继续修改copy_properties函数,使得copy_properties可以返回一个函数

python

代码解读

复制代码

def copy_properties(src):
    def _copy(dst):	# 内置一个_copy函数便于返回
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
    return _copy

def timeit(fn):
    def wrap(*args, **kwargs):
        start = time.time()
        ret = fn(*args, **kwargs)
        end = time.time()
        print(end - start)
        return ret
    copy_properties(fn)(wrap)	# 调用copy_properties函数
    return wrap

同样可以问题。

如果继续修改copy_properties函数,使得_copy函数是一个装饰器,传入dst,返回dst,修改如下:

python

代码解读

复制代码

def copy_properties(src):	# 先固定dst,传入src
    def _copy(dst):	# 传入dst
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
        return dst	# 返回dst
    return _copy	# 返回一个装饰器

def timeit(fn):
    @copy_properties(fn)	# 带参数装饰器的使用方法
    def wrap(*args, **kwargs):
        start = time.time()
        ret = fn(*args, **kwargs)
        end = time.time()
        print(end - start)
        return ret
    return wrap

copy_properties在此处返回一个带参数的装饰器,因此可以直接按照装饰器的使用方法来装饰wrap函数,这个修改copy_properties函数的过程称为函数的柯里化。

方案2:使用functools库的@wraps装饰器

functools库的@wraps装饰器本质上就是copy_properties函数的高级版本:包含更多的函数元信息。首先查看wrap装饰器的帮助信息:

python

代码解读

复制代码

import functools
help(functools.wraps)

wrap装饰器函数的原型是:

python

代码解读

复制代码

wraps(wrapped, assigned=('module', 'name', 'qualname', 'doc', 'annotations'), updated=('dict',))

所以这个装饰器会复制module等元信息,但是也不是所有的元信息,并且会更新dict。

使用示例如下:

python

代码解读

复制代码

import time
import functools

def timeit(fn):
    @functools.wraps(fn)	# wraps装饰器的使用
    def wrap(*args, **kwargs):
        start = time.time()
        ret = fn(*args, **kwargs)
        end = time.time()
        print(end - start)
        return ret
    return wrap

def sleep(x):
    time.sleep(x)

print(sleep.__name__)
print(sleep.__doc__)

编写一个带参数的装饰器

如果上述的timeit装饰器,我们需要输出执行时间超过若干秒(比如一秒)的函数的名称和执行时间,那么就需要给装饰器传入一个参数s,表示传入的时间间隔,默认为1s。

我们可以给写好的装饰器外面包一个函数timeitS,时间间隔s作为这个函数的参数传入,并且对内层的函数可见,然后这个函数返回写好的装饰器。

python

代码解读

复制代码

import time
import functools


def timeitS(s):
    def timeit(fn):
        @functools.wraps(fn)
        def wrap(*args, **kwargs):
            start = time.time()
            ret = fn(*args, **kwargs)
            end = time.time()
            if end - start > s:
                print('call {} takes {}s'.format(fn.__name__, end - start))
            else:
                print('call {} takes {}s less than {}'.format(fn.__name__, end - start, s))
            return ret
        return wrap
    return timeit

@timeitS(2)
def sleep(x):
    time.sleep(x)

sleep(3)
sleep(1)

输出结果如下:

bash

代码解读

复制代码

call sleep takes 3.001342535018921s
call sleep takes 1.000471830368042s less than 2

所以,我们可以将带参数的装饰器理解为:

  • 带参数的装饰器就是一个函数, 这个函数返回一个不带参数的装饰器


转载来源:https://juejin.cn/post/6878240565385756686

相关文章
|
1月前
|
开发者 Python
探索Python中的装饰器:从基础到高级应用
本文将带你深入了解Python中的装饰器,这一强大而灵活的工具。我们将一起探讨装饰器的基本概念,它们如何工作,以及如何使用它们来增强函数和类的功能,同时不改变其核心逻辑。通过具体代码示例,我们将展示装饰器的创建和使用,并探索一些高级应用,比如装饰器堆栈和装饰带参数的装饰器。无论你是初学者还是有经验的开发者,这篇文章都将为你提供新的视角,帮助你更有效地使用装饰器来简化和优化你的代码。
|
1月前
|
测试技术 数据安全/隐私保护 开发者
探索Python中的装饰器:从基础到高级应用
装饰器在Python中是一个强大且令人兴奋的功能,它允许开发者在不修改原有函数代码的前提下增加额外的功能。本文将通过具体代码示例,带领读者从装饰器的基础概念入手,逐步深入到高级用法,如带参数的装饰器和装饰器嵌套等。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧。
|
1月前
|
开发框架 数据建模 中间件
Python中的装饰器:简化代码,增强功能
在Python的世界里,装饰器是那些静悄悄的幕后英雄。它们不张扬,却能默默地为函数或类增添强大的功能。本文将带你了解装饰器的魅力所在,从基础概念到实际应用,我们一步步揭开装饰器的神秘面纱。准备好了吗?让我们开始这段简洁而富有启发性的旅程吧!
46 6
|
2天前
|
测试技术 数据库 Python
Python装饰器实战:打造高效性能计时工具
在数据分析中,处理大规模数据时,分析代码性能至关重要。本文介绍如何使用Python装饰器实现性能计时工具,在不改变现有代码的基础上,方便快速地测试函数执行时间。该方法具有侵入性小、复用性强、灵活度高等优点,有助于快速发现性能瓶颈并优化代码。通过设置循环次数参数,可以更准确地评估函数的平均执行时间,提升开发效率。
70 61
Python装饰器实战:打造高效性能计时工具
|
1月前
|
缓存 数据安全/隐私保护 Python
python装饰器底层原理
Python装饰器是一个强大的工具,可以在不修改原始函数代码的情况下,动态地增加功能。理解装饰器的底层原理,包括函数是对象、闭包和高阶函数,可以帮助我们更好地使用和编写装饰器。无论是用于日志记录、权限验证还是缓存,装饰器都可以显著提高代码的可维护性和复用性。
36 5
|
1月前
|
测试技术 Python
探索Python中的装饰器:简化代码,增强功能
在Python的世界中,装饰器是那些能够为我们的代码增添魔力的小精灵。它们不仅让代码看起来更加优雅,还能在不改变原有函数定义的情况下,增加额外的功能。本文将通过生动的例子和易于理解的语言,带你领略装饰器的奥秘,从基础概念到实际应用,一起开启Python装饰器的奇妙旅程。
49 11
|
1月前
|
测试技术 开发者 Python
探索Python中的装饰器:从入门到实践
装饰器,在Python中是一块强大的语法糖,它允许我们在不修改原函数代码的情况下增加额外的功能。本文将通过简单易懂的语言和实例,带你一步步了解装饰器的基本概念、使用方法以及如何自定义装饰器。我们还将探讨装饰器在实战中的应用,让你能够在实际编程中灵活运用这一技术。
44 7
|
1月前
|
Python
探索Python中的装饰器:简化代码,增强功能
在Python的世界里,装饰器就像是给函数穿上了一件神奇的外套,让它们拥有了超能力。本文将通过浅显易懂的语言和生动的比喻,带你了解装饰器的基本概念、使用方法以及它们如何让你的代码变得更加简洁高效。让我们一起揭开装饰器的神秘面纱,看看它是如何在不改变函数核心逻辑的情况下,为函数增添新功能的吧!
|
1月前
|
程序员 测试技术 数据安全/隐私保护
深入理解Python装饰器:提升代码重用与可读性
本文旨在为中高级Python开发者提供一份关于装饰器的深度解析。通过探讨装饰器的基本原理、类型以及在实际项目中的应用案例,帮助读者更好地理解并运用这一强大的语言特性。不同于常规摘要,本文将以一个实际的软件开发场景引入,逐步揭示装饰器如何优化代码结构,提高开发效率和代码质量。
59 6
|
1月前
|
存储 缓存 Python
Python中的装饰器深度解析与实践
在Python的世界里,装饰器如同一位神秘的魔法师,它拥有改变函数行为的能力。本文将揭开装饰器的神秘面纱,通过直观的代码示例,引导你理解其工作原理,并掌握如何在实际项目中灵活运用这一强大的工具。从基础到进阶,我们将一起探索装饰器的魅力所在。