在 Python 中部分应用的高效方法?

Performant way to partially apply in Python?

我正在寻找一种方法来部分应用 python 中的函数,该方法易于理解、可读、可重用并且尽可能少地出现编码错误。最重要的是,我希望样式尽可能高效——堆栈上的帧越少越好,部分应用函数的内存占用越少也是可取的。我考虑了 4 种风格,并在下面写了例子:

import functools

def multiplier(m):
    def inner(x):
        return m * x

    return inner

def divide(n,d):
    return n/d

def divider(d):
    return functools.partial(divide,d=d)

times2 = multiplier(2)
print(times2(3))  # 6

by2 = divider(2)
print(by2(6)) # 3.0

by3 = functools.partial(divide,d=3)
print(by3(9)) # 3.0

by4 = lambda n: divide(n,4)
print(by4(12)) # 3.0

我对他们的分析是:

times2是一个嵌套的东西。我想 python 用 m 边界关闭,一切都很好。代码可读(我认为)并且易于理解。没有外部库。这是我今天使用的样式。

by2 有一个明确命名的函数,这对用户来说很简单。它使用 functools,所以它给你额外的导入。我在某种程度上喜欢这种风格,因为它是透明的,如果我愿意,可以选择以 其他 方式使用 divide。将此与无法访问的 inner 进行对比。

by3 类似于 by2,但强制代码的 reader 适应 functools.partial,因为它们把它放在脸上。我不太喜欢的是 PyCharm 不能给我的工具提示 functools.partial 的参数应该是什么,因为它们实际上是 by3 的参数。每次定义一些新的部分应用程序时,我都必须知道 divide 自己的签名。

by4 很容易输入,因为我可以自动完成。它不需要导入 functools。我认为它看起来非 pythonic。此外,我总是对在 python 中使用 lambda 工作的变量/闭包范围界定感到不舒服。永远不确定它的行为方式....

样式之间的逻辑区别是什么?这对内存有何影响 CPU?

第一种方式似乎是最有效的。我调整了您的代码,以便所有 4 个函数计算完全相同的数学函数:

import functools,timeit

def multiplier(m):
    def inner(x):
        return m * x

    return inner

def mult(x,m):
    return m*x

def multer(m):
    return functools.partial(mult,m=m)

f1 = multiplier(2)
f2 = multer(2)
f3 = functools.partial(mult,m=2)
f4 = lambda x: mult(x,2)

print(timeit.timeit('f1(10)',setup = 'from __main__ import f1'))
print(timeit.timeit('f2(10)',setup = 'from __main__ import f2'))
print(timeit.timeit('f3(10)',setup = 'from __main__ import f3'))
print(timeit.timeit('f4(10)',setup = 'from __main__ import f4'))

典型输出(在我的机器上):

0.08207898699999999
0.19439769299999998
0.20093803199999993
0.1442435820000001

这两种 functools.partial 方法是相同的(因为其中一个只是另一个的包装器),第一个快两倍,最后一个介于两者之间(但更接近第一个) .在直接闭包上使用 functools 有明显的开销。由于闭包方法可以说也更具可读性(并且比不能很好地扩展到更复杂函数的 lambda 更灵活)我会接受它。

从技术上讲,您错过了另一个选项,因为 operator.mul 做的事情与您希望做的事情相同,您只需在其上使用 functools.partial 即可获得默认的第一个参数,而无需重新发明轮子。

与自定义函数或 lambda 语句相比,它不仅是最快的选项,而且使用的选项最少 space。它是 partial 的事实就是为什么它使用与其他相同的 space,我认为这是这里最好的路线。

from timeit import timeit
from functools import partial
from sys import getsizeof
from operator import mul

def multiplier(m):
    def inner(x):
        return m * x
    return inner

def mult(x,m):
    return m*x

def multer(m):
    return partial(mult,m=m)

f1 = multiplier(2)
f2 = multer(2)
f3 = partial(mult,m=2)
f4 = lambda n: mult(n,2)
f5 = partial(mul, 2)
from_main = 'from __main__ import {}'.format

print(timeit('f1(10)', from_main('f1')), getsizeof(f1))
print(timeit('f2(10)', from_main('f2')), getsizeof(f2))
print(timeit('f3(10)', from_main('f3')), getsizeof(f3))
print(timeit('f4(10)', from_main('f4')), getsizeof(f4))
print(timeit('f5(10)', from_main('f5')), getsizeof(f5))

输出

0.5278953390006791 144
1.0804575479996856 96
1.0762036349988193 96
0.9348237040030654 144
0.3904160970050725 96

这应该可以回答您关于内存使用和速度的问题。