Python partial

functools模块

functools模块是为了高阶函数(该高阶函数的定义为作用于或返回其它函数的函数)而设置的。一般来说,任何可调用的对象在该模块中都可被当做函数而处理。

这是在关于functools模块的功能总结,但很是晦涩,
换句话说,functools模块支持函数式编程,即将自定义的函数引用作为参数传递给functools模块下某一个功能函数,得到一个可执行的函数对象


partial

functool.partial返回一个调用的partial对象,使用方法按照partial(func, *args, **kwargs)调用。

其发挥的作用在于固定部分参数,从而减少可调用对象的参数个数

from functools import partial

def spam(a, b, c, d):
    print(a, b, c, d)

s1 = partial(spam, 1)  # a = 1
s1(1, 2, 3)  #  1, 1, 2, 3

s2 = partial(spam, 1, 2, d=12)
s2(1)  # 1, 2, 1, 12

可以看出 partial() 固定某些参数并返回一个新的callable对象。这个新的callable接受未赋值的参数, 然后跟之前已经赋值过的参数合并起来,最后将所有参数传递给原始函数。


例子1:根据一个基点来排序点列表集[^乱序]

# coding: utf-8
from functools import partial
from operator import itemgetter
import math

origin = (0, 1)  # 基点
points = [(7, 1), (6, 3), (3, 3)]  # 待排序点集

def distance(p1, p2):
    """
    return the distance between p1 and p2
    """
    x1, y1 = p1
    x2, y2 = p2
    return math.hypot(x1-x2, y1-y2)

sorted_points = sorted(points, key=partial(distance, origin))

print(sorted_points)  # [(3, 3), (6, 3), (7, 1)]

其实,我们是可以通过sorted(points, key=lambda x: distance(x, origin))来实现同样的功能


partial专注于设计模式,从而提高代码的健壮性。

例子2:如下代码使用 multiprocessing.apply_async() 来异步计算一个结果值, 然后这个值被传递给一个接受一个result值和一个可选logging参数的回调函数,通过partial固定了log对象。

from functools import partial
from multiprocessing import Pool
import logging

def output_result(result, logger=None):
    """output result"""
    if logger is not None:
        logger.debug("%d", result)

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

if __name__ == '__main__':
    args = (1, 2)
    logging.basicConfig(level=logging.DEBUG)
    logger = logging.getLogger(__name__)
    p = Pool()  # 工作进程的数量默认使用os.cpu_count()返回的数量
    p.apply_async(add, args, callback=partial(output_result, logger=logger))  # 异步执行
    p.close()
    p.join()  # 主进程需等待所有子进程执行完毕才关闭

例子3:用socketserver模块编写一个易用的网络服务器,实现echo功能

from socketserver import TCPServer, StreamRequestHandler


class TCPEchoHandler(StreamRequestHandler):
    """
    c/s下实现echo请求
    """
    def __init__(self, prefix, *args, **kwargs):
        self.prefix = prefix
        super(TCPEchoHandler).__init__(self, *args, **kwargs)

    def handle(self):
        """
        请求处理
        """
        # rfile带有缓冲区, 支持分行读取
        for line in self.rfile:
            response = 'server get :{data}'.format(data=line.encode('utf-8'))
            self.wfile.write(response)


if __name__ == '__main__':
    addr = ('localhost', 10030)
    server = TCPServer(addr, TCPEchoHandler)
    server.serve_forever()

假设此时,我们需要额外的为TCPEchoHandler增加一个属性,按照常识我们可能会这么做:

1452443-20190922000419926-972916240.png

那如何使得server对象中接收这一属性呢?partial就派上用场了

server = TCPServer(addr, partial(TCPEchoHandler, prefix=b"TCPEchoHandler: \t"))


*特别说明

  • 大部分的partial实现的高阶函数引用是能够采用lambda表达式来替换的(针对于运行时有产出值的情况, 例如排序用到的sorted函数的参数key);
  • partiallambda相比较拥有较高的性能;
  • lambda语义相对较模糊,可读性不高。


源码分析

在分析源码前,我们必须知道

Python中函数也是对象,意味着可以为函数对象动态添加属性

>>> def test(key1, key2):
    pass
>>> type(test)
<class 'function'>
>>> test.kwargs = {'key2': 1}
>>> getattr(test, kwargs)
Traceback (most recent call last):
  File "<pyshell#45>", line 1, in <module>
    getattr(test, kwargs)
NameError: name 'kwargs' is not defined
>>> getattr(test, 'kwargs')
{'key2': 1}

通过以上推导可知,将固定参数[^(不定参数或关键字参数)]重载到原函数即可实现partial


这时我们再来查看partial的源码:

def partial(func, *args, **keywords):
    """New function with partial application of the given arguments
    and keywords.
    """
    if hasattr(func, 'func'):
        args = func.args + args
        tmpkw = func.keywords.copy()
        tmpkw.update(keywords)
        keywords = tmpkw
        del tmpkw
        func = func.func

    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
def add(m, n):
    return m*n

if __name__ == '__main__':
    p = partial(add, n=10)
    print(p(1))

newfunc[^函数对象]将func、固定的不定参数args、固定的关键字参数keywords封装为自己的属性,利用闭包将固定参数与非固定参数(fargs, fkeywords)进行拼接,然后返回该新构造的func对象的调用。


转载于:https://www.cnblogs.com/kisun168/p/11559960.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值