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
); partial
與lambda
相比較擁有較高的性能;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
對象的調用。