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

时间:2008-09-25 17:15:03

标签: python syntax function lambda

我主要使用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”

16 个答案:

答案 0 :(得分:91)

如果您需要将lambda分配给姓名,请改用defdef只是赋值的语法糖,所以结果是一样的,它们更灵活,更易读。

lambda可以用于使用一次,丢弃没有名字的函数。

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

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

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

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

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

答案 1 :(得分:28)

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

首先是他们做了什么以及他们回来了什么:

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

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

因此,如果你需要调用一个带有函数对象的函数,在一行python代码中执行该操作的唯一方法是使用lambda。没有与def相同的东西。

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

这样的事情
d.addCallback(lambda result: setattr(self, _someVariable, result))

很常见,而且对lambdas更简洁。

第二个区别是关于允许实际功能的内容。

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

例如,

def p(x): print x

按预期工作,而

lambda x: print x

是一个SyntaxError。

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

答案 2 :(得分:20)

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秒内运行:)

答案 3 :(得分:9)

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

答案 4 :(得分:6)

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

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

答案 5 :(得分:5)

性能:

使用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) 或其他模块的成员替换。例如:

map

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

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

这类似于single source of truth学说。不幸的是,单一显而易见的原则一直是Python的渴望,而不是真正的指导原则。考虑一下Python中非常强大的数组解析。它们在功能上等同于filter[e for e in some_array if some_condition(e)] filter(some_array, some_condition) 函数:

lambda

def和{{1}}是相同的。

这是一个意见问题,但我会说,用于一般用途的Python语言中任何显然不会破坏任何内容的东西都是" Pythonic"够了。

答案 6 :(得分:4)

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

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

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

答案 7 :(得分:3)

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

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

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

答案 8 :(得分:3)

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

话虽这么说,Lua影响了我的编程风格,广泛使用了匿名函数,我把它们丢弃了。最重要的是,我倾向于将map / reduce视为抽象运算符,我不会考虑列表推导或生成器,几乎就像我通过使用这些运算符明确地推迟实现决策一样。

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

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

答案 9 :(得分:3)

  

更优选:lambda函数或嵌套函数(def)?

在常规函数上使用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是函数,因此它们是第一类对象。

lambdas和函数:

  • 可以作为参数传递(与常规函数相同)
  • 在外部函数中创建时,成为对外部函数的封闭&#39;当地人

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

Lamba的__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>

Lambdas没有注释,没有文档字符串

基本上,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

然而,我们没有相同的机制向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

但是,可能会有一些错误搞砸了帮助的输出。

Lambdas只能返回表达式

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

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

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

我们使用Python来提高清晰度和可维护性。过度使用lambdas可以解决这个问题。

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对于更正式的情况是不利的,在这种情况下,你需要明确后来的代码编辑,特别是在它们非常重要的情况下。

我们知道我们应该给我们的对象好名字。当对象具有 no 名称时,我们如何才能这样做?

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

答案 10 :(得分: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

答案 11 :(得分: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)

答案 12 :(得分:1)

我发现的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)更精简。

答案 13 :(得分:0)

我同意nosklo。顺便说一下,即使使用一次,也可以扔掉功能,大部分时间你只想使用运算符模块中的东西。

E.G:

你有一个带有这个签名的函数:myFunction(数据,回调函数)。

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

使用lambda:

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

pythonic方式:

import operator
myFunction(data, operator.add)

当然这是一个简单的例子,但是运算符模块提供了很多东西,包括列表和字典的setter / getters。真的很酷。

答案 14 :(得分:-1)

lambda对于生成新函数非常有用:

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

答案 15 :(得分:-1)

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

my_list.sort(key=lambda o: o.x)
因此,我建议将lambdas用于这种简单的操作,这些操作也没有真正受益于命名函数提供的自动文档。