我主要使用 lambda 函数,但有时使用似乎提供相同行为的嵌套函数。
以下是一些简单的例子,如果在另一个函数中找到它们,它们在功能上会做同样的事情:
Lambda 函数
>>> a = lambda x : 1 + x
>>> a(5)
6
嵌套函数
>>> def b(x): return 1 + x
>>> b(5)
6
使用其中一种是否有优势?(性能?可读性?限制?一致性?等)
这还重要吗?如果没有,那是否违反了 Pythonic 原则:
我主要使用 lambda 函数,但有时使用似乎提供相同行为的嵌套函数。
以下是一些简单的例子,如果在另一个函数中找到它们,它们在功能上会做同样的事情:
Lambda 函数
>>> a = lambda x : 1 + x
>>> a(5)
6
嵌套函数
>>> def b(x): return 1 + x
>>> b(5)
6
使用其中一种是否有优势?(性能?可读性?限制?一致性?等)
这还重要吗?如果没有,那是否违反了 Pythonic 原则:
如果您需要将 分配lambda
给名称,请改用 a def
。def
s 只是赋值的语法糖,所以结果是一样的,而且它们更加灵活和可读。
lambda
s 可以使用一次,扔掉没有名字的函数。
但是,这种用例非常罕见。您很少需要传递未命名的函数对象。
内置函数map()
和filter()
需要函数对象,但列表推导和生成器表达式通常比这些函数更具可读性,并且可以覆盖所有用例,而不需要 lambda。
对于你真的需要一个小函数对象的情况,你应该使用operator
模块函数,比如operator.add
而不是lambda x, y: x + y
如果您仍然需要一些lambda
未涵盖的内容,您可以考虑编写一个def
,只是为了更具可读性。如果函数比operator
模块中的函数更复杂,adef
可能更好。
因此,现实世界中好的lambda
用例非常罕见。
实际上,对我来说有两个不同之处:
首先是关于他们做什么以及他们返回什么:
def 是一个不返回任何内容并在本地命名空间中创建一个“名称”的关键字。
lambda 是一个关键字,它返回一个函数对象,并且不会在本地命名空间中创建一个“名称”。
因此,如果您需要调用一个带有函数对象的函数,那么在一行 python 代码中执行此操作的唯一方法是使用 lambda。def 没有等价物。
在某些框架中,这实际上很常见;例如,我经常使用Twisted,所以做类似的事情
d.addCallback(lambda result: setattr(self, _someVariable, result))
很常见,并且使用 lambdas 更简洁。
第二个区别是关于允许实际功能做什么。
例如,
def p(x): print x
按预期工作,而
lambda x: print x
是一个语法错误。
当然,有一些解决方法 -print
用sys.stdout.write
或import
替换__import__
。但通常在这种情况下你最好使用一个函数。
在这次采访中, Guido van Rossum 说他希望他没有让 'lambda' 进入 Python:
” Q. Python 的哪个特性你最不满意?
有时我接受贡献太快了,后来意识到这是一个错误。一个例子是函数式编程特性,比如 lambda 函数。是一个关键字,可让您创建小型匿名函数;内置函数(如 map、filter 和 reduce)在序列类型(如列表)上运行函数。
在实践中,结果并不是那么好。Python 只有两个作用域:本地和全局。这使得编写 lambda 函数很痛苦,因为您经常希望访问定义 lambda 的范围内的变量,但由于有两个范围,您不能。有一种方法可以解决这个问题,但它有点像一个kludge。通常,在 Python 中使用 for 循环而不是使用 lambda 函数似乎更容易。只有当已经有一个内置功能可以满足您的需求时,地图和朋友才能正常工作。
恕我直言,Iambas 有时会很方便,但通常会以牺牲可读性为代价。你能告诉我这是做什么的吗:
str(reduce(lambda x,y:x+y,map(lambda x:x**x,range(1,1001))))[-10:]
我写了它,我花了一分钟才弄明白。这是来自 Project Euler - 我不会说是哪个问题,因为我讨厌剧透,但它在 0.124 秒内运行 :)
对于 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 函数还是嵌套函数 (
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__
是'<lambda>'
毕竟,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。让我们重写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: if True: 0
File "<stdin>", line 1
lambda: if True: 0
^
SyntaxError: invalid syntax
诚然,表达式可能相当复杂,如果您非常努力,您可能可以使用 lambda 完成相同的任务,但增加的复杂性更不利于编写清晰的代码。
为了清晰和可维护性,我们使用 Python。过度使用 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 不适用于更正式的情况,在这种情况下,您需要让稍后出现的代码编辑器更加清晰,尤其是在它们不重要的情况下。
我们知道我们应该给我们的对象起个好名字。当对象没有名称时,我们怎么能这样做呢?
def
由于所有这些原因,我通常更喜欢使用 with而不是 with创建函数lambda
。
表现:
使用 创建函数比使用 创建函数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 原则:“应该有一种——最好只有一种——明显的方法”</p>
这类似于真理教义的单一来源。不幸的是,single-obvious-way-to-do-it 原则一直是 Python 的一种渴望,而不是真正的指导原则。考虑一下 Python 中非常强大的数组解析。它们在功能上等同于map
和filter
功能:
[e for e in some_array if some_condition(e)]
filter(some_array, some_condition)
lambda
并且def
是相同的。
这是一个见仁见智的问题,但我会说 Python 语言中的任何用于一般用途的东西,但显然不会破坏任何东西,这已经足够“Pythonic”了。
我同意 nosklo 的建议:如果您需要为函数命名,请使用def
. lambda
我为我只是将一小段代码传递给另一个函数的情况保留函数,例如:
a = [ (1,2), (3,4), (5,6) ]
b = map( lambda x: x[0]+x[1], a )
虽然同意其他答案,但有时它更具可读性。lambda
这是一个派上用场的示例,在一个用例中,我不断遇到 N 维defaultdict
。
这是一个例子:
from collections import defaultdict
d = defaultdict(lambda: defaultdict(list))
d['Foo']['Bar'].append(something)
我发现它比def
为第二维创建更具可读性。这对于更高的维度更为重要。
lambda 的主要用途一直是简单的回调函数,以及需要函数作为参数的 map、reduce、filter。随着列表推导成为规范,并且添加了允许,如下所示:
x = [f for f in range(1, 40) if f % 2]
很难想象在日常使用中使用 lambda 的真实案例。因此,我会说,避免使用 lambda 并创建嵌套函数。
lambda 的一个重要限制是它们不能包含除表达式之外的任何内容。除了微不足道的副作用之外,lambda 表达式几乎不可能产生任何东西,因为它不可能拥有像def
'ed 函数那样丰富的主体。
话虽如此,Lua 影响了我广泛使用匿名函数的编程风格,我在代码中乱扔垃圾。最重要的是,我倾向于以我不考虑列表推导或生成器的方式将 map/reduce 视为抽象运算符,就像我通过使用这些运算符显式推迟实现决策一样。
编辑:这是一个相当古老的问题,我对此事的看法有所改变。
首先,我强烈反对将lambda
表达式分配给变量;因为 python 有一个特殊的语法(提示,def
)。除此之外,lambda 的许多用途,即使它们没有名字,也有预定义(并且更有效)的实现。例如,有问题的示例可以缩写为 just (1).__add__
,而无需将其包装在lambda
ordef
中。许多其他常见用途可以通过operator
,itertools
和functools
模块的某种组合来满足。
考虑一个简单的例子,
# 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
如果您只是要将 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 的一种用途是在调试消息中。
由于可以懒惰地评估 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) 更简洁。
我同意诺斯克洛。顺便说一句,即使使用一次,丢弃功能,大多数时候您只想使用操作员模块中的某些东西。
例如:
你有一个带有这个签名的函数:myFunction(data, callback function)。
您想传递一个添加 2 个元素的函数。
使用 lambda :
myFunction(data, (lambda x, y : x + y))
蟒蛇方式:
import operator
myFunction(data, operator.add)
当然,这是一个简单的例子,但是 operator 模块提供了很多东西,包括 list 和 dict 的项目 setter / getter。真的很酷。
一个主要的区别是你不能使用def
内联函数,在我看来这是函数最方便的用例lambda
。例如,在对对象列表进行排序时:
my_list.sort(key=lambda o: o.x)
因此,我建议将 lambdas 用于此类琐碎的操作,这也不会真正受益于通过命名函数提供的自动文档。
lambda 对于生成新函数很有用:
>>> def somefunc(x): return lambda y: x+y
>>> f = somefunc(10)
>>> f(2)
12
>>> f(4)
14