哪个更可取:lambda 函数或嵌套函数('def')?

Posted

技术标签:

【中文标题】哪个更可取:lambda 函数或嵌套函数(\'def\')?【英文标题】:Which is more preferable to use: lambda functions or nested functions ('def')?哪个更可取:lambda 函数或嵌套函数('def')? 【发布时间】:2010-09-13 04:10:00 【问题描述】:

我主要使用 lambda 函数,但有时使用似乎提供相同行为的嵌套函数。

这里有一些简单的例子,如果在另一个函数中找到它们,它们在功能上会做同样的事情:

Lambda 函数

>>> a = lambda x : 1 + x
>>> a(5)
6

嵌套函数

>>> def b(x): return 1 + x

>>> b(5)
6

使用其中一种是否有优势? (性能?可读性?限制?一致性?等等)

这还重要吗?如果没有,那是否违反了 Pythonic 原则:

There should be one-- and preferably only one --obvious way to do it..

【问题讨论】:

【参考方案1】:

如果您需要将lambda 分配给名称,请改用defdefs 只是赋值的语法糖,所以结果是一样的,而且它们更加灵活和可读。

lambdas 可用于一次性使用没有名称的函数。

但是,这种用例非常少见。您很少需要传递未命名的函数对象。

内置函数map()filter() 需要函数对象,但列表推导生成器表达式通常比这些函数更具可读性,可以涵盖所有用例,无需 lambdas。

如果你真的需要一个小函数对象,你应该使用operator模块函数,比如operator.add而不是lambda x, y: x + y

如果您仍然需要一些未涵盖的lambda,您可以考虑编写def,只是为了更具可读性。如果函数比operator 模块中的函数更复杂,def 可能会更好。

所以,现实世界中好的 lambda 用例非常少见。

【讨论】:

我同意何时使用lambda 的答案,但我不同意这是“非常罕见的”,对于sorteditertools.groupby 等的关键功能很常见,例如sorted(['a1', 'b0'], key= lambda x: int(x[1]))【参考方案2】:

实际上,对我来说有两个不同:

首先是关于他们做什么和他们回报什么:

def 是一个关键字,它不返回任何内容并在本地命名空间中创建一个“名称”。

lambda 是返回函数对象的关键字,不会在本地命名空间中创建“名称”。

因此,如果您需要调用带有函数对象的函数,在一行 python 代码中执行此操作的唯一方法是使用 lambda。 def 没有等价物。

在某些框架中,这实际上很常见;例如,我经常使用Twisted,所以会这样做

d.addCallback(lambda result: setattr(self, _someVariable, result))

很常见,使用 lambda 更简洁。

第二个区别是关于实际函数可以做什么。

用'def'定义的函数可以包含任何python代码 使用 'lambda' 定义的函数必须计算为表达式,因此不能包含 print、import、raise 等语句...

例如,

def p(x): print x

按预期工作,而

lambda x: print x

是一个语法错误。

当然,有一些解决方法 - 将 print 替换为 sys.stdout.write,或将 import 替换为 __import__。但在这种情况下,通常你最好使用函数。

【讨论】:

【参考方案3】:

In this interview, Guido van Rossum 说他希望他没有让“lambda”进入 Python:

"问。您最不满意 Python 的哪个功能? 有时我接受捐款太快了,后来意识到这是一个错误。一个例子是一些函数式编程特性,例如 lambda 函数。 lambda 是一个关键字,可让您创建一个小型匿名函数;内置函数(如 map、filter 和 reduce)在序列类型(如列表)上运行函数。 在实践中,结果并不是那么好。 Python 只有两个作用域:本地和全局。这使得编写 lambda 函数很痛苦,因为您经常希望访问定义 lambda 的范围内的变量,但由于有两个范围,您不能。有一种方法可以解决这个问题,但它有点像一个kludge。通常,在 Python 中使用 for 循环而不是使用 lambda 函数似乎更容易。只有当已经有一个内置函数可以满足您的需求时,地图和朋友才能正常工作。

恕我直言,Iambdas 有时很方便,但通常以牺牲可读性为代价。你能告诉我这是做什么的吗:

str(reduce(lambda x,y:x+y,map(lambda x:x**x,range(1,1001))))[-10:]

我写了它,我花了一分钟才弄明白。这是来自 Project Euler - 我不会说是哪个问题,因为我讨厌剧透,但它在 0.124 秒内运行 :)

【讨论】:

请注意,采访相当陈旧,Python 早就添加了嵌套作用域,这使得他针对 lambda 给出的论点不再相关。我敢肯定他仍然对 lambda 感到遗憾,但还不足以在 Python 3.0 中将其删除。 真的你的例子应该是一个反对单行的论据,而不是 lambdas。此外,您应该使用内置的 sum 函数而不是使用 lambda 进行归约: str(sum(map(lambda x:x**x, range(1001))))[:-10] @ThomasWouters:我知道lambda 在 3.0 中不被删除是一件很近的事情,而且 Guido 并没有努力保留它。【参考方案4】:

对于 n=1000,这是调用函数与 lambda 的一些时间:

In [11]: def f(a, b):
             return a * b

In [12]: g = lambda x, y: x * y

In [13]: %%timeit -n 100
for a in xrange(n):
  for b in xrange(n):
    f(a, b)
   ....:
100 loops, best of 3: 285 ms per loop

In [14]: %%timeit -n 100
for a in xrange(n):
  for b in xrange(n):
    g(a, b)
   ....:
100 loops, best of 3: 298 ms per loop

In [15]: %%timeit -n 100
for a in xrange(n):
  for b in xrange(n):
    (lambda x, y: x * y)(a, b)
   ....:
100 loops, best of 3: 462 ms per loop

【讨论】:

有趣的是,lambda 和定义的版本大致相同。最后一次测试花费了更多时间,因为 python 可能需要在每次定义该 lambda 函数时分配空间。 我想这是有道理的,因为定义可能会引用局部变量(可能已经改变)......尽管在没有像这里这样的情况下,cpython 可以做得更好。 使用dis.dis;您的 (lambda x, y: x * y) 在每个循环中创建函数。如果您在循环之前创建 lambda(又名 f = lambda x, y: x * y),则调用该函数的字节码将与您之前示例中的 g/f 完全相同,因此 lambda 性能是相同的作为 def 函数。因此,如果您使用相同的 lambda 或 def ,则没有影响。做相反的事情,在循环中声明 f() 函数,然后调用它... @tito 我相信这正是 3 个定时示例所展示的...... @tito 哦,你说的是在循环中定义函数,当然,但我认为这是一种不寻常的模式。不知道为什么这需要对该评论投反对票...【参考方案5】:

更可取:lambda 函数或嵌套函数 (def)?

与常规函数相比,使用 lambda 有一个优势:它们是在表达式中创建的。

有几个缺点:

没有名字(只有'<lambda>') 没有文档字符串 没有注释 没有复杂的语句

它们也是相同类型的对象。由于这些原因,我通常更喜欢使用 def 关键字而不是 lambdas 创建函数。

第一点 - 它们是同一类型的对象

lambda 产生与常规函数相同类型的对象

>>> l = lambda: 0
>>> type(l)
<class 'function'>
>>> def foo(): return 0
... 
>>> type(foo)
<class 'function'>
>>> type(foo) is type(l)
True

由于 lambda 是函数,因此它们是一流的对象。

lambda 和函数:

可以作为参数传递(与常规函数相同) 在外部函数中创建时,会成为该外部函数局部变量的闭包

但默认情况下,lambdas 缺少一些函数通过完整函数定义语法获得的东西。

兰巴的__name__'&lt;lambda&gt;'

Lambda 毕竟是匿名函数,所以它们不知道自己的名字。

>>> l.__name__
'<lambda>'
>>> foo.__name__
'foo'

因此无法在其命名空间中以编程方式查找 lambda。

这限制了某些事情。比如foo可以用序列化代码查找,而l不能:

>>> import pickle
>>> pickle.loads(pickle.dumps(l))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
_pickle.PicklingError: Can't pickle <function <lambda> at 0x7fbbc0464e18>: 
attribute lookup <lambda> on __main__ failed

我们可以很好地查找foo - 因为它知道自己的名字:

>>> pickle.loads(pickle.dumps(foo))
<function foo at 0x7fbbbee79268>

Lambda 没有注释也没有文档字符串

基本上,没有记录 lambda。让我们重写 foo 以便更好地记录:

def foo() -> int:
    """a nullary function, returns 0 every time"""
    return 0

现在,foo 有文档:

>>> foo.__annotations__
'return': <class 'int'>
>>> help(foo)
Help on function foo in module __main__:

foo() -> int
    a nullary function, returns 0 every time

然而,我们没有相同的机制向 lambdas 提供相同的信息:

>>> help(l)
Help on function <lambda> in module __main__:

<lambda> lambda (...)

但我们可以破解它们:

>>> l.__doc__ = 'nullary -> 0'
>>> l.__annotations__ = 'return': int
>>> help(l)
Help on function <lambda> in module __main__:

<lambda> lambda ) -> in
    nullary -> 0

但是,可能有一些错误弄乱了帮助的输出。

Lambda 只能返回一个表达式

Lambda 不能返回复杂的语句,只能返回表达式。

>>> lambda: if True: 0
  File "<stdin>", line 1
    lambda: if True: 0
             ^
SyntaxError: invalid syntax

当然,表达式可能相当复杂,如果您非常努力尝试,您可能可以使用 lambda 完成相同的操作,但增加的复杂性更不利于编写清晰的代码。

为了清晰和可维护性,我们使用 Python。过度使用 lambda 可能会对此产生不利影响。

lambda 的唯一优点:可以在单个表达式中创建

这是唯一可能的好处。由于您可以使用表达式创建 lambda,因此您可以在函数调用中创建它。

在函数调用中创建一个函数避免了(廉价的)名称查找,而不是在其他地方创建的。

但是,由于 Python 是经过严格评估的,因此除了避免名称查找之外,这样做没有其他性能提升。

对于一个非常简单的表达式,我可能会选择一个 lambda。

在执行交互式 Python 时,我也倾向于使用 lambda,以避免在需要时使用多行代码。当我想在调用timeit.repeat 时将参数传递给构造函数时,我使用以下代码格式:

import timeit

def return_nullary_lambda(return_value=0):
    return lambda: return_value

def return_nullary_function(return_value=0):
    def nullary_fn():
        return return_value
    return nullary_fn

现在:

>>> min(timeit.repeat(lambda: return_nullary_lambda(1)))
0.24312214995734394
>>> min(timeit.repeat(lambda: return_nullary_function(1)))
0.24894469301216304

我相信上面的细微时差可以归因于return_nullary_function 中的名称查找 - 请注意,它非常可以忽略不计。

结论

Lambda 适用于您希望尽量减少代码行数以支持创建奇异点的非正式情况。

Lambda 不适用于更正式的情况,在这种情况下,您需要稍后来的代码编辑人员清楚地了解,尤其是在它们不重要的情况下。

我们知道我们应该给我们的对象起个好名字。当对象有 no 名称时,我们如何做到这一点?

出于所有这些原因,我通常更喜欢使用 def 而不是 lambda 创建函数。

【讨论】:

【参考方案6】:

性能:

使用lambda 创建函数比使用def 创建函数要快。不同之处在于 def 在 locals 表中创建了一个名称条目。生成的函数具有相同的执行速度。


可读性:

对于大多数 Python 用户来说,Lambda 函数的可读性稍差,但在某些情况下也更加简洁。考虑从使用非功能性例程转换为功能性例程:

# Using non-functional version.

heading(math.sqrt(v.x * v.x + v.y * v.y), math.atan(v.y / v.x))

# Using lambda with functional version.

fheading(v, lambda v: math.sqrt(v.x * v.x + v.y * v.y), lambda v: math.atan(v.y / v.x))

# Using def with functional version.

def size(v):
    return math.sqrt(v.x * v.x + v.y * v.y)

def direction(v):
    return math.atan(v.y / v.x)

deal_with_headings(v, size, direction)

如您所见,lambda 版本更短且“更容易”,因为您只需将lambda v: 添加到原始非功能版本即可转换为功能版本。它也更加简洁。但请记住,许多 Python 用户会对 lambda 语法感到困惑,因此您在长度和真正复杂性方面损失的内容可能会在其他编码人员的困惑中重新获得。


限制:

lambda 函数只能使用一次,除非分配给变量名。 分配给变量名的lambda 函数与def 函数相比没有优势。 lambda 函数可能很难或不可能腌制。 def 函数的名称必须经过仔细选择,以使其具有合理的描述性和唯一性,或者至少在范围内未使用。

一致性:

Python 大多避免函数式编程约定,转而支持过程性和更简单的目标语义。 lambda 运算符与这种偏见形成鲜明对比。此外,作为已经流行的def 的替代方案,lambda 函数为您的语法增加了多样性。有些人会认为这不太一致。


已有功能:

正如其他人所指出的,lambda 在该领域的许多用途可以由operator 或其他模块的成员替换。例如:

do_something(x, y, lambda x, y: x + y)
do_something(x, y, operator.add)

在许多情况下使用预先存在的函数可以使代码更具可读性。


Pythonic 原则:“应该有一种——最好只有一种——明显的方法”

这类似于single source of truth 学说。不幸的是,single-obvious-way-to-do-it 原则一直是 Python 的一种渴望,而不是真正的指导原则。考虑一下 Python 中非常强大的数组解析。它们在功能上等同于 mapfilter 函数:

[e for e in some_array if some_condition(e)]
filter(some_array, some_condition)

lambdadef 是一样的。

这是一个见仁见智的问题,但我想说的是,在 Python 语言中旨在用于一般用途且不会明显破坏任何内容的任何内容都足够“Pythonic”。

【讨论】:

【参考方案7】:

我同意 nosklo 的建议:如果您需要为函数命名,请使用 def。我保留 lambda 函数用于我只是将简短的 sn-p 代码传递给另一个函数的情况,例如:

a = [ (1,2), (3,4), (5,6) ]
b = map( lambda x: x[0]+x[1], a )

【讨论】:

在大多数 map/lambda 组合中,您可以将其替换为列表推导或更合适的函数。例如,“map (sum, a)”或“[x[0] + x[1] for x in a]” 是的,这是真的。有时我更喜欢 map() 。这主要只是一个使用内联函数的人为示例。 完全正确...大多数示例都是人为设计的,因为使用起来不自然,而且在大多数情况下有更好的实用方法。【参考方案8】:

虽然同意其他答案,但有时它更具可读性。这是一个 lambda 派上用场的示例,在一个用例中,我不断遇到 N 维 defaultdict。这是一个示例:

from collections import defaultdict
d = defaultdict(lambda: defaultdict(list))
d['Foo']['Bar'].append(something)

我发现它比为第二维创建def 更具可读性。这对于更高的维度更为重要。

【讨论】:

from functools import partial; defaultdict(partial(defaultdict, list))。如果您想多次使用它,请将其分配给一个名称。但是,如果你继续遇到这个结构,这意味着你不是 DRY。将其分解为实用程序库。您可以使用此构造使用其他 functools(或循环或递归)创建任意 n 维 defaultdict。【参考方案9】:

lambda 的主要用途一直是简单的回调函数,以及需要函数作为参数的 map、reduce、filter。随着列表推导成为常态,并且添加了允许的内容,如下所示:

x = [f for f in range(1, 40) if f % 2]

很难想象在日常使用中使用 lambda 的真实案例。因此,我会说,避免使用 lambda 并创建嵌套函数。

【讨论】:

【参考方案10】:

lambda 的一个重要限制是它们不能包含除表达式之外的任何内容。除了微不足道的副作用之外,lambda 表达式几乎不可能产生任何东西,因为它不可能有像 def'ed 函数那样丰富的主体。

话虽如此,Lua 影响了我广泛使用匿名函数的编程风格,我在代码中乱扔垃圾。最重要的是,我倾向于以我不考虑列表推导或生成器的方式将 map/reduce 视为抽象运算符,就像我通过使用这些运算符显式推迟实现决策一样。

编辑:这是一个相当古老的问题,我对此事的看法有所改变。

首先,我强烈反对将lambda 表达式分配给变量;因为 python 有一个特殊的语法(提示,def)。除此之外,lambda 的许多用途,即使它们没有名字,也有预定义(并且更有效)的实现。例如,有问题的示例可以缩写为(1).__add__,而无需将其包装在lambdadef 中。 operatoritertoolsfunctools 模块的组合可以满足许多其他常见用途。

【讨论】:

(1).__add__ -- 直接调用 dunder 方法几乎不会发生。每次直接调用都有一千个lambdas。 @EthanFurman:嗯,根据我的经验,(1).__add__ 的性质的电话有点不常见,但我不会去任何接近“应该”的地方。毫无疑问,我发现前者对lambda x: 1 + x 的可读性要高得多。如果我们有更类似于 haskells 切片表示法的东西,(1+) 那就太好了,但我们必须使用语义上确切的东西,即 dunder 方法名称。【参考方案11】: 计算时间。 没有名字的函数。 实现一功能多用功能。

考虑一个简单的例子,

# CREATE ONE FUNCTION AND USE IT TO PERFORM MANY OPERATIONS ON SAME TYPE OF DATA STRUCTURE.
def variousUse(a,b=lambda x:x[0]):
    return [b(i) for i in a]

dummyList = [(0,1,2,3),(4,5,6,7),(78,45,23,43)]
variousUse(dummyList)                           # extract first element
variousUse(dummyList,lambda x:[x[0],x[2],x[3]]) # extract specific indexed element
variousUse(dummyList,lambda x:x[0]+x[2])        # add specific elements
variousUse(dummyList,lambda x:x[0]*x[2])        # multiply specific elements

【讨论】:

【参考方案12】:

如果您只是要将 lambda 分配给本地范围内的变量,您也可以使用 def,因为它更具可读性并且将来可以更轻松地扩展:

fun = lambda a, b: a ** b # a pointless use of lambda
map(fun, someList)

def fun(a, b): return a ** b # more readable
map(fun, someList)

【讨论】:

from operator import pow;map(pow, someList)(a**b for a,b in someList) 都更具可读性。【参考方案13】:

我发现 lambdas 的一个用途是在调试消息中。

由于可以对 lambda 进行惰性求值,因此您可以编写如下代码:

log.debug(lambda: "this is my message: %r" % (some_data,))

而不是可能很昂贵:

log.debug("this is my message: %r" % (some_data,))

即使调试调用由于当前的日志记录级别而没有产生输出,它也会处理格式字符串。

当然,要使其按照描述的方式工作,正在使用的日志记录模块必须支持 lambdas 作为“惰性参数”(就像我的日志记录模块一样)。

同样的想法可能适用于任何其他用于按需内容价值创造的惰性评估情况。

例如这个自定义的三元运算符:

def mif(condition, when_true, when_false):
    if condition:
         return when_true()
    else:
         return when_false()

mif(a < b, lambda: a + a, lambda: b + b)

代替:

def mif(condition, when_true, when_false):
    if condition:
         return when_true
    else:
         return when_false

mif(a < b, a + a, b + b)

使用 lambdas 只有条件选择的表达式会被计算,没有 lambdas 两者都会被计算。

当然,您可以简单地使用函数而不是 lambda,但对于简短的表达式,lambda 更 (c) 更简洁。

【讨论】:

NB logging 已经有延迟格式化:log.debug("this is my message: %r", some_data) 只会在请求消息时/如果消息被请求时格式化。 @j08lue lambda 方法会跳过对所有内容的评估,以防未生成调试输出,如果您显示 some_data 可能是昂贵的表达式或函数/方法调用。【参考方案14】:

我同意 nosklo。顺便说一句,即使有 use once, throw away 功能,大多数时候你只是想使用 operator 模块中的东西。

例如:

你有一个带有这个签名的函数:myFunction(data, callback function)。

您想传递一个添加 2 个元素的函数。

使用 lambda:

myFunction(data, (lambda x, y : x + y))

pythonic 方式:

import operator
myFunction(data, operator.add)

当然,这是一个简单的例子,但是 operator 模块提供了很多东西,包括用于 list 和 dict 的项目 setter / getter。真的很酷。

【讨论】:

【参考方案15】:

一个主要的区别是你不能内联使用def 函数,在我看来这是lambda 函数最方便的用例。例如在对对象列表进行排序时:

my_list.sort(key=lambda o: o.x)

因此,我建议将 lambdas 用于此类琐碎的操作,这也不会真正受益于通过命名函数提供的自动文档。

【讨论】:

【参考方案16】:

lambda 对于生成新函数很有用:

>>> def somefunc(x): return lambda y: x+y
>>> f = somefunc(10)
>>> f(2)
12
>>> f(4)
14

【讨论】:

以上是关于哪个更可取:lambda 函数或嵌套函数('def')?的主要内容,如果未能解决你的问题,请参考以下文章

使用 React 时,在构造函数中使用粗箭头函数还是绑定函数更可取?

什么是更 Pythonic - 函数组合、lambdas 或其他东西? [关闭]

带有字符串键或嵌套引号的数组到 php 中的 lambda 函数中

子表单连接,主/子,引用表单控件或使用查询连接,哪个更可取

匿名函数内置函数与模块

我可以确定哪个 AWS 服务触发了我的 Lambda 函数吗?