============================ 7.8 å‡å°‘å¯è°ƒç”¨å¯¹è±¡çš„å‚数个数 ============================ ---------- 问题 ---------- ä½ æœ‰ä¸€ä¸ªè¢«å…¶ä»–python代ç 使用的callable对象,å¯èƒ½æ˜¯ä¸€ä¸ªå›žè°ƒå‡½æ•°æˆ–者是一个处ç†å™¨ï¼Œ 但是它的å‚数太多了,导致调用时出错。 ---------- 解决方案 ---------- 如果需è¦å‡å°‘æŸä¸ªå‡½æ•°çš„å‚æ•°ä¸ªæ•°ï¼Œä½ å¯ä»¥ä½¿ç”¨ ``functools.partial()`` 。 ``partial()`` 函数å…è®¸ä½ ç»™ä¸€ä¸ªæˆ–å¤šä¸ªå‚数设置固定的值,å‡å°‘接下æ¥è¢«è°ƒç”¨æ—¶çš„å‚数个数。 为了演示清楚,å‡è®¾ä½ 有下é¢è¿™æ ·çš„函数: .. code-block:: python def spam(a, b, c, d): print(a, b, c, d) 现在我们使用 ``partial()`` 函数æ¥å›ºå®šæŸäº›å‚数值: .. code-block:: python >>> from functools import partial >>> s1 = partial(spam, 1) # a = 1 >>> s1(2, 3, 4) 1 2 3 4 >>> s1(4, 5, 6) 1 4 5 6 >>> s2 = partial(spam, d=42) # d = 42 >>> s2(1, 2, 3) 1 2 3 42 >>> s2(4, 5, 5) 4 5 5 42 >>> s3 = partial(spam, 1, 2, d=42) # a = 1, b = 2, d = 42 >>> s3(3) 1 2 3 42 >>> s3(4) 1 2 4 42 >>> s3(5) 1 2 5 42 >>> å¯ä»¥çœ‹å‡º ``partial()`` 固定æŸäº›å‚数并返回一个新的callable对象。这个新的callable接å—未赋值的å‚数, 然åŽè·Ÿä¹‹å‰å·²ç»èµ‹å€¼è¿‡çš„å‚æ•°åˆå¹¶èµ·æ¥ï¼Œæœ€åŽå°†æ‰€æœ‰å‚æ•°ä¼ é€’ç»™åŽŸå§‹å‡½æ•°ã€‚ ---------- 讨论 ---------- 本节è¦è§£å†³çš„问题是让原本ä¸å…¼å®¹çš„代ç å¯ä»¥ä¸€èµ·å·¥ä½œã€‚下é¢æˆ‘会列举一系列的例å。 第一个例å是,å‡è®¾ä½ 有一个点的列表æ¥è¡¨ç¤º(x,y)åæ ‡å…ƒç»„ã€‚ ä½ å¯ä»¥ä½¿ç”¨ä¸‹é¢çš„函数æ¥è®¡ç®—两点之间的è·ç¦»ï¼š .. code-block:: python points = [ (1, 2), (3, 4), (5, 6), (7, 8) ] import math def distance(p1, p2): x1, y1 = p1 x2, y2 = p2 return math.hypot(x2 - x1, y2 - y1) 现在å‡è®¾ä½ 想以æŸä¸ªç‚¹ä¸ºåŸºç‚¹ï¼Œæ ¹æ®ç‚¹å’ŒåŸºç‚¹ä¹‹é—´çš„è·ç¦»æ¥æŽ’åºæ‰€æœ‰çš„这些点。 列表的 ``sort()`` 方法接å—一个关键å—å‚æ•°æ¥è‡ªå®šä¹‰æŽ’åºé€»è¾‘, 但是它åªèƒ½æŽ¥å—一个å•ä¸ªå‚数的函数(distance()很明显是ä¸ç¬¦åˆæ¡ä»¶çš„)。 现在我们å¯ä»¥é€šè¿‡ä½¿ç”¨ ``partial()`` æ¥è§£å†³è¿™ä¸ªé—®é¢˜ï¼š .. code-block:: python >>> pt = (4, 3) >>> points.sort(key=partial(distance,pt)) >>> points [(3, 4), (1, 2), (5, 6), (7, 8)] >>> 更进一æ¥ï¼Œ``partial()`` 通常被用æ¥å¾®è°ƒå…¶ä»–库函数所使用的回调函数的å‚数。 例如,下é¢æ˜¯ä¸€æ®µä»£ç ,使用 ``multiprocessing`` æ¥å¼‚æ¥è®¡ç®—一个结果值, 然åŽè¿™ä¸ªå€¼è¢«ä¼ 递给一个接å—一个result值和一个å¯é€‰loggingå‚数的回调函数: .. code-block:: python def output_result(result, log=None): if log is not None: log.debug('Got: %r', result) # A sample function def add(x, y): return x + y if __name__ == '__main__': import logging from multiprocessing import Pool from functools import partial logging.basicConfig(level=logging.DEBUG) log = logging.getLogger('test') p = Pool() p.apply_async(add, (3, 4), callback=partial(output_result, log=log)) p.close() p.join() 当给 ``apply_async()`` æ供回调函数时,通过使用 ``partial()`` ä¼ é€’é¢å¤–çš„ ``logging`` å‚数。 而 ``multiprocessing`` å¯¹è¿™äº›ä¸€æ— æ‰€çŸ¥â€”â€”å®ƒä»…ä»…åªæ˜¯ä½¿ç”¨å•ä¸ªå€¼æ¥è°ƒç”¨å›žè°ƒå‡½æ•°ã€‚ 作为一个类似的例å,考虑下编写网络æœåŠ¡å™¨çš„问题,``socketserver`` 模å—让它å˜å¾—很容易。 下é¢æ˜¯ä¸ªç®€å•çš„echoæœåŠ¡å™¨ï¼š .. code-block:: python from socketserver import StreamRequestHandler, TCPServer class EchoHandler(StreamRequestHandler): def handle(self): for line in self.rfile: self.wfile.write(b'GOT:' + line) serv = TCPServer(('', 15000), EchoHandler) serv.serve_forever() ä¸è¿‡ï¼Œå‡è®¾ä½ 想给EchoHandlerå¢žåŠ ä¸€ä¸ªå¯ä»¥æŽ¥å—其他é…置选项的 ``__init__`` 方法。比如: .. code-block:: python class EchoHandler(StreamRequestHandler): # ack is added keyword-only argument. *args, **kwargs are # any normal parameters supplied (which are passed on) def __init__(self, *args, ack, **kwargs): self.ack = ack super().__init__(*args, **kwargs) def handle(self): for line in self.rfile: self.wfile.write(self.ack + line) 这么修改åŽï¼Œæˆ‘们就ä¸éœ€è¦æ˜¾å¼åœ°åœ¨TCPServerç±»ä¸æ·»åŠ å‰ç¼€äº†ã€‚ ä½†æ˜¯ä½ å†æ¬¡è¿è¡Œç¨‹åºåŽä¼šæŠ¥ç±»ä¼¼ä¸‹é¢çš„错误: .. code-block:: python Exception happened during processing of request from ('127.0.0.1', 59834) Traceback (most recent call last): ... TypeError: __init__() missing 1 required keyword-only argument: 'ack' åˆçœ‹èµ·æ¥å¥½åƒå¾ˆéš¾ä¿®æ£è¿™ä¸ªé”™è¯¯ï¼Œé™¤äº†ä¿®æ”¹ ``socketserver`` 模å—æºä»£ç 或者使用æŸäº›å¥‡æ€ªçš„方法之外。 但是,如果使用 ``partial()`` 就能很轻æ¾çš„è§£å†³â€”â€”ç»™å®ƒä¼ é€’ ``ack`` å‚数的值æ¥åˆå§‹åŒ–å³å¯ï¼Œå¦‚下: .. code-block:: python from functools import partial serv = TCPServer(('', 15000), partial(EchoHandler, ack=b'RECEIVED:')) serv.serve_forever() 在这个例åä¸ï¼Œ``__init__()`` 方法ä¸çš„ackå‚数声明方å¼çœ‹ä¸ŠåŽ»å¾ˆæœ‰è¶£ï¼Œå…¶å®žå°±æ˜¯å£°æ˜Žack为一个强制关键å—å‚数。 关于强制关键å—å‚数问题我们在7.2å°èŠ‚我们已ç»è®¨è®ºè¿‡äº†ï¼Œè¯»è€…å¯ä»¥å†åŽ»å›žé¡¾ä¸€ä¸‹ã€‚ 很多时候 ``partial()`` 能实现的效果,lambda表达å¼ä¹Ÿèƒ½å®žçŽ°ã€‚比如,之å‰çš„å‡ ä¸ªä¾‹åå¯ä»¥ä½¿ç”¨ä¸‹é¢è¿™æ ·çš„表达å¼ï¼š .. code-block:: python points.sort(key=lambda p: distance(pt, p)) p.apply_async(add, (3, 4), callback=lambda result: output_result(result,log)) serv = TCPServer(('', 15000), lambda *args, **kwargs: EchoHandler(*args, ack=b'RECEIVED:', **kwargs)) è¿™æ ·å†™ä¹Ÿèƒ½å®žçŽ°åŒæ ·çš„效果,ä¸è¿‡ç›¸æ¯”而已会显得比较臃肿,对于阅读代ç 的人æ¥è®²ä¹Ÿæ›´åŠ 难懂。 这时候使用 ``partial()`` å¯ä»¥æ›´åŠ ç›´è§‚çš„è¡¨è¾¾ä½ çš„æ„图(ç»™æŸäº›å‚数预先赋值)。