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: \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對象的調用。



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM