Python标准库系列教程——functools

欢迎关注天善智能,我们是专注于商业智能BI,人工智能AI,大数据分析与挖掘领域的垂直社区,学习,问答、求职一站式搞定!

对商业智能BI、大数据分析挖掘、机器学习,python,R等数据领域感兴趣的同学加微信:tstoutiao,邀请你进入数据爱好者交流群,数据爱好者们都在这儿。

作者:草yang年华

个人公众号:机器学习与python集中营

01

声明

functools, itertools, operator是Python标准库为我们提供的支持函数式编程的三大模块,合理的使用这三个模块,我们可以写出更加简洁可读的Pythonic代码,本次的系列文章将介绍并使用这些python自带的标准模块,系列文章分篇连载,此为第二篇,有兴趣的小伙伴后面记得关注学习哦!

3901436-f4dd557f96b808c8

Python标准库之functools

目录

1 partial函数

2 partialmethod函数

3 reduce函数

4 wraps函数

5 update_wrapper函数

6 singledispatch函数

7 total_ordering函数

8 namedtuple函数

鉴于篇幅较长,functools模块将分为三篇进行说明,

上篇:partial、partialmethod

中篇:reduce、wraps、update_wrapper

下篇:singledispatch、total_ordering

前面的python标准库系列教程(二)——functools (上篇)中已经详细讲解了partial函数和partialmethod函数的使用,本文为系列文章的中篇,将详细分析reduce、wraps、upgrade_wrapper三个函数的使用方法,特别是wraps、upgrade_wrapper,都是从源码进行剖析哦,认真看完的小伙伴一定会大有所获的。后续还有下篇,请持续关注!

03

reduce函数

reduce函数的前身是来自于python2.x版本的内置函数reduce,但是在python3中该内置函数被取消了,但是由于该函数提供的功能又非常便捷,所以将其放到了functools模块里面了。

首先我们从它的定义开始看起:

reduce(function, sequence[, initial]) -> value

Apply a function of two arguments cumulatively to the items of a sequence,

fromleft to right, soasto reduce the sequence to a single value.

For example, reduce(lambdax, y: x+y, [1,2,3,4,5]) calculates

((((1+2)+3)+4)+5).  If initialispresent, itisplaced before the items

of the sequenceinthe calculation,andservesasa default when the

sequenceisempty.

从上面的定义可以看出,该函数有两个必选参数

function,而且要求这个函数必须要有接收两个参数,当然可以使lambda表达式;

sequence,要处理的序列

initial,可选参数,如果没给出这个可选参数

则第一次调用传递sequence的两个元素,

如果给出initial, 则第一次传递initial和sequence的第一个元素给function.

那reduce函数到底是干什么的,有什么用呢?

其实用传给reduce中的函数 func()(必须是一个二元操作函数)先对集合中的第1,2个数据进行func()操作,得到的结果再与第3个数据用func()函数运算,依次下去最后得到一个结果。

注意:对于一些常见的“累乘”、“累加”操作,使用该方法非常快捷;

最后返回的是一个结果(一个数),而不是一个序列哦!

实例一:reduce实现累加

fromfunctoolsimportreduce

a=[1,2,3,4,5]

b=reduce(lambdax,y:x+y,a)  #使用的lambda表达式

c=reduce(lambdax,y:x+y,a,10)

print(b)

print(c)

运行结果为:

15

25

实例二:reduce实现累乘

fromfunctoolsimportreduce

a=[1,2,3,4,5]

b=reduce(lambdax,y:x*y,a)

c=reduce(lambdax,y:x*y,a,10)

print(b)

print(c)

运行结果为:

120

1200

04

wraps函数

使用装饰器时,有一些细节需要被注意。例如,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变)。

为什么会发生这样的改变呢?参见前面的系列文章:

Python高级编程——装饰器Decorator超详细讲解(中篇)

Python高级编程——装饰器Decorator超详细讲解(补充篇——嵌套装饰器)

添加后由于函数名和函数的doc发生了改变,对测试结果有一些影响,例如:

deftest():

'i am test'

print('一个简单的实验')

test()

print(test.__doc__)

print(test.__name__)

运行结果为:

一个简单的实验

i am test

test

如果使用了一个普通的装饰器之后,代码如下

defdecorator(function):

'i am decorator'

defwrapper():

'i am wrapper'

print('添加额外功能')

function()

print('添加完毕')

returnwrapper

@decorator

deftest():

'i am test'

print('一个简单的实验')

test()

print(test.__doc__)

print(test.__name__)

运行结果为:

添加额外功能

一个简单的实验

添加完毕

i am wrapper

wrapper

我们发现,test函数的名称__name_

_变成了wrapper,说明文档__doc__也变成了wrapper的说明文档,这是为什么?想知道具体原因的小伙伴请参见我的前面的系列文章:

Python高级编程——装饰器Decorator超详细讲解(中篇)

Python高级编程——装饰器Decorator超详细讲解(补充篇——嵌套装饰器)

换句话说,此时的test早已经不是当初的test了,虽然使用装饰器达到了添加额外功能的作用,但是也产生了一些副作用,即装饰器会遗失被装饰函数的__name__和__doc__等属性。所以,Python的functools包中提供了一个叫wraps的装饰器来消除这样的副作用,例如:

importfunctools

defdecorator(function):

'i am decorator'

@functools.wraps(function)#就是在wrapper上面再添加这样一个装饰器

defwrapper():

'i am wrapper'

print('添加额外功能')

function()

print('添加完毕')

returnwrapper

@decorator

deftest():

'i am test'

print('一个简单的实验')

test()

print(test.__doc__)

print(test.__name__)

运行结果为:

添加额外功能

一个简单的实验

添加完毕

i am test

test

我们发现,就是在wrapper上面再添加一个

@functools.wraps(function)

就不会再改变test本身的__doc__和__name__属性了

——————————————————————

那么这个功能到底是怎么实现的,可以参考它的实现源代码,可以采用我前面讲解装饰器的时候的“逐步调试”,跟踪函数每一次的运行状态,来了解它的实现机制。

实际上wraps的内部是使用update_wrapper来实现的,这也是下面要讲的一个函数。

05

update_wrapper函数

前面已经说过,wraps函数内部是通过update_wrapper函数来实现的,我们可以查看一下它的定义:

defwraps(wrapped,assigned = WRAPPER_ASSIGNMENTS,updated = WRAPPER_UPDATES):

returnpartial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)

由此可见,wraps其实就是使用partial预先给upgrade_wrapper函数绑定了参数,至于这些参数wrapped、assigned、updated是什么?这就要去看upgrade_wrapper的函数定义了。

什么是updated_wrapper?

其实它和我们的wraps实现的是一样的功能,只不过它更加接近底层,它俩所要解决的问题都是:针对装饰器改变被装饰函数的函数名、函数说明的属性这一现象而提出来的。我们前面说过了,被装饰的函数实际上变成了内层函数wrapper,所以被装饰函数的一切属性都变得和wrapper一样了。

首先我们查看updated_wrapper的函数定义如下:(删除了注释部分,添加了自己的注解)

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

WRAPPER_UPDATES = ('__dict__',)

defupdate_wrapper(wrapper,

wrapped,

assigned = WRAPPER_ASSIGNMENTS,

updated = WRAPPER_UPDATES)

:

'''

wrapper:指的是装饰器的内层wrapper函数

wrapped:指的是被装饰的函数,比如前面例子中的test函数

assigned:指的是那些会因为装饰器而改变的属性

updated:指的是更新之后的字典属性

'''

forattrinassigned:#第一个循环,遍历那些会被改变的属性

try:

value = getattr(wrapped, attr)#得到被装饰函数的属性值

exceptAttributeError:

pass

else:

setattr(wrapper, attr, value)#将wrapper函数的这些属性,设置为被装饰函数的那些属性值

forattrinupdated:

getattr(wrapper, attr).update(getattr(wrapped, attr, {}))#更新wrapper的字典__dict__,把新设置的那些属性值更新进去

wrapper.__wrapped__ = wrapped#包装器函数wrapper都有一个__wrapped__属性。指向被包装的那个函数

returnwrapper#再返回那个wrapper包装器

相信有一定python基础的小伙伴看到这里已经恍然大悟了,原来就是这么一回事啊。

如果还是不懂,也没关系,我用尽量通俗的语言来表述,它的大致思想如下:

这里我假设被包装函数为test,test就是wrapped既然被包装函数test在被装饰器包装之后,它会变成wrapper,它的相关属性会变成wrapper的相关属性,那我们就这么解决吧,我把wrapper的那些属性重新赋值,赋值什么呢?就将test的那些属性值赋值给wrapper对应的那些属性,然后这些更新的属性使用wrapper的__dict__进行更新,这样一来,虽然我的test变成了wrapper,但是我访问它相关的属性还是得到我本身test的相关属性,因为我将wrapper的属性重新设置为了test的属性值,正式通过这样的方法,来保证了test即使被装饰器包装,依然不改变它的相关属性值。

基本结论总结:

(1)wraps和updated_wrapper的实现原理是一样的,wraps是后者进一步的包装;

(2)从前面的例子可以看出,wraps和updated_wrapper的作用对象都是内层包装器wrapper。这就是为什么前面讲wraps时为什么将@wraps放在wrapper上面的原因了。

(3)它们本质上是属于挂羊头卖狗肉的行为,因为test(wrapped)从本质上来说还是wrapper,我们之所一通过__name__、__doc__属性看不是这样子,只是因为我故意把wrapper的属性改成了wrapped的一样的了。

(4)这样做的好处是,对于我们调试来说非常有帮助,我们就知道函数的名称到底是哪一个,不会再弄混了。

(5)我们还可以自定义WRAPPER_ASSIGNMENTS里面的那些值,来约束test(wrapped)和wrapper的属性一样。

自己的实现同样的效果:

其实要保持test被装饰后的相关属性不改变,我们完全可以不用上面的wraps和upgrade_wrapper,我们知道它的设计原理之后,就可以自己实现了。如下:

importfunctools

defdecorator(function):

'i am decorator'

defwrapper():

'i am wrapper'

print('添加额外功能')

function()

print('添加完毕')

wrapper.__doc__=function.__doc__#是wrapper和test的__doc__属性保持一致

wrapper.__name__=function.__name__#是wrapper和test的__name__属性保持一致

returnwrapper

@decorator

deftest():

'i am test'

print('一个简单的实验')

test()

print(test.__doc__)

print(test.__name__)

运行结果为:

添加额外功能

一个简单的实验

添加完毕

i am test

test

由此可见,我自己只需要添加上面代码注释中的两句话可以达到同样的效果。

核心思想:保持wrapper和test的属性值一样

现在我们使用这两个特殊函数实现,代码如下:

importfunctools

defdecorator(function):

'i am decorator'

defwrapper():

'i am wrapper'

print('添加额外功能')

function()

print('添加完毕')

returnfunctools.update_wrapper(wrapper,function)

@decorator

deftest():

'i am test'

print('一个简单的实验')

test()

print(test.__doc__)

print(test.__name__)

运行的结果为:

添加额外功能

一个简单的实验

添加完毕

i am test

test

总结

upgrade_wrapper和wraps的实现原理完全一样,它们的一般模板如下所示:

(1)upgrade_wrapper模板

defdecorator(function):

'i am decorator'

defwrapper():

'i am wrapper'

'添加额外功能'

function()

'添加额外功能'

returnfunctools.update_wrapper(wrapper,function)

#因为upgrade_wrapper函数返回的就是wrapper

(2)wraps模板

defdecorator(function):

'i am decorator'

@functools.wraps(function)

defwrapper():

'i am wrapper'

'添加额外功能'

function()

'添加额外功能'

returnwrapper

上面的两者完全等价。

Python的爱好者社区历史文章大合集

2018年Python爱好者社区历史文章合集(作者篇)

福利:文末扫码关注公众号,“Python爱好者社区”,开始学习Python课程:

关注后在公众号内回复“ 课程 ”即可获取:

小编的转行入职数据科学(数据分析挖掘/机器学习方向)【最新免费】

小编的Python的入门免费视频课程

小编的Python的快速上手matplotlib可视化库!

崔老师爬虫实战案例免费学习视频。

陈老师数据分析报告扩展制作免费学习视频。

玩转大数据分析!Spark2.X + Python精华实战课程免费学习视频。

3901436-3ec0b74206cbf092
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值