查看原文
其他

python标准库系列教程(二)——functools (上篇)

草yang年华 机器学习与python集中营 2021-09-10

python进阶教程

机器学习

深度学习

长按二维码关注

进入正文


01

声明

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

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、namedtuple

functools是Python中很重要的内置模块,它提供了一些非常有用的高阶函数。所谓高阶函数就是说一个可以接受函数作为参数或者以函数作为返回值的函数,因为Python中函数也是对象,因此很容易支持这样的函数式特性

functools 是python2.5被引人的,一些工具函数放在此包里。

python3.5中

import functools
dir(functools)
['MappingProxyType',
 'RLock',
 'WRAPPER_ASSIGNMENTS',
 'WRAPPER_UPDATES',
 'WeakKeyDictionary',
 '_CacheInfo',
 '_HashedSeq',
 '__all__',
 '__builtins__',
 '__cached__',
 '__doc__',
 '__file__',
 '__loader__',
 '__name__',
 '__package__',
 '__spec__',
 '_c3_merge',
 '_c3_mro',
 '_compose_mro',
 '_convert',
 '_find_impl',
 '_ge_from_gt',
 '_ge_from_le',
 '_ge_from_lt',
 '_gt_from_ge',
 '_gt_from_le',
 '_gt_from_lt',
 '_le_from_ge',
 '_le_from_gt',
 '_le_from_lt',
 '_lru_cache_wrapper',
 '_lt_from_ge',
 '_lt_from_gt',
 '_lt_from_le',
 '_make_key',
 'cmp_to_key',
 'get_cache_token',
 'lru_cache',
 'namedtuple',
 'partial',
 'partialmethod',
 'reduce',
 'singledispatch',
 'total_ordering',
 'update_wrapper',
 'wraps']

python3中增加了更多工具函数,做业务开发时大多情况下用不到,鉴于上面的内容偏多,本文主要介绍使用频率较高的7个函数


01

partial函数(偏函数)

到底什么是偏函数呢?

函数在执行时,要带上所有必要的参数进行调用。但是,有时参数可以在函数被调用之前提前获知。这种情况下,一个函数有一个或多个参数预先就能用上,以便函数能用更少的参数进行调用。举个简单的比喻:我们都听过偏将军吧,在三国时代的官制中,系将军的辅佐,与裨将军两者都为杂号将军;今天我们要讲的偏函数,其实是函数的辅佐,什么意思呢,我们借助Python的help帮助函数或者是转到定义进行查看,发现,它的本质是一个类partial,实现了__call__对象调用方法,类的构造函数和__call__的参数都是*args和*keywords,用来匹配一切可能的参数。

总结:提前为函数绑定一些已经知道的函数参数,而不需要再等到函数调用的时候绑定

把一个函数的某些参数设置默认值,返回一个新的函数,调用这个新函数会更简单。import functools

def showarg(*args, **kw):
    print(args)  #打印位置参数
    print(kw)    #打印关键字参数
    print('------------------------------------')

p1=functools.partial(showarg, 1,2,3)  #提前给*args绑定1,2,3
p1()       #**kw是没有参数的,所以打印为空
p1(4,5,6)  #给*args追加参数
p1(a='python', b='itcast')  #给**kw提前绑定参数
print('++++++++++++++++++++++++++++++++++++++++')
p2=functools.partial(showarg, a=3,b='linux')   #给**kw提前绑定参数
p2()
p2(1,2)
p2(a='python', b='itcast')

'''
运行结果为:
(1, 2, 3)
{}
------------------------------------
(1, 2, 3, 4, 5, 6)
{}
------------------------------------
(1, 2, 3)
{'a': 'python', 'b': 'itcast'}
------------------------------------
++++++++++++++++++++++++++++++++
()
{'a': 3, 'b': 'linux'}
------------------------------------
(1, 2)
{'a': 3, 'b': 'linux'}
------------------------------------
()
{'a': 'python', 'b': 'itcast'}
------------------------------------
'''


下面再举两个简单的小例子加以理解:

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

>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo('10010')
18

basetwo('10010')实际上等价于调用int('10010', base=2)

再看一个小例子:


from functools import partial
def add(a,b):
    return a+b

add(4,3)  #直接调用
plus = partial(add,100)  #先绑定一个参数100
plus(200#调用时再添加另一个参数
'''
7
300
'''

总结

当函数的参数个数太多的时候,可以通过使用functools.partial来创建一个新的函数来简化逻辑从而增强代码的可读性,而partial内部实际上就是通过一个简单的闭包来实现的。

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*args, *fargs, **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

本质

看过我前面的系列文章的一定还有记得装饰器的系列文章,参见:

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

Python高级编程——装饰器Decorator超详细讲解(下篇之python闭包详解)


partial的本质就是一个类装饰器,功能是通过里面定义的构造函数__init__和对象调用__call__实现的。这个很重要,这对于理解一个功能的本质非常有帮助!后面在讲到partial与partialmethod的本质区别的时候也会用到!!!

02

partialmethod


partialmethod虽然与partial看起来似乎很像,但是它们的本质和所要实现的功能是千差万别的,最主要的原因就是他们的本质不一样,

partial本质是类装饰器

partialmethod的本质是一个描述符(类)

同样对于上面的例子,如果我们使用partialmethod,代码如下:

from functools import partial,partialmethod
def add(a,b):
    return a+b

a1=add(4,3)  #直接调用
plus = partialmethod(add,100)  #先绑定一个参数100
a2=plus(200#调用时再添加另一个参数

plus1 = partialmethod(add,1000)  #先绑定一个参数100
a3=plus(2000#调用时再添加另一个参数
print(a1,a2,a3)

运行则会产生如下错误:

TypeError: 'partialmethod' object is not callable

因为partialmethod并没有实现对象调用__call__方法。所以显示不可调用。

不管是partial还是partialmethod,其实都是对函数的绑定,它们的第一个参数都是函数名,这点很重要。

   上面的是全局的普通函数,我们需要使用partial对它进行绑定,而不能使用partialmethod,但是如果我想要绑定的是一个类里面的方法,这该怎么办呢?

    我们首先使用partial试验一下:

import functools
class RGB(object):
    def __init__(self, red, blue, green):
        self._red = red
        self._blue = blue
        self._green = green

    def _color(self, type):   #定义在类中的方法,有一个type参数
        return getattr(self, type)

    red = functools.partial(_color, type='_red') #事先给type参数绑定一个_red参数
    blue = functools.partial(_color, type='_blue')
    green = functools.partial(_color, type='_green')

rgb = RGB(100192240)
print(rgb.red())

运行结果显示:

TypeError: _color() missing 1

required positional argument: 'self'


我们可以再看一个简单点的例子,依然以前面两个数相加作为例子:

import functools
class Person(object):
    def __init__(self, age):
        self.age=age

    def add(self, a,b):
        return self.age+a+b  #在原来年纪上面加上a,b两个数

    age_01 = functools.partial(add, a=3)  #先给a绑定一个参数
    age_02 = functools.partial(add, a=3,b=4#先给a b都绑定一个参数
p=Person(20)
age1=p.age_01(b=4)
age2=p.age_02()
print(age1)
print(age2)

依然会显示上面的错误。


针对上面的问题,如果我想要在一个类里面预先为一个方法绑定参数,该怎么办呢?partialmethod提供了解决方案。


partialmethod和partial类似,我们通过下面这个例子来看一下两者的差异。

先看那个两数相加的例子:

import functools
class Person(object):
    def __init__(self, age):
        self.age=age

    def add(self, a,b):
        return self.age+a+b  #在原来年纪上面加上a,b两个数

    age_01 = functools.partialmethod(add, a=3)  #先给a绑定一个参数
    age_02 = functools.partialmethod(add, a=3,b=4#先给a b都绑定一个参数

age1=p.age_01(b=4)
age2=p.age_02()
print(age1)
print(age2)

运行结果为:

27

27


再看那个RGB颜色的例子:

import functools
class RGB(object):
    def __init__(self, red, blue, green):
        self._red = red
        self._blue = blue
        self._green = green

    def _color(self, type):
        return getattr(self, type)

    red = functools.partialmethod(_color, type='_red')
    blue = functools.partialmethod(_color, type='_blue')
    green = functools.partialmethod(_color, type='_green')

rgb = RGB(100192240)
print(rgb.red())
print(rgb.blue())
print(rgb.green())

运行结果为:

100

192

240

总结

相同点:partial和partialmethod所要实现的功能都是一样的,那就是事先为函数绑定一些已经确定的参数,当函数参数较多的时候,较为实用。

不同点:二者使用情景不同,partial适用于一般的函数,而partialmethod适用于给类里面的函数事先绑定参数。

其实从他们的命名上也可以看出来他们的区别。我们在程序设计中,称定义在类里面的函数为"方法(method)",定义在类外面的为“函数(function)”,这就是为什么在后面添加一个method的原因啦(个人理解)

关于它们内部的实现机理可以查看源代码,会涉及到一些python高级编程的知识,包括对象调用、类装饰器、描述符等等。



2018/08/21

Friday

小伙伴们,学好python的关键标准库,可以让我们的代码更加pythonic哦,对于python标准库functools而言,这两个方法可能是最为常见的得了,partial在面试中也有出现哦,看完这篇文章,是不是有所收获呢?后面还有系列文章连载,请记得关注哦!如果你有需要,就添加我的公众号哦,里面分享有海量资源,包含各类数据、教程等,后面会有更多面经、资料、数据集等各类干货等着大家哦,重要的是全都是免费、无套路分享,有兴趣的小伙伴请持续关注!

推 荐 阅 读

想尽快成为一个顶级的coder?先来把这份攻略拿走吧!

python标准库系列教程(一)——itertools

机器学习面试真题1000题详细讲解(一)

Python高级编程——描述符Descriptor超详细讲解(下篇之描述符三剑客)

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


您的点赞和分享是我们进步的动力!

↘↘↘


: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存