使用哪个更合适:lambda函数或嵌套函数('def')?


101

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

这是一些琐碎的示例,如果在另一个函数中找到它们,它们在功能上会做同样的事情:

Lambda函数

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

嵌套功能

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

>>> b(5)
6

使用一个相对于另一个有优势吗?(性能?可读性?局限性?一致性?等)

有关系吗 如果不这样做,那确实违反了Python原则:

应该有一种(最好只有一种)明显的方式来做到这一点

Answers:


105

如果需要将分配lambda给名称,请改用a defdefs只是分配的语法糖,因此结果是相同的,并且它们更具灵活性和可读性。

lambdas只能使用一次,丢弃没有名称的函数。

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

内建函数map()filter()需要函数对象,但是列表理解生成器表达式通常比那些函数更具可读性,并且可以覆盖所有用例,而无需使用lambda。

对于情况下,你真的需要一个小的函数对象,你应该使用operator模块的功能,如operator.add代替lambda x, y: x + y

如果您仍然需要一些lambda未涵盖的内容,则可以考虑编写def,以提高可读性。如果功能比operator模块中的功能更复杂,则a def可能更好。

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


9
我同意答案时使用lambda,但我不同意,这是“非常罕见”,这是关键的功能共同sorteditertools.groupby等,例如sorted(['a1', 'b0'], key= lambda x: int(x[1]))
Chris_Rands

30

实际上,对我来说有两个区别:

首先是关于他们做什么以及他们返回什么:

  • 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

是一个SyntaxError。

当然,也有变通方法-代替printsys.stdout.write,或import__import__。但是通常情况下,最好还是使用一个函数。


22

Guido van Rossum 在这次采访中说,他希望自己不要让“ lambda”进入Python:

问:您最不满意Python的什么功能?

有时我太快地接受了贡献,后来才意识到这是一个错误。一个例子就是一些函数式编程功能,例如lambda函数。lambda是一个关键字,可让您创建一个小的匿名函数;内置函数(例如map,filter和reduce)可在序列类型(例如列表)上运行该函数。

在实践中,结果并非如此。Python只有两个范围:本地和全局。这使编写lambda函数很痛苦,因为您经常想在lambda定义的作用域中访问变量,但由于这两个作用域而不能。有办法解决这个问题,但这有点不合时宜。在Python中,通常只使用for循环而不是搞乱lambda函数似乎容易得多。只有当已有内置功能可以满足您的需求时,地图和朋友才能正常工作。

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

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

我写了它,花了我一分钟才弄清楚。这是来自欧拉计划-我不会说哪个问题,因为我讨厌剧透,但是它只需要0.124秒即可:)


20
请注意,这次采访的历史悠久,Python很久以来就添加了嵌套作用域,这使得他针对lambda的论点不再相关。我确定他仍然对lambda感到遗憾,但不足以在Python 3.0中将其删除。
Thomas Wouters

10
确实,您的示例应该是针对单行的论据,而不是lambda。另外,您应该使用内置的sum函数,而不要使用lambda进行归约:str(sum(map(lambda x:x ** x,range(1001))))[:-10]
三联式

2
@ThomasWouters:我知道lambda在3.0 中不被删除是一件很近的事情,Guido并没有努力保持它。
伊桑·弗曼

11

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

3
有趣的是,lambda和定义的版本大致相同。上一次测试花费了更多时间,因为python每次定义该lambda函数时可能都需要分配空间。
hlin117

我认为这很有意义,因为定义可以引用局部变量(可能已更改)...尽管在这种情况下,cpython可以做得更好。
安迪·海登

使用dis.dis; 您的(lambda x,y:x * y)在每个循环中创建函数。如果在循环之前创建lambda(又名f = lambda x,y:x * y),则用于调用该函数的字节码将与上一个示例中的g / f完全相同,因此lambda的性能相同作为def函数。因此,如果相同使用lambda或def则没有影响。反过来说,在循环中声明f()函数,然后将其命名为……
tito

@tito我相信这正是三个定时示例所展示的……
Andy Hayden

@tito哦,你是说在循环中定义函数,但是我认为这是一个不寻常的模式。不确定为什么这需要对此评论进行否决...
Andy Hayden

7

性能:

创建一个功能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)

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


Python原则:“应该有一种-最好只有一种-显而易见的方法”

这类似于真理教义的单一来源。不幸的是,单行之道的原则一直是Python的渴望,而不是真正的指导原则。考虑一下Python中非常强大的数组理解。它们在功能上等效于mapfilter函数:

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

lambdadef一样。

这是一个见解,但是我想说,Python语言中用于一般用途的任何东西如果没有明显破坏任何东西,都足够“ Pythonic”。


7

更可取的是: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和功能:

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

但是,默认情况下,lambda缺少某些功能,这些功能是通过完整的函数定义语法获得的。

兰巴舞__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没有注释,也没有文档字符串

基本上没有记录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

鉴于我们没有相同的机制为lambda提供相同的信息:

>>> 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时,我也倾向于使用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-注意,这是微不足道的。

结论

Lambda非常适合非正式情况,在这种情况下,您希望减少代码行以支持单数点。

Lambda对于更正式的情况是不利的,在这种情况下,您需要为以后将要出现的代码编辑者提供清晰的信息,特别是在它们不平凡的情况下。

我们知道应该给我们的对象起好名字。当对象没有物体时我们该怎么做名称,?

由于所有这些原因,我通常更喜欢使用def代替with 来创建函数lambda


6

我同意nosklo的建议:如果需要给函数命名,请使用deflambda当我只是将简短的代码片段传递给另一个函数时,我会保留函数,例如:

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

3
在map / lambda的大多数组合中,您可以将其替换为列表推导或更合适的功能。例如,“ map(sum,a)”或“ [a中x的[x [0] + x [1]”]
John Millikin

是的,这是真的。有时我还是喜欢map()。这主要只是使用内联函数的人为例子。
Dan Lenski

确切地...大多数示例都是人为设计的,因为使用起来很不自然,并且在大多数情况下还有一些实用的更好方法。
nosklo

5

在同意其他答案的同时,有时它更具可读性。这是一个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。将其分解为实用程序库。您可以使用此构造通过其他函数工具(或循环或递归)创建任意的n维defaultdict。
DylanYoung

3

lambda的主要用途一直是用于简单的回调函数,以及用于map,reduce,filter,后者需要将函数用作参数。随着列表理解成为规范,并且允许添加,如:

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

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


3

Lambda的一个重要限制是它们除了表达式外不能包含其他任何内容。一个lambda表达式几乎不可能产生除琐碎的副作用之外的其他任何东西,因为它的身体不能像人体一样富裕。def “ ed函数”。

话虽这么说,Lua影响了我的编程风格,使之广泛地使用了匿名函数,并且我在其中充斥了代码。最重要的是,我倾向于以不考虑列表推导或生成器的方式将map / reduce视为抽象运算符,就像我要通过使用这些运算符明确推迟实现决策一样。

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

首先,我强烈反对将lambda表达式分配给变量。因为python具有专门的语法(提示,def)。除此之外,lambda的许多用途(即使没有名称)也具有预定义的(且效率更高)的实现。例如,所讨论的示例可以缩写为just (1).__add__,而无需将其包装在a lambda或中def。许多其他常见的用途可以使用的某种组合来满足operatoritertoolsfunctools模块。


1
(1).__add__-几乎永远不会发生直接调用dunder方法的情况。lambda每次直接催声呼叫都需要1000 秒。
伊桑·弗曼

1
@EthanFurman:好吧,以我的经验,大自然的呼唤在(1).__add__某种程度上并不常见,但是我不会去接近“应该”的地方。毫无疑问,我发现前者更具可读性lambda x: 1 + x。如果我们有更类似于haskells slice表示法的东西,(1+)那就太好了,但是我们必须在语义上准确地做到这一点,即dunder方法名称。
SingleNegationElimination

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

考虑一个简单的例子,

# 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

1

如果仅要将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)都更具可读性。
InQβ

1

我发现的lambda的一种用途是在调试消息中。

由于可以懒惰地评估lambda,因此您可以使用以下代码:

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

而不是可能很昂贵:

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

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

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

相同的想法可以应用于按需内容值创建的任何其他惰性评估情况。

例如,此自定义三元运算符:

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)

如果使用lambda,则只会评估由条件选择的表达式,而不会评估lambda。

当然,您可以简单地使用函数而不是lambda,但是对于短表达式而言,lambda更精简。


1
NB logging已经具有惰性格式化:log.debug("this is my message: %r", some_data)仅在/如果请求消息时格式化。
j08lue

如果未显示调试输出,则@ j08lue lambda方法跳过对所有内容的评估 some_data可能会出昂贵的表达式或函数/方法调用。
Glushiator

0

我同意nosklo。顺便说一句即使使用一次也扔掉功能,大多数情况下,您只想使用操作员模块中的某些功能。

EG:

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

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

使用lambda:

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

pythonic方式:

import operator
myFunction(data, operator.add)

或当然,这是一个简单的示例,但是操作员模块提供了很多东西,包括用于列表和字典的项目设置器/获取器。真的很酷。


-1

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

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

因此,我建议继续使用lambda进行此类琐碎的操作,这些操作也并不能真正受益于功能命名所提供的自动文档。


-2

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

>>> def somefunc(x): return lambda y: x+y
>>> f = somefunc(10)
>>> f(2)
12
>>> f(4)
14
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.