使用 lambda 函数和嵌套函数(‘ def’)哪个更好?

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

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

Lambda 函数

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

嵌套函数

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


>>> b(5)
6

使用一种方法有什么优点吗? (性能? 可读性? 局限性? 一致性? 等等)

这重要吗? 如果不重要,那么这是否违反了 Python 的原则:

应该有一种——最好只有一种——显而易见的方法来做到这一点。

87189 次浏览

如果需要将 lambda分配给名称,则使用 defdef只是作业的语法糖,所以结果是一样的,而且它们更加灵活和易读。

lambda可以用于没有名称的 使用一次,扔掉函数。

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

内置的 map()filter()需要函数对象,但是 列表理解法生成器表达式生成器表达式通常比这些函数更具可读性,可以覆盖所有用例,而不需要 lambdas。

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

如果你仍然需要一些 lambda没有涵盖,你可以考虑写一个 def,只是为了更具可读性。如果函数比 operator模块的函数更复杂,那么 def可能更好。

因此,现实世界中良好的 lambda用例非常罕见。

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

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

Lambda 的主要用途一直是用于简单的回调函数,以及用于 map、 reduce、 filter,它们需要一个函数作为参数。随着列表理解成为常态,并允许添加如下内容:

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

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

我同意 nosklo。顺便说一下,即使使用 使用一次,扔掉函数,大多数情况下您也只是想使用操作符模块中的某些内容。

例如:

您有一个具有此签名的函数: myFunction (data,callback 函数)。

你想要传递一个函数来添加2个元素。

使用 lambda:

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

蟒蛇式:

import operator
myFunction(data, operator.add)

当然,这是一个简单的示例,但是操作符模块提供了很多内容,包括 list 和 dict 的条目 setter/getter。真的很酷。

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

第一个是关于他们做了什么和他们回来了什么:

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

  • Lambda 是一个关键字,它返回一个函数对象,并且不在本地名称空间中创建一个“ name”。

因此,如果需要调用一个接受函数对象的函数,那么在一行 python 代码中调用该函数的唯一方法是使用 lambda。与 def 没有等价关系。

在一些框架中,这种情况实际上很常见; 例如,我经常使用 扭曲,因此做了类似于

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

是相当普遍的,更简洁的与 lambdas。

第二个区别是实际函数允许做什么。

  • 用‘ def’定义的函数可以包含任何 python 代码
  • 用“ lambda”定义的函数必须求值为一个表达式,因此不能包含诸如 print、 import、 rise 等语句。

比如说,

def p(x): print x

工作正常,同时

lambda x: print x

是一个语法错误。

当然,也有变通方法——用 sys.stdout.write代替 print,或者用 __import__代替 import。但通常在这种情况下,最好使用函数。

如果你只是想把 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)

Lambda 在生成新函数方面很有用:

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

Lambdas 的一个重要限制是,除了表达式之外,它们不能包含任何内容。对于一个 lambda 表达式来说,除了琐碎的副作用外,几乎不可能产生任何其他效果,因为它不可能具有像 def’ed 函数那样丰富的主体。

也就是说,Lua 影响了我对匿名函数的广泛使用的编程风格,我的代码中到处都是这些函数。最重要的是,我倾向于把 map/reduce 看作抽象操作符,而不是列表理解或生成器,几乎就像我通过使用这些操作符明确地推迟实现决策一样。

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

首先,我强烈反对将 lambda表达式分配给变量; 因为 python 有专门的语法(提示,def)。除此之外,lambda 的许多用法,即使在没有名称的情况下,都有预定义的(而且更有效率的)实现。例如,有问题的示例可以缩写为 (1).__add__,而不需要将其包装在 lambdadef中。operatoritertoolsfunctools模块的某些组合可以满足许多其他常见用途。

对于 n = 1000,有一段时间是调用一个函数 vs 一个 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派上用场的例子,在一个用例中,我一直遇到 N 维 defaultdict。< br > 这里有一个例子:

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

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

我发现 lambdas 的一个用途... 就是调试消息。

由于可以对 lambdas 进行惰性计算,因此可以使用以下代码:

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 时,两个表达式都将被求值。

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

演出:

与使用 def创建函数相比,使用 lambda创建函数是 稍微快一点。区别在于 def在局部变量表中创建了一个名称条目。生成的函数具有相同的执行速度。


可读性:

对于大多数 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)

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


Python 原则: “应该有一种ーー最好只有一种ーー显而易见的方法来做这件事。”

这与 唯一的真相来源原则相似。不幸的是,单一显而易见的实现方式原则一直是 Python 的一个渴望,而不是一个真正的指导原则。考虑 Python 中非常强大的数组理解。它们在功能上等同于 mapfilter功能:

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

lambdadef是一样的。

这只是个人观点的问题,但是我要说,Python 语言中任何用于通用用途的东西,只要没有破坏任何东西,就已经足够“ Python 化”了。

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

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

因此,我建议将 lambdas 的使用保留在这种琐碎的操作中,这种操作也不能真正从命名函数所提供的自动文档中获益。

更可取的: 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

因为 lambdas 是函数,所以它们是第一类对象。

Lambda 和函数:

  • 可以作为参数传递(与正则函数相同)
  • 当在外部函数中创建时,就成为该外部函数的局部变量的闭包

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

羊的 __name__'<lambda>'

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

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

Lambdas 没有注释,也没有 docstring

基本上,lambdas 没有文档记录。让我们重写 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

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

Lambdas 只能返回一个表达式

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

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

不可否认,表达式可能相当复杂,如果您努力尝试 非常,您可能也可以使用 lambda 实现相同的功能,但是增加的复杂性更有损于编写清晰的代码。

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

Lambdas: 的 只有上限可以在一个表达式中创建

这是唯一可能的好处。因为可以用表达式创建 lambda,所以可以在函数调用中创建它。

在函数调用中创建函数可以避免在其他地方创建的名称查找(代价不高)。

但是,由于对 Python 进行了严格的评估,因此除了避免名称查找之外,这样做不会带来其他性能提高。

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

我还倾向于在执行交互式 Python 时使用 lambdas,以避免一行代码执行多行代码。当我想在调用 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中的名称查找-注意,它是 非常可以忽略不计。

结论

Lambdas 适用于非正式的情况,在这种情况下,您希望尽量减少代码行,以便创建单点。

Lambdas 对于更正式的情况是不利的,因为在这种情况下,您需要清晰的代码编辑器,而这些编辑器将在以后出现,特别是在它们非常重要的情况下。

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

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

  • 计算时间
  • 没有名称的函数。
  • 实现一个功能和许多使用功能。

举个简单的例子,

# 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