简体   繁体   English

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

[英]Differences between functools.partial and a similar lambda?

In Python, suppose I have a function f that I want to pass around with some secondary arguments (assume for simplicity that it's just the first argument that remains variable).在 Python 中,假设我有一个函数f ,我想传递一些次要参数(为简单起见,假设它只是保持可变的第一个参数)。

What are the differences between doing it these two ways (if any)?这两种方式(如果有的话)之间有什么区别?

# 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)

In the doc page for partial , for example, there is this quote:例如,在partial的文档页面中,有这样一句话:

partial objects defined in classes behave like static methods and do not transform into bound methods during instance attribute look-up.类中定义的partial对象的行为类似于静态方法,并且在实例属性查找期间不会转换为绑定方法。

Will the lambda-method suffer from this if used to make a class method from arguments supplied to the class (either in the constructor or through a function later on)?如果用于从提供给类的参数(在构造函数中或稍后通过函数)创建类方法,lambda 方法是否会受到影响?

  1. A lambda function has the same type as a standard function, so it will behave like an instance method. lambda 函数与标准函数具有相同的类型,因此它的行为类似于实例方法。

  2. The partial object in your example can be called like this:您示例中的partial对象可以这样调用:

     g1(x, y, z)

    leading to this call (not valid Python syntax, but you get the idea):导致这个调用(不是有效的 Python 语法,但你明白了):

     f(*secondary_args, x, y, z, **secondary_kwargs)

    The lambda only accepts a single argument and uses a different argument order. lambda 只接受一个参数并使用不同的参数顺序。 (Of course both of these differences can be overcome – I'm just answering what the differences between the two versions you gave are.) (当然这两种差异都可以克服——我只是回答你给出的两个版本之间的差异是什么。)

  3. Execution of the partial object is slightly faster than execution of the equivalent lambda . partial对象的执行比等效的lambda的执行稍快。

Summary概括

The practical differences between lambda and functools.partial in the common use cases seems to be lambdafunctools.partial在常见用例中的实际区别似乎是

  • functools.partial needs an import, lambda does not. functools.partial需要导入, lambda不需要。
  • The function definition for functions created with functools.partial is visible just by printing the created function.使用functools.partial创建的函数的函数定义仅通过打印创建的函数可见。 The functions created with lambda should be inspected with inspect.getsource() .使用lambda创建的函数应该使用inspect.getsource()检查。

These were found to be practically identical for lambda and functools.partial发现这些对于lambdafunctools.partial实际上是相同

  • Speed速度
  • Tracebacks回溯

Speed (lambda vs functools.partial)速度(lambda 与 functools.partial)

I think that tests and real data speaks louder than just guesses about which one is faster than the another.我认为测试和真实数据比仅仅猜测哪个比另一个更快更能说明问题。

Looks like that there is no statistical proof for speed difference between lambda and functools.partial .看起来lambdafunctools.partial之间的速度差异没有统计证据 I ran different tests with different amount of repetitions, getting slightly different results each time;我以不同的重复次数运行了不同的测试,每次得到的结果都略有不同; any of the three approaches could be the fastest.这三种方法中的任何一种都可能是最快的。 The speeds were identical with 95% (2 sigma) confidence.速度相同,置信度为 95% (2 sigma)。 Here are some numerical results*这是一些数值结果*

# 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)

Tracebacks回溯

I also tried running the f_lambda and f_partial using list with string element inserted, and the tracebacks were equal (except for the very first entry, of course).我还尝试使用插入了字符串元素的列表运行f_lambdaf_partial ,并且回溯是相等的(当然除了第一个条目)。 So there is no difference there.所以那里没有区别。

Inspecting the source code检查源代码

  • The function definition for functions created with functools.partial is visible just by printing the created function.使用functools.partial创建的函数的函数定义仅通过打印创建的函数可见。 The functions created with lambda should be inspected with inspect.getsource() .使用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)

Appendix附录

* Setup used in the tests * 测试中使用的设置

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)

The tests were performed on Python 3.7.3 64-bit (Windows 10).测试是在 Python 3.7.3 64 位 (Windows 10) 上执行的。

partials are not only about 20% faster than equivalent lambdas as already said but they keep a direct ref to they function the relate to.如前所述,partials 不仅比等效的 lambda 快 20%,而且它们保留了一个直接引用,以指示它们函数的相关性。 While in lambdas that function is 'buried' within the function body.而在 lambda 中,该函数被“埋藏”在函数体内。

=> If you need to only solve the problem of deferring evaluation of one function until all args are known then use partials. => 如果您只需要解决将一个函数的评估推迟到所有参数都已知的问题,那么请使用部分函数。 You'll have way better introspection methods compared to bury the calls into anonymous functions, ie lambdas.与将调用埋入匿名函数(即 lambda)相比,您将拥有更好的自省方法。

I believe that the class method thing only applies to functions assigned during class definition.我相信类方法只适用于在类定义期间分配的函数。 Functions assigned later are not treated specially.以后分配的功能不作特殊处理。

Other than that, I'd personally favor lambdas since they're more common and hence make the code easier to understand.除此之外,我个人更喜欢 lambda,因为它们更常见,因此使代码更容易理解。

class Foo(object):
    def __init__(self, base):
        self.int = lambda x:int(x, base)

print Foo(4).int('11')

Yes, lambda will "suffer" from this.是的, lambda将因此而“受苦”。 partial doesn't have this problem because it is an object with the call operator overloaded, rather than a real function. partial没有这个问题,因为它是一个重载了调用运算符的对象,而不是一个真正的函数。

But using a lambda like this in a class definition is just misuse.但是在类定义中使用这样的 lambda 只是误用。

The most important point here is missed - lambda have links to input vars, but partition make a copy of args during creation:这里最重要的一点被遗漏了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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM