Python:为什么需要 functools.partial?

2025-01-20 09:06:00
admin
原创
82
摘要:问题描述:部分应用很酷。functools.partial它提供了哪些无法通过 lambda 实现的功能?>>> sum = lambda x, y : x + y >>> sum(1, 2) 3 >>> incr = lambda y : sum(1, ...

问题描述:

部分应用很酷。functools.partial它提供了哪些无法通过 lambda 实现的功能?

>>> sum = lambda x, y : x + y
>>> sum(1, 2)
3
>>> incr = lambda y : sum(1, y)
>>> incr(2)
3
>>> def sum2(x, y):
    return x + y

>>> incr2 = functools.partial(sum2, 1)
>>> incr2(4)
5

是否functools更有效率,或者更具可读性?


解决方案 1:

functools.partial它提供了哪些无法通过 lambda 获得的功能?

就额外功能而言,没有太多改进(但稍后再看)——而且,可读性是每个人的眼光所及。

大多数熟悉函数式编程语言(尤其是 Lisp/Scheme 系列)的人似乎都很喜欢lambda——我说“大多数”,绝对不是全部,因为 Guido 和我肯定是“熟悉”(等等)的人,但认为lambdaPython 中存在令人讨厌的异常现象……

他后悔曾经将它纳入 Python,而计划将其从 Python 3 中删除,因为这是“Python 的缺陷”之一。

我完全支持他。(我喜欢lambda Scheme ……但它在 Python 中的局限性,以及它与语言其他部分格格不入的奇怪方式让我毛骨悚然)。

然而,对于大批lambda爱好者来说并非如此——他们上演了 Python 历史上最接近叛乱的事件之一,直到 Guido 改变主意并决定离开lambda

几个可能的添加functools(使函数返回常量、身份等)没有实现(以避免明确重复更多lambda功能),尽管partial当然保留了下来(这不是完全重复,也不是碍眼的东西)。

请记住,lambda的主体仅限于表达式因此它有局限性。例如……:

>>> import functools
>>> f = functools.partial(int, base=2)
>>> f.args
()
>>> f.func
<type 'int'>
>>> f.keywords
{'base': 2}
>>> 

functools.partial返回的函数带有可用于自省的属性——它包装的函数,以及它在其中修复的位置和命名参数。此外,命名参数可以直接被覆盖(“修复”在某种意义上是默认值的设置):

>>> f('23', base=10)
23

所以,如你所见,它绝对不像那么简单lambda s: int(s, base=2)!-)

是的,你可以扭曲你的 lambda 来给你其中的一些——例如,对于关键字覆盖,

>>> f = lambda s, **k: int(s, **dict({'base': 2}, **k))

但我真心希望,即使是最狂热的lambda- 爱好者也不会认为这个恐怖的东西比调用更易读partial!-)。“属性设置”部分甚至更难,因为 Python 的“主体是单个表达式”限制lambda(加上赋值永远不能成为 Python 表达式的一部分)...你最终会通过将列表理解扩展到其设计限制之外来“在表达式中伪造赋值”...:

>>> f = [f for f in (lambda f: int(s, base=2),)
           if setattr(f, 'keywords', {'base': 2}) is None][0]

现在将命名参数的可覆盖性加上三个属性的设置组合成一个表达式,然后告诉我它的可读性如何……!

解决方案 2:

嗯,这里有一个显示差异的例子:

In [132]: sum = lambda x, y: x + y

In [133]: n = 5

In [134]: incr = lambda y: sum(n, y)

In [135]: incr2 = partial(sum, n)

In [136]: print incr(3), incr2(3)
8 8

In [137]: n = 9

In [138]: print incr(3), incr2(3)
12 8

Ivan Moore 的这些帖子扩展了 Python 中的“lambda 的局限性”和闭包:

  • Python 中的闭包(第 2 部分)

  • Python 中的闭包(第 3 部分)

解决方案 3:

在最新版本的 Python (>=2.7) 中,可以使用picklepartial但不能使用lambda

>>> pickle.dumps(partial(int))
'cfunctools
partial
p0
(c__builtin__
int
p1
tp2
Rp3
(g1
(tNNtp4
b.'
>>> pickle.dumps(lambda x: int(x))
Traceback (most recent call last):
  File "<ipython-input-11-e32d5a050739>", line 1, in <module>
    pickle.dumps(lambda x: int(x))
  File "/usr/lib/python2.7/pickle.py", line 1374, in dumps
    Pickler(file, protocol).dump(obj)
  File "/usr/lib/python2.7/pickle.py", line 224, in dump
    self.save(obj)
  File "/usr/lib/python2.7/pickle.py", line 286, in save
    f(self, obj) # Call unbound method with explicit self
  File "/usr/lib/python2.7/pickle.py", line 748, in save_global
    (obj, module, name))
PicklingError: Can't pickle <function <lambda> at 0x1729aa0>: it's not found as __main__.<lambda>

解决方案 4:

functools 是否更有效率?

作为对此问题的部分回答,我决定测试一下性能。下面是我的例子:

from functools import partial
import time, math

def make_lambda():
    x = 1.3
    return lambda: math.sin(x)

def make_partial():
    x = 1.3
    return partial(math.sin, x)

Iter = 10**7

start = time.clock()
for i in range(0, Iter):
    l = make_lambda()
stop = time.clock()
print('lambda creation time {}'.format(stop - start))

start = time.clock()
for i in range(0, Iter):
    l()
stop = time.clock()
print('lambda execution time {}'.format(stop - start))

start = time.clock()
for i in range(0, Iter):
    p = make_partial()
stop = time.clock()
print('partial creation time {}'.format(stop - start))

start = time.clock()
for i in range(0, Iter):
    p()
stop = time.clock()
print('partial execution time {}'.format(stop - start))

在 Python 3.3 上,它给出:

lambda creation time 3.1743163756961392
lambda execution time 3.040552701787919
partial creation time 3.514482823352731
partial execution time 1.7113973411608114

这意味着 partial 需要更多的时间来创建,但执行时间却要少得多。这很可能是ars的答案中讨论的早期和晚期绑定的效果。

解决方案 5:

除了 Alex 提到的额外功能之外,functools.partial 的另一个优势是速度。使用 partial,您可以避免构造(和破坏)另一个堆栈框架。

partial 和 lambdas 生成的函数默认都没有文档字符串(尽管您可以通过 为任何对象设置文档字符串__doc__)。

您可以在此博客中找到更多详细信息:Python 中的偏函数应用

解决方案 6:

这是一个非常老的问题,但我想我会把它放在这里以防它对某人有用。

部分函数相对于 lambda 的一个优势在于它们在循环中的行为方式。由于 lambda 评估参数的方式,在使用它们时容易出错的方法是将 lambda 函数声明为循环的一部分(例如菜单项的回调方法)。

例如,这给了你一个答案,不熟悉 lambda 中这种行为的开发人员可能会感到惊讶,

funcs = []
for i in range(10):
    f = lambda j: i + j
    funcs.append(f)
print(f(1) for f in funcs)
# [10, 10, 10, 10, 10, 10, 10, 10, 10, 10]

然而,如果你使用部分,你就不会遇到这个问题;

parts = []
for i in range(10):
    p = partial(sum, (i, ))
    parts.append(p)
print([p(1) for p in parts])
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

当然,使用 lambda 可以解决这个问题,但是使用 partials 的语法(在我看来)更加简单并且行为符合你的预期。

解决方案 7:

我在第三个例子中最快地理解了意图。

当我解析 lambda 表达式时,我期望其复杂性/奇异性比标准库直接提供的要大。

另外,您会注意到第三个示例是唯一一个不依赖于完整签名的示例sum2;从而使其耦合度稍微松散一些。

解决方案 8:

当评估某些变量时,泛函非常有用。

作为一个局外人,这里有一系列更友好的例子:

from functools import partial

sum = lambda x, y: x + y            # sum(x, y) == x + y

n = 2
normalSum = lambda x: sum(x, n)     # normalSum(x) == sum(x, y=n)
partialSum = partial(sum, y = n)    # partialSum(sum(y=n)) == sum(x, 2)
print(normalSum(2), partialSum(2))  # 4 4

n = 6
print(normalSum(2), partialSum(2))  # 8 4

n注意部分如何保存当时的值。

...
n = 2
partialSumOrig = partial(sum, y = n)        # partialSumOrig(sum(y=n)) == sum(x, 2)
n = 6
partialSumNew = partial(sum, y = n)         # partialSumNew(sum(y=n)) == sum(x, 6)

print(partialSumOrig(2), partialSumNew(2))  # 4 8

额外的示例展示了如何将参数传递到嵌套的 lambda 表达式中:

...
n = 8
partialSumOrig = partial(sum, y = n)  # partialSumOrig(sum(y=n)) == sum(x, 8)
partialSumNew = partial(sum, n)       # partialSumNew(sum(n)) == sum(8, y)

print(partialSumOrig(2))  # 10        # partialSumOrig(sum(2, 8)) == sum(2, 8)
print(partialSumNew(2))   # 10        # partialSumNew(sum(8, 2)) == sum(8, 2)

最后一个例子展示了如何在部分参数中传递:

...
n = 2
m = 2
partialSumSilly = partial(sum, n, m)  # partialSumSilly(sum(n, m)) == sum(2, 2)
print(partialSumSilly())              # 4

最大的收获是:

  • normalSum() 行为类似于后期绑定,n在运行时进行评估。

  • partialSum() 行为类似于早期绑定,n在定义时进行评估。

注意:实际上,由于 cpython 的解释特性,几乎所有内容都是后期绑定。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用