在Python中,假设我有一个函数f
,我想用一些辅助参数传递它(为了简单起见,假设它只是第一个保持可变的参数)。
这两种方式(如果有的话)有什么区别?
# Assume secondary_args and secondary_kwargs have been defined
import functools
g1 = functools.partial(f, *secondary_args, **secondary_kwargs)
g2 = lambda x: f(x, *secondary_args, **secondary_kwargs)
例如,在partial
的文档页面中,有这样一句话:
类中定义的
partial
对象的行为类似于静态方法,并且在实例属性查找过程中不会转换为绑定方法。
如果lambda方法用于根据提供给类的参数(在构造函数中或稍后通过函数)生成类方法,它会受到这种影响吗?
-
lambda函数与标准函数具有相同的类型,因此它的行为类似于实例方法。
-
示例中的
partial
对象可以这样调用:g1(x, y, z)
导致这个调用(不是有效的Python语法,但你已经明白了):
f(*secondary_args, x, y, z, **secondary_kwargs)
lambda只接受一个参数,并使用不同的参数顺序。(当然,这两个差异都是可以克服的——我只是在回答你给出的两个版本之间的差异。)
-
partial
对象的执行略快于等效lambda
的执行。
摘要
常见用例中lambda
和functools.partial
之间的实际差异似乎是
functools.partial
需要导入,lambda
不需要- 使用
functools.partial
创建的函数的函数定义只需打印创建的函数即可查看。使用lambda
创建的函数应使用inspect.getsource()
进行检查
对于lambda
和functools.partial
,发现它们实际上是相同的
- 速度
- 追溯
速度(lambda与functools.partial)
我认为测试和真实数据比猜测哪一个比另一个更快更能说明问题。
看起来lambda
和functools.partial
之间的速度差没有统计证据。我进行了不同的测试,重复次数不同,每次得到的结果略有不同;这三种方法中的任何一种都可能是最快的。速度相同,置信度为95%(2西格玛)。以下是一些数值结果*
# When functions are defined beforehand
In [1]: timeit -n 1000 -r 1000 f_partial(data)
23.6 µs ± 2.92 µs per loop (mean ± std. dev. of 1000 runs, 1000 loops each)
In [2]: timeit -n 1000 -r 1000 f_lambda(data)
22.6 µs ± 2.6 µs per loop (mean ± std. dev. of 1000 runs, 1000 loops each)
# When function is defined each time again
In [3]: timeit -n 1000 -r 1000 (lambda x: trim_mean(x, 0.1))(data)
22.6 µs ± 1.98 µs per loop (mean ± std. dev. of 1000 runs, 1000 loops each)
In [4]: timeit -n 1000 -r 1000 f_lambda = lambda x: trim_mean(x, 0.1); f_lambda(data)
23.7 µs ± 3.89 µs per loop (mean ± std. dev. of 1000 runs, 1000 loops each)
In [5]: timeit -n 1000 -r 1000 f_partial = partial(trim_mean, proportiontocut=0.1); f_partial(data)
24 µs ± 3.38 µs per loop (mean ± std. dev. of 1000 runs, 1000 loops each)
追溯
我还尝试使用插入了字符串元素的list运行f_lambda
和f_partial
,并且回溯是相等的(当然,除了第一个条目)。所以没有区别。
检查源代码
- 使用
functools.partial
创建的函数的函数定义只需打印创建的函数即可查看。使用lambda
创建的函数应使用inspect.getsource()
进行检查
# Can be inspected with just printing the function
In [1]: f_partial
Out[1]: functools.partial(<function trim_mean at 0x000001463262D0D0>, proportiontocut=0.1)
In [2]: print(f_partial)
functools.partial(<function trim_mean at 0x000001463262D0D0>, proportiontocut=0.1)
# Lambda functions do not show the source directly
In [3]: f_lambda
Out[3]: <function __main__.<lambda>(x)>
# But you can use inspect.getsource()
In [4]: inspect.getsource(f_lambda)
Out[4]: 'f_lambda = lambda x: trim_mean(x, 0.1)n'
# This throws a ValueError, though.
In [5]: inspect.getsource(f_partial)
附录
*测试中使用的设置
from functools import partial
from scipy.stats import trim_mean
import numpy as np
data = np.hstack((np.random.random(1000), np.random.random(50)*25000))
f_lambda = lambda x: trim_mean(x, 0.1)
f_partial = partial(trim_mean, proportiontocut=0.1)
测试是在Python 3.7.3 64位(Windows 10)上进行的。
这里忽略了最重要的一点-lambda
有到输入变量的链接,但partition
在创建过程中复制了一个args:
>>> for k,v in {"1": "2", "3": "4"}.items():
... funcs.append(lambda: print(f'{k}: {v}'))
...
>>> print(funcs)
[<function <lambda> at 0x106db71c0>, <function <lambda> at 0x10747a3b0>]
>>> for f in funcs:
... f()
...
3: 4 # result are indentical
3: 4
>>> import functools
>>> funcs = []
>>> for k,v in {"1": "2", "3": "4"}.items():
... funcs.append(functools.partial(print, f'{k}: {v}'))
...
>>> print(funcs)
[functools.partial(<built-in function print>, '1: 2'), functools.partial(<built-in function print>, '3: 4')]
>>>
>>> for f in funcs:
... f()
...
1: 2 # result differs
3: 4
分部不仅比前面所说的等效lambda快20%,而且它们保留了对其相关函数的直接引用。而在lambda中,该函数"隐藏"在函数体中。
=>如果只需要解决将一个函数的求值延迟到所有参数都已知的问题,那么就使用分部。与将调用嵌入匿名函数(即lambdas)相比,您将拥有更好的内省方法。
我相信类方法只适用于在类定义期间分配的函数。稍后分配的函数不作特殊处理。
除此之外,我个人更喜欢lambdas,因为它们更常见,因此使代码更容易理解。
class Foo(object):
def __init__(self, base):
self.int = lambda x:int(x, base)
print Foo(4).int('11')
是的,lambda
将因此"受苦"。partial
没有这个问题,因为它是一个重载了调用运算符的对象,而不是一个真正的函数。
但是在类定义中使用这样的lambda只是误用。