• 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增加一个属性,按照常识我们可能会这么做:

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

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

    *特别说明

    • 大部分的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对象的调用。


  • 相关阅读:
    DMA详解
    Python实现爬虫设置代理IP和伪装成浏览器的方法(转载)
    Python3 简单验证码识别思路及实例
    Python3 比较两个图片是否类似或相同
    python3 验证码图片切割
    python3 验证码去噪
    Python3 OpenCV应用
    python3 IEDriver抓取时报数据
    Impala 数值函数大全(转载)
    java 获取指定日前的前一天
  • 原文地址:https://www.cnblogs.com/kisun168/p/11559960.html
Copyright © 2020-2023  润新知