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
); 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
对象的调用。