前面一篇文章介绍了python装饰器,最后引入了functools.wraps的使用,本篇文章将对它进行深入的探究。

functools模块提供了一系列的高阶函数以及对可调用对象的操作,其中为人熟知的有reduce,partial,wraps等。

为了保证被装饰器装饰后的函数还拥有原来的属性,wraps通过partial以及update_wrapper来实现。

我们先来了解一下partial,partial用于部分应用一个函数,它基于一个函数创建一个可调用对象,把原函数的某些参数固定,调用时只需要传递未固定的参数即可。

我们先看partial如何使用:

import functools

def add(a, b):

print(a + b)

add = functools.partial(add, 1)

add(2)

输出:3

add函数原本接收两个参数a和b,经过partial包装之后,a参数的值被固定为了1,新的add对象(注意此处add已经是一个可调用对象,而非函数,下文分析源码会看到)只需要接收一个参数即可。

通俗点说:就是把原函数的部分参数固定了初始值,新的调用只需要传递其它参数。

下面来分析partial的源码(Python3.7),只摘录了核心部分:

class partial:

"""New function with partial application of the given argumentsand keywords."""

__slots__ = "func", "args", "keywords", "__dict__", "__weakref__"

def __new__(*args, **keywords):

if not args:

raise TypeError("descriptor '__new__' of partial needs an argument")

if len(args) < 2:

raise TypeError("type 'partial' takes at least one argument")

cls, func, *args = args

if not callable(func):

raise TypeError("the first argument must be callable")

args = tuple(args)

if hasattr(func, "func"):

args = func.args + args

tmpkw = func.keywords.copy()

tmpkw.update(keywords)

keywords = tmpkw

del tmpkw

func = func.func

self = super(partial, cls).__new__(cls)

self.func = func

self.args = args

self.keywords = keywords

return self

def __call__(*args, **keywords):

if not args:

raise TypeError("descriptor '__call__' of partial needs an argument")

self, *args = args

newkeywords = self.keywords.copy()

newkeywords.update(keywords)

return self.func(*self.args, *args, **newkeywords)

通过重写“_new__”方法,自定义对象实例化过程。

1、元组拆包,获取到传入的原函数(func)和需要固定的参数(args)

cls, func, *args = args

2、主要是为了支持嵌套调用,即add=partial(partial(add,1),2)这种情况,可先看第三步,回过头再来看

if hasattr(func, "func"):

args = func.args + args

tmpkw = func.keywords.copy()

tmpkw.update(keywords)

keywords = tmpkw

del tmpkw

func = func.func

3、实例化partial对象,将传入的函数和参数设置为当前对象的属性

self = super(partial, cls).__new__(cls)

self.func = func

self.args = args

self.keywords = keywords

return self

到这里我们已经明白了partial是怎么保存原函数和固定参数的了,下面来看一下调用的时候是如何执行的。

先简单了解一下可调用对象:当一个类实现了"__call__"方法后,这个类的对象就能够像函数一样被调用。

class Callable:

def __call__(self, a, b):

return a + b

func = Callable()

result = func(2, 3) # 像函数一样调用

print(result)

输出:5

好啦,我们看下partial的"__call__"是如何实现的:

def __call__(*args, **keywords):

if not args:

raise TypeError("descriptor '__call__' of partial needs an argument")

self, *args = args

newkeywords = self.keywords.copy()

newkeywords.update(keywords)

return self.func(*self.args, *args, **newkeywords)

1、元组拆包,获取到传入的非固定参数args

self, *args = args

2、拷贝当前对象的keywords参数,并且合并传入的非固定参数字典

newkeywords = self.keywords.copy()

newkeywords.update(keywords)

3、调用当前对象的func属性,func即被partial包装的原函数,同时传入暂存的固定参数self.args以及新传入的其它参数。

至此一切真相大白:partial通过实现"__new__"和"__call__"生成一个可调用对象,这个对象内部保存了被包装函数以及固定参数,这个对象可以像函数一样被调用,调用时,其实是执行了对象内部持有的被包装函数,其参数由固定参数和新传入的参数组合而来。

继续探索wraps的源码:

def wraps(wrapped,

assigned = WRAPPER_ASSIGNMENTS,

updated = WRAPPER_UPDATES):

"""Decorator factory to apply update_wrapper() to a wrapper functionReturns a decorator that invokes update_wrapper() with the decoratedfunction as the wrapper argument and the arguments to wraps() as theremaining arguments. Default arguments are as for update_wrapper().This is a convenience function to simplify applying partial() toupdate_wrapper()."""

return partial(update_wrapper, wrapped=wrapped,

assigned=assigned, updated=updated)

入参解读:

wrapped:指被装饰器装饰的原函数,我们的装饰器便是要拷贝它的属性。

assigned:要被重新赋值的属性列表,默认为WRAPPER_ASSIGNMENTS,可自定义传入

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',

'__annotations__')

updated:要被合并的属性列表,默认为WRAPPER_UPDATES,可自定义传入

WRAPPER_UPDATES = ('__dict__',)

返回值:

返回了一个partial对象,这个对象对update_wrapper进行了包装,固定了wrapped,assigned,updated三个参数。

wraps本省就是一个装饰器,因为它返回的是一个“函数”即partial对象,这个对象接收函数作为参数,同时以函数作为返回值。

接下来看update_wrapper:

def update_wrapper(wrapper,

wrapped,

assigned = WRAPPER_ASSIGNMENTS,

updated = WRAPPER_UPDATES):

"""Update a wrapper function to look like the wrapped functionwrapper is the function to be updatedwrapped is the original functionassigned is a tuple naming the attributes assigned directlyfrom the wrapped function to the wrapper function (defaults tofunctools.WRAPPER_ASSIGNMENTS)updated is a tuple naming the attributes of the wrapper thatare updated with the corresponding attribute from the wrappedfunction (defaults to functools.WRAPPER_UPDATES)"""

for attr in assigned:

try:

value = getattr(wrapped, attr)

except AttributeError:

pass

else:

setattr(wrapper, attr, value)

for attr in updated:

getattr(wrapper, attr).update(getattr(wrapped, attr, {}))

# Issue #17482: set __wrapped__ last so we don't inadvertently copy it

# from the wrapped function when updating __dict__

wrapper.__wrapped__ = wrapped

# Return the wrapper so this can be used as a decorator via partial()

return wrapper

这个便是解析@functools.wraps(func)时最底层执行的逻辑,代码很简洁,就是把wrapped函数的属性拷贝到wrapper函数中。

wrapped是被装饰的原函数

wrapper是被装饰器装饰后的新函数。

通过下面的例子对执行过程和参数进行对号入座

def outer(func):

@functools.wraps(func)

def inner(*args, **kwargs):

print(f"before...")

func(*args, **kwargs)

print("after...")

return inner

@outer

def add(a, b):

"""求和运算"""

print(a + b)

1、原函数为add。

2、@outer会去执行outer装饰器,传入add函数,返回一个inner函数。

3、执行outer函数时,加载inner函数,此时会直接执行functools.wraps(func)返回一个可调用对象,即partial对象。

4、此时inner的装饰器实际上是@partial,partial会被调用,传入inner函数,执行partial内部的update_wrapper函数,将func的相应属性拷贝给inner函数,最后返回inner函数。这一步并没有生成新的函数,仅仅是改变了inner函数的属性。

5、把add指向inner函数。

6、调用add实际调用的是inner函数,inner函数内部持有原add函数的引用即func。

update_wrapper函数参数对应:

wrapper指的是inner函数

wrapped指的是func即原始的add函数

总结:

1)functools.wraps 旨在消除装饰器对原函数造成的影响,即对原函数的相关属性进行拷贝,已达到装饰器不修改原函数的目的。

2)wraps内部通过partial对象和update_wrapper函数实现。

3)partial是一个类,通过实现了双下方法new,自定义实例化对象过程,使得对象内部保留原函数和固定参数,通过实现双下方法call,使得对象可以像函数一样被调用,再通过内部保留的原函数和固定参数以及传入的其它参数进行原函数调用。

python wraps_Python functools.wraps 深入理解相关推荐

  1. python wraps_python functools 中的wraps和 partial

    1.wraps functools.wraps(wrapped[, assigned][, updated]) 当定义一个wrapper函数时,这是一个非常方便的函数,用来调用 update_wrap ...

  2. Python 中 functools.wraps 简介

    在人类世界中有一些大佬,经过细心装扮之后,自身的属性可以变得让人雌雄莫辨.(我没有在说 Abbily,我是在说花木兰!) 在 Python 世界中也是一样,一些函数经过装饰器的悉心装饰之后,一些属性也 ...

  3. 【Python】functools.wraps定义函数装饰器

    第一次见到functools.wraps是在 Flask Web开发 中,一直不明白怎么回事. 装饰器(decorator)是干嘛的?对于受到封装的原函数来说,装饰器能够在那个函数执行前或者执行后分别 ...

  4. python中wraps是什么意思_python 理解functools.wraps

    先复习下装饰器 # coding=utf-8 def logged(func): def with_logging(*args, **kwargs): """ 哈哈哈,这 ...

  5. python functools import wraps_python装饰器中functools.wraps的作用详解

    # 定义一个最简单的装饰器 def user_login_data(f): def wrapper(*args, **kwargs): return f(*args, **kwargs) return ...

  6. python functools.wraps functools.partial实例解析

    一:python functools.wraps 实例 1. 未使用wraps的实例 #!/usr/bin/env python # coding:utf-8def logged(func):def ...

  7. Python 装饰器@functools.wraps(func)

    def wrapper(func):def dec(*args, **kw):print('在{} 函数之前装饰:'.format(func.__name__))func(*args, **kw)re ...

  8. Python functools.wraps 详解

    标准库 functools 中的 wrap 函数用于包装函数, 不改变原有函数的功能, 仅改变原有函数的一些属性, 例如 __name__, __doc__, __annotations__ 等属性 ...

  9. python装饰器的通俗理解_简单理解Python装饰器

    Python有大量强大又贴心的特性,如果要列个最受欢迎排行榜,那么装饰器绝对会在其中. 刚接触装饰器,会觉得代码不多却难以理解.其实装饰器的语法本身挺简单的,复杂是因为同时混杂了其它的概念.下面我们一 ...

最新文章

  1. 领域驱动设计,让程序员心中有码(七)
  2. android自定义push通知_android通过自定义toast实现悬浮通知效果的示例代码
  3. 判断是否为微信环境下打开的网页
  4. 计算机网络 | 应用层 :HTTP协议详解
  5. c语言各种变量的优缺点,C语言优缺点
  6. 读Getting Started With Windows PowerShell笔记
  7. pytorch list转tensor_点赞收藏:PyTorch常用代码段整理合集
  8. sublime快捷键_安利 | sublime
  9. 三星电子时隔近3年再次成为全球最大半导体厂商
  10. python的目的_Python-** wargs的目的和用途是什么?
  11. linux服务器如何添加sudo用户
  12. php resize函数,Php Image Resize图片大小调整的函数代码
  13. Tensorflow-slim 学习笔记(一)概述
  14. 热烈庆贺博客等级V7
  15. 《C专家编程》笔记——第一章
  16. 用maya画凳子_maya椅子模型下载
  17. 科学前沿 AI共拓!AI for Science论坛重磅来袭
  18. Balancer均衡器时段设置
  19. 广播电视相关信息系统安全 等级保护测评要求
  20. 大学四年,靠着这些学习网站,我从挂科学渣变成了别人眼中的大神

热门文章

  1. 实现延时消息的6种方案
  2. Android文件的加密与解密
  3. 数字图像处理-空间域图像增强(一)(图像反转,对数变换,幂次变换、分段线性变换)
  4. 2023河北工程大学计算机考研信息汇总
  5. grub2磁盘安装windows
  6. 关于用python定(和)时(好)发(基)新(友)年(拼)祝(手)福(速)的那些事
  7. 基于centos7.9安装 imagemagick7.1.0
  8. 原来真有外挂,QQ自动抢红包,JAVA可以实现!卧槽
  9. .NET 6 “目标进程已退出,但未引发 CoreCLR 启动事件。请确保将目标进程配置为使用 .NET Core。如果目标进程未运行 .NET Core,则发生这种情况并不意外。”
  10. c++ MP4文件解析