ラムダ関数と入れ子関数( 'def')のどちらを使用するのがより望ましいですか?


101

私は主にラムダ関数を使用しますが、同じ動作を提供するように見える入れ子関数を使用することもあります。

次に、どちらかが別の関数内で見つかった場合に機能的に同じことを行ういくつかの簡単な例を示します。

ラムダ関数

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

入れ子関数

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

>>> b(5)
6

どちらを使用するよりも利点はありますか?(パフォーマンス?読みやすさ?制限?一貫性?など)

それも重要ですか?そうでない場合は、Pythonicの原則に違反します。

それを行うには、明白な方法が1つ(できれば1つだけ)あるはずです。

回答:


105

lambda名前を割り当てる必要がある場合は、def代わりにを使用してください。defsは割り当ての単なる構文糖なので、結果は同じであり、はるかに柔軟で読みやすくなっています。

lambdasは1度だけ使用でき、名前のない関数を破棄できます。

ただし、この使用例は非常にまれです。名前のない関数オブジェクトを渡す必要はほとんどありません。

ビルトインmap()filter()関数オブジェクトが必要ですが、内包表記ジェネレータ式をリストしますは一般にこれらの関数より読みやすく、ラムダを必要とせずにすべてのユースケースをカバーできます。

あなたは本当に小さな関数オブジェクトを必要とするケースでは、あなたが使用する必要がありますoperatorように、モジュールの機能をoperator.add代わりにlambda x, y: x + y

それでもlambdaカバーされていないものが必要な場合はdef、読みやすくするためにを書くことを検討してください。関数がoperatorモジュールのものよりも複雑な場合は、defおそらくaの方が優れています。

そのため、実際の良いlambdaユースケースは非常にまれです。


9
私はどのような場合に使用するための答えに同意するlambdaが、私は、これは「非常にまれ」であることを同意しない、それはに重要な機能のための共通であるsorteditertools.groupbyなど、例えばsorted(['a1', 'b0'], key= lambda x: int(x[1]))
Chris_Rands

30

実際には、私には2つの違いがあります。

最初は彼らが何をして何を返すかについてです:

  • defは何も返さず、ローカルの名前空間に「名前」を作成するキーワードです。

  • lambdaは、関数オブジェクトを返し、ローカル名前空間に「名前」を作成しないキーワードです。

したがって、関数オブジェクトを受け取る関数を呼び出す必要がある場合、Pythonコードの1行でそれを行う唯一の方法は、ラムダを使用することです。defに相当するものはありません。

一部のフレームワークでは、これは実際にはかなり一般的です。たとえば、私はTwistedを頻繁に使用しているので、次のようなことをしています

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

かなり一般的で、ラムダを使用するとより簡潔になります。

2番目の違いは、実際の関数が実行できることです。

  • '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はPythonに「ラムダ」を入れないことを望んでいると述べています。

" Q. Pythonのどの機能に最も満足し ていませんか?実際には、それはうまくいきませんでした。Pythonには、ローカルとグローバルの2つのスコープしかありません。ラムダ関数の記述は面倒です。ラムダが定義されたスコープですが、2つのスコープのためにできません。これを回避する方法はありますが、これはなんらかの方法です。多くの場合、Pythonでは、いじる代わりにforループを使用する方がはるかに簡単です。ラムダ関数:マップとフレンドは、必要なことを実行する組み込み関数が既にある場合にのみ機能します。

時々私は貢献を受け入れるのが速すぎて、後でそれが間違いであることに気付きました。1つの例は、ラムダ関数などの関数型プログラミング機能の一部です。lambdaは、小さな無名関数を作成できるキーワードです。map、filter、reduceなどの組み込み関数は、リストなどのシーケンス型に対して関数を実行します。

私見、Iambdasは時々便利ですが、通常は読みやすさを犠牲にして便利です。これが何をしているのか教えてください:

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

私はそれを書いて、それを理解するのに1分かかりました。これはProject Eulerによるものです。スポイラーが嫌いなので、どの問題かはわかりませんが、0.124秒で実行されます:)


20
インタビューはかなり古く、Pythonには長い間ネストされたスコープが追加されていることに注意してください。これにより、ラムダに対して彼が与える議論はもはや意味がなくなります。彼はまだラムダを後悔していると確信していますが、Python 3.0でラムダを削除するには不十分です。
Thomas Wouters

10
本当にあなたの例はラムダではなくワンライナーに対する議論であるべきです。また、あなたの代わりにラムダを減らすの組み込みのSUM関数を使用している必要があります:STR(合計(マップ(ラムダX:X **のx、範囲(1001))))[ - 10]
トリプティク

2
@ThomasWouters:lambda3.0で削除されないことは近いことであり、Guidoはそれを維持するために戦っていなかったことを理解しています。
イーサンファーマン

11

n = 1000の場合は、関数とラムダを呼び出すときがあります。

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
興味深いのは、ラムダバージョンと定義済みバージョンがほぼ同等であることを確認することです。最後のテストは、Pythonがそのラムダ関数を定義するたびにスペースを割り当てる必要があったため、さらに時間がかかりました。
hlin117 2014年

定義がローカル変数(変更されている可能性があります)を参照している可能性があるため、これは理にかなっていると思います。
アンディヘイデン

dis.disを使用します。(lambda x、y:x * y)はループごとに関数を作成します。ループの前にラムダを作成する場合(別名f =ラムダx、y:x * y)、関数を呼び出すためのバイトコードは前の例のg / fとまったく同じであるため、ラムダのパフォーマンスは同じですdef関数として。したがって、ラムダまたはデフを同じように使用しても影響はありません。逆を行い、ループ内でf()関数を宣言してから呼び出します...
tito

@titoそれがまさに3つの時限の例が示すものだと思います...
Andy Hayden

@titoああ、あなたはループで関数を定義していると言っていますが、私はそれが異常なパターンであると主張します。なぜこれがそのコメントへの反対票を必要としたのかわからない...
Andy Hayden

7

パフォーマンス:

で関数を作成することlambda、わずかに速いとそれを作成するよりもdef。違いはdef、localsテーブルに名前エントリを作成するためです。結果の関数の実行速度は同じです。


読みやすさ:

Lambda関数は、ほとんどのPythonユーザーにとって読みにくくなりますが、状況によってはより簡潔になります。非機能ルーチンから機能ルーチンへの変換を検討してください:

# 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変数名に割り当てられた関数は、関数よりも優れてい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の原則:「それを行うには、明白な方法が1つ(できれば1つだけ)あるべきです」

それは真実の教義の単一の情報源に似ています。残念ながら、単一の自明な方法の原則は、真の指針となる原則ではなく、常にPythonにとって切ない願望でした。Pythonの非常に強力な配列内包を考えてみましょう。これらはmapおよびfilter関数と機能的に同等です。

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

lambdadef同じです。

それは意見の問題ですが、一般的に使用することを目的としたPython言語の中で、明らかに何も破壊しないものは「Pythonic」で十分だと思います。


7

より好ましい:ラムダ関数またはネストされた関数(def)?

通常の関数よりもラムダを使用する利点は1つあります。それらは式で作成されます。

いくつかの欠点があります:

  • 名前なし(だけ'<lambda>'
  • ドキュメント文字列なし
  • 注釈なし
  • 複雑なステートメントはありません

また、どちらも同じタイプのオブジェクトです。これらの理由から、私は一般にdefラムダではなくキーワードを使用して関数を作成することを好みます。

最初のポイント-それらは同じタイプのオブジェクトです

ラムダは通常の関数と同じタイプのオブジェクトになります

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

ラムダは関数であるため、ファーストクラスのオブジェクトです。

ラムダと関数の両方:

  • 引数として渡すことができます(通常の関数と同じ)
  • 外部関数内で作成されると、その外部関数のローカルのクロージャーになります

しかし、ラムダには、デフォルトで、関数が完全な関数定義構文を介して取得するいくつかのものが欠けています。

ランバ__name__'<lambda>'

ラムダは匿名関数であるため、自分の名前はわかりません。

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

したがって、ラムダは名前空間でプログラムで検索できません。

これは特定のものを制限します。たとえば、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>

ラムダには注釈も文書文字列もありません

基本的に、ラムダは文書化されていません。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

一方、ラムダに同じ情報を提供する同じメカニズムはありません。

>>> 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: if True: 0
  File "<stdin>", line 1
    lambda: if True: 0
             ^
SyntaxError: invalid syntax

式は確かにかなり複雑になる可能性があり、非常に一生懸命努力すると、おそらくラムダで同じことを達成できますが、追加された複雑さは、明確なコードを書くことに対してより多くの不利益になります。

明快さと保守性のためにPythonを使用しています。ラムダの過剰使用はそれを防ぐことができます。

ラムダの唯一の利点:単一の式で作成できます

これが唯一の可能なアップサイドです。式を使用してラムダを作成できるため、関数呼び出し内でラムダを作成できます。

関数呼び出し内で関数を作成すると、他の場所で作成したものよりも(安価な)名前の検索を回避できます。

ただし、Pythonは厳密に評価されるため、名前の検索を回避することを除いて、そうすることによる他のパフォーマンスの向上はありません。

非常に単純な表現の場合、ラムダを選択する場合があります。

私はまた、インタラクティブなPythonを実行するときにラムダを使用する傾向があります。を呼び出すときにコンストラクタに引数を渡したい場合は、次のようなコード形式を使用します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ていると思います。これはごくわずかです。

結論

ラムダは、特異点を作成するためにコード行を最小限にしたい非公式な状況に適しています。

ラムダは、特に重要でない場合に、後で来るコードの編集者を明確にする必要があるより正式な状況には適していません。

オブジェクトに適切な名前を付けることになっています。オブジェクトに名前がない場合、どうすればよいですか?

これらすべての理由から、私は一般にwith defではなくwithを使用して関数を作成することを好みますlambda


6

noskloのアドバイスに同意しますdef。関数に名前を付ける必要がある場合は、を使用してください。私lambdaは、コードの短いスニペットを別の関数に渡す場合のために関数を予約します。例:

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

3
map / lambdaのほとんどの組み合わせでは、それをリスト内包またはより適切な関数に置き換えることができます。たとえば、「map(sum、a)」または「[x [0] + x [1] for x in a]」
John Millikin

はい、そうです。たまにmap()を好むこともあります。これは主に、インライン関数を使用した不自然な例です。
Dan Lenski

正確に...ほとんどの例は不自然です。使用するのは不自然であり、ほとんどの場合、実用的なより良い方法があるからです。
nosklo

5

他の答えには同意しますが、より読みやすい場合もあります。これlambdaは便利な例です。ユースケースでN次元に遭遇し続けdefaultdictます。
次に例を示します。

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

def2番目の次元のを作成するよりも読みやすいと思います。これは、高次元ではさらに重要です。


from functools import partial; defaultdict(partial(defaultdict, list))。複数回使用する場合は、名前にパーシャルを割り当てます。しかし、この構造に遭遇し続ける場合、それはあなたがDRYではないことを意味します。ユーティリティライブラリにそれを因数分解します。この構成を使用して、他のfunctools(またはループや再帰)を使用して任意のn次元defaultdictを作成できます。
DylanYoung

3

ラムダの主な用途は、常に単純なコールバック関数と、引数として関数を必要とするマップ、リデュース、フィルターです。リスト内包表記が標準になり、次のように追加された場合は許可されます:

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

日常的にラムダを使用する実際のケースを想像するのは困難です。結果として、ラムダを避け、ネストされた関数を作成します。


3

ラムダの重要な制限は、式以外に何も含めることができないことです。ラムダ式は、def'ed関数と同じくらい豊かなボディを持つことができないため、些細な副作用以外のものを生成することはほぼ不可能です。

そうは言っても、Luaは私のプログラミングスタイルに影響を与えて、匿名関数を広範囲に使用するようになりました。それに加えて、リストの内包やジェネレーターを考慮しない方法でmap / reduceを抽象演算子として考える傾向があります。ほとんどの場合、それらの演算子を使用して実装の決定を明示的に延期する場合と同じです。

編集:これはかなり古い質問であり、この件に関する私の意見は多少変わっています。

まず、私はlambda変数に式を割り当てることに対して強いバイアスをかけています。Pythonにはそのための特別な構文があるため(ヒント、def)。それに加えて、ラムダの多くの使用法は、名前を取得しない場合でも、事前定義された(より効率的な)実装を備えています。たとえば、問題の例(1).__add__は、lambdaやでラップする必要なく、単にに短縮できますdef。他の多くの一般的な用途はoperatoritertoolsfunctoolsモジュールのいくつかの組み合わせで満たすことができます。


1
(1).__add__-dunderメソッドを直接呼び出すことはほとんどありません。lambdaダイレクトダンダーコールごとに1000 秒。
イーサンファーマン

1
@EthanFurman:まあ、私の経験では、自然の呼び声(1).__add__は多少珍しいですが、「すべき」に近い場所には行きません。間違いなく、前者の方がはるかに読みやすくなっていlambda x: 1 + xます。Haskellsのスライス表記に似たものがあれば、(1+)それはすばらしいことですが、意味的にはまさにそのことであるdunderメソッド名を使用する必要があります。
SingleNegationElimination 14

2
  • 計算時間。
  • 名前のない関数。
  • 1つの機能を実現し、多くの機能を使用します。

簡単な例を考えると、

# 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

ラムダをローカルスコープの変数に割り当てるだけの場合は、defを使用することもできます。これは、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

私が見つけたラムダの1つの用途は...デバッグメッセージです。

ラムダは遅延評価できるため、次のようなコードを使用できます。

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

おそらく高価な代わりに:

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

現在のログレベルのためにデバッグ呼び出しが出力を生成しない場合でも、フォーマット文字列を処理します。

もちろん、説明したように機能するためには、使用中のロギングモジュールがラムダを「レイジーパラメータ」としてサポートしている必要があります(私のロギングモジュールと同様)。

同じアイデアは、オンデマンドのコンテンツ値作成のための遅延評価の他のどのケースにも適用できます。

たとえば、次のカスタム三項演算子:

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)

ラムダを使用すると、条件によって選択された式のみが評価され、ラムダを使用しない場合は両方が評価されます。

もちろん、ラムダの代わりに単純に関数を使用することもできますが、短い式ではラムダの方が(c)簡単です。


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つの要素を追加する関数を渡したいとします。

ラムダを使用する:

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)

したがって、ラムダをこのような簡単な操作に使用することをお勧めします。これも、関数に名前を付けることによって提供される自動ドキュメンテーションからはあまりメリットがありません。


-2

ラムダは新しい関数を生成するのに便利です:

>>> def somefunc(x): return lambda y: x+y
>>> f = somefunc(10)
>>> f(2)
12
>>> f(4)
14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.