functools.partial和类似lambda之间的区别?

ely*_*ely 12 python lambda partial functools

在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)
Run Code Online (Sandbox Code Playgroud)

例如,在doc页面中partial,有这样的引用:

partial 在类中定义的对象的行为类似于静态方法,并且在实例属性查找期间不会转换为绑定方法.

如果用于从提供给类的参数(在构造函数中或稍后通过函数)中创建类方法,lambda方法是否会受此影响?

Sve*_*ach 12

  1. lambda函数与标准函数具有相同的类型,因此它的行为类似于实例方法.

  2. partial您的示例中的对象可以像这样调用:

    g1(x, y, z)
    
    Run Code Online (Sandbox Code Playgroud)

    导致这个调用(不是有效的Python语法,但你明白了):

    f(*secondary_args, x, y, z, **secondary_kwargs)
    
    Run Code Online (Sandbox Code Playgroud)

    lambda只接受一个参数并使用不同的参数顺序.(当然这两个差异都可以克服 - 我只是回答你给出的两个版本之间的差异.)

  3. 所述的执行partial对象比等效的执行稍快lambda.

  • @nightcracker:是的,不是现在,而是过去几次.`partial`对象不会创建Python代码对象,因此它保存了一个完整的Python堆栈框架. (3认同)
  • 您是否对“partial”对象与“lambda”进行了比较? (2认同)
  • @nightcracker:大多数时候,只有执行时间很重要,所以这就是我测量的. (2认同)

mrv*_*vol 11

这里忽略了最重要的一点 -lambda有输入变量的链接,但partition在创建过程中复制了参数:

>>> 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
Run Code Online (Sandbox Code Playgroud)
>>> 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
Run Code Online (Sandbox Code Playgroud)

  • 例如,考虑一下如果您为 lambda 情况创建了一个辅助函数“def mk_lambda(k, v): return lambda: print(f'{k}: {v}')”,然后执行了“funcs.append(mk_lambda)” (k, v))`。函数调用“mk_lambda”的归纳将在调用时解析参数值,因此 lambda 稍后将在“funcs”中保留正确的参数。它不是 lambda 的属性,只是 Python 在解决作用域闭包时的选择。 (5认同)
  • 从语言律师的角度来看,这里的解释可能并不完美,正如其他评论所表明的那样——但至少对我来说它立即“点击”了。谢谢@mrvol! (3认同)
  • 换句话说,您演示的观点非常有效,但它是关于函数调用与值赋值引起的参数绑定。使用“lambda”和“partial”演示它只是一个示例,但任何其他因赋值与函数求值而不同的替代方案示例都会显示出相同的效果。 (2认同)

np8*_*np8 5

概括

在实际的差异之间lambda,并functools.partial在常见的用例似乎是

  • functools.partial需要导入,lambda不需要。
  • functools.partial通过打印创建的函数,可以看到创建的函数的函数定义。lambda应使用 来检查使用创建的函数inspect.getsource()。

这些被认为是几乎相同的lambda,并functools.partial

  • 速度
  • 回溯

速度(lambda 与 functools.partial)

我认为测试和真实数据比仅仅猜测哪个比另一个更快更能说明问题。

貌似有速度差异无统计学证据之间lambda和functools.partial。我用不同的重复次数进行了不同的测试,每次得到的结果都略有不同;这三种方法中的任何一种都可能是最快的。速度与 95% (2 sigma) 置信度相同。以下是一些数值结果*

# 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)
Run Code Online (Sandbox Code Playgroud)

回溯

我还尝试运行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)
Run Code Online (Sandbox Code Playgroud)

附录

* 测试中使用的设置

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)
Run Code Online (Sandbox Code Playgroud)

测试是在 Python 3.7.3 64 位 (Windows 10) 上执行的。