7.8 减少可调用对象的参数个数

    如果需要减少某个函数的参数个数,你可以使用 。partial() 函数允许你给一个或多个参数设置固定的值,减少接下来被调用时的参数个数。为了演示清楚,假设你有下面这样的函数:

    现在我们使用 partial() 函数来固定某些参数值:

    1. >>> from functools import partial
    2. >>> s1 = partial(spam, 1) # a = 1
    3. >>> s1(2, 3, 4)
    4. 1 2 3 4
    5. >>> s1(4, 5, 6)
    6. 1 4 5 6
    7. >>> s2 = partial(spam, d=42) # d = 42
    8. >>> s2(1, 2, 3)
    9. 1 2 3 42
    10. >>> s2(4, 5, 5)
    11. 4 5 5 42
    12. >>> s3 = partial(spam, 1, 2, d=42) # a = 1, b = 2, d = 42
    13. >>> s3(3)
    14. 1 2 3 42
    15. >>> s3(4)
    16. 1 2 4 42
    17. >>> s3(5)
    18. 1 2 5 42

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

    本节要解决的问题是让原本不兼容的代码可以一起工作。下面我会列举一系列的例子。

    第一个例子是,假设你有一个点的列表来表示(x,y)坐标元组。你可以使用下面的函数来计算两点之间的距离:

    1. points = [ (1, 2), (3, 4), (5, 6), (7, 8) ]
    2.  
    3. import math
    4. def distance(p1, p2):
    5. x1, y1 = p1
    6. x2, y2 = p2
    7. return math.hypot(x2 - x1, y2 - y1)

    更进一步, 通常被用来微调其他库函数所使用的回调函数的参数。例如,下面是一段代码,使用 multiprocessing 来异步计算一个结果值,然后这个值被传递给一个接受一个result值和一个可选logging参数的回调函数:

    1. def output_result(result, log=None):
    2. if log is not None:
    3. log.debug('Got: %r', result)
    4.  
    5. # A sample function
    6. def add(x, y):
    7. return x + y
    8.  
    9. if __name__ == '__main__':
    10. import logging
    11. from multiprocessing import Pool
    12. from functools import partial
    13.  
    14. logging.basicConfig(level=logging.DEBUG)
    15.  
    16. p = Pool()
    17. p.apply_async(add, (3, 4), callback=partial(output_result, log=log))
    18. p.close()
    19. p.join()

    当给 apply_async() 提供回调函数时,通过使用 partial() 传递额外的 logging 参数。而 multiprocessing 对这些一无所知——它仅仅只是使用单个值来调用回调函数。

    作为一个类似的例子,考虑下编写网络服务器的问题, 模块让它变得很容易。下面是个简单的echo服务器:

    1. from socketserver import StreamRequestHandler, TCPServer
    2.  
    3. class EchoHandler(StreamRequestHandler):
    4. def handle(self):
    5. for line in self.rfile:
    6. self.wfile.write(b'GOT:' + line)
    7.  
    8. serv = TCPServer(('', 15000), EchoHandler)
    9. serv.serve_forever()

    不过,假设你想给EchoHandler增加一个可以接受其他配置选项的 init 方法。比如:

    这么修改后,我们就不需要显式地在TCPServer类中添加前缀了。但是你再次运行程序后会报类似下面的错误:

    1. Exception happened during processing of request from ('127.0.0.1', 59834)
    2. Traceback (most recent call last):
    3. ...
    4. TypeError: __init__() missing 1 required keyword-only argument: 'ack'
    1. from functools import partial
    2. serv = TCPServer(('', 15000), partial(EchoHandler, ack=b'RECEIVED:'))
    3. serv.serve_forever()

    在这个例子中,init() 方法中的ack参数声明方式看上去很有趣,其实就是声明ack为一个强制关键字参数。关于强制关键字参数问题我们在7.2小节我们已经讨论过了,读者可以再去回顾一下。

    很多时候 能实现的效果,lambda表达式也能实现。比如,之前的几个例子可以使用下面这样的表达式:

    这样写也能实现同样的效果,不过相比而已会显得比较臃肿,对于阅读代码的人来讲也更加难懂。这时候使用 partial() 可以更加直观的表达你的意图(给某些参数预先赋值)。

    原文: