Python で使用するのがより望ましいのは次のとおりです。ラムダ関数または入れ子関数 ('def')?
質問
私は主にラムダ関数を使用しますが、同じ動作を提供すると思われる入れ子関数を使用することもあります。
以下に、どちらかが別の関数内で見つかった場合に、機能的に同じことを行う簡単な例をいくつか示します。
ラムダ関数
>>> a = lambda x : 1 + x
>>> a(5)
6
入れ子関数
>>> def b(x): return 1 + x
>>> b(5)
6
一方を他方よりも使用することに利点はありますか?(パフォーマンス?可読性?制限?一貫性?等。)
それも重要ですか?そうでない場合は、Python の原則に違反します。
解決
を割り当てる必要がある場合は、 lambda
名前には、 def
その代わり。 def
は代入の糖衣構文にすぎないため、結果は同じですが、はるかに柔軟で読みやすくなっています。
lambda
に使用できます 一度使ったら捨てる 名前のない関数。
ただし、この使用例は非常にまれです。名前のない関数オブジェクトを渡す必要が生じることはほとんどありません。
ビルトイン map()
そして filter()
関数オブジェクトが必要ですが、 リスト内包表記 そして ジェネレータ式 一般に、これらの関数よりも読みやすく、ラムダを必要とせずにすべてのユースケースに対応できます。
本当に小さな関数オブジェクトが必要な場合には、 operator
モジュール関数など operator.add
の代わりに lambda x, y: x + y
まだ必要な場合は lambda
カバーされていない場合は、 def
, 、読みやすくするためです。関数が上記の関数よりも複雑な場合は、 operator
モジュール、 def
おそらく良いでしょう。
それで、現実世界は良いです lambda
使用例は非常にまれです。
他のヒント
実際的に言えば、私にとって次の 2 つの違いがあります。
1 つ目は、彼らが何をし、何を返すかについてです。
def は何も返さず、ローカル名前空間に「名前」を作成するキーワードです。
lambda は関数オブジェクトを返すキーワードであり、ローカル名前空間に「名前」を作成しません。
したがって、関数オブジェクトを受け取る関数を呼び出す必要がある場合、Python コードの 1 行でそれを行う唯一の方法はラムダを使用することです。def に相当するものはありません。
一部のフレームワークでは、これは実際には非常に一般的です。たとえば、私が使用するのは ツイスト たくさんあるので、次のようなことをします
d.addCallback(lambda result: setattr(self, _someVariable, result))
これは非常に一般的であり、ラムダを使用するとより簡潔になります。
2 番目の違いは、実際の関数で何ができるかということです。
- 「def」で定義された関数には、任意の Python コードを含めることができます
- 「lambda」で定義された関数は式として評価される必要があるため、print、import、raise などのステートメントを含めることはできません。
例えば、
def p(x): print x
期待通りに動作しますが、
lambda x: print x
は構文エラーです。
もちろん、回避策はあります - 代替品 print
と sys.stdout.write
, 、 または import
と __import__
. 。しかし、その場合は通常、関数を使用する方が良いでしょう。
このインタビューでは、 Guido van Rossum 氏は、Python に「lambda」を入れなければよかったと言っています:
"Q.Python の最も不満な機能は何ですか?
時々、急いで寄付を受け入れすぎて、後でそれが間違いだったと気づくことがあります。一例としては、ラムダ関数などの関数型プログラミング機能の一部が挙げられます。lambda は、小さな匿名関数を作成できるキーワードです。Map、filter、reduce などの組み込み関数は、リストなどのシーケンス型に対して関数を実行します。
実際にやってみると、それほどうまくいきませんでした。Python には 2 つのスコープしかありません。ローカルとグローバル。これにより、ラムダ関数の作成が面倒になります。ラムダが定義されたスコープ内の変数にアクセスしたいことがよくありますが、スコープが 2 つあるためアクセスできないからです。これを回避する方法はありますが、それはちょっと面倒です。多くの場合、Python ではラムダ関数をいじるよりも for ループを使用する方がはるかに簡単に思えます。マップとフレンドは、必要なことを実行する組み込み関数がすでに存在する場合にのみ適切に機能します。
私見ですが、Iambdas は便利な場合もありますが、通常は読みやすさが犠牲になります。これが何をするのか教えていただけますか:
str(reduce(lambda x,y:x+y,map(lambda x:x**x,range(1,1001))))[-10:]
私はそれを書きましたが、それを理解するのに1分かかりました。これは Project Euler のものです。ネタバレは嫌なのでどの問題かは言いませんが、0.124 秒で実行されます :)
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
私は nosklo のアドバイスに同意します。関数に名前を付ける必要がある場合は、次を使用します。 def
. 。予約します lambda
コードの短いスニペットを別の関数に渡すだけの場合の関数。例:
a = [ (1,2), (3,4), (5,6) ]
b = map( lambda x: x[0]+x[1], a )
パフォーマンス:
関数を作成する lambda
は わずかに速い で作成するよりも def
. 。違いの原因は、 def
ローカルテーブルに名前エントリを作成します。結果として得られる関数の実行速度は同じです。
可読性:
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
変数名に割り当てられない限り、関数は 1 回だけ使用できます。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)
lambda
そして def
同じだ。
これは意見の問題ですが、一般的な使用を目的とした Python 言語のうち、明らかに何も破壊しないものであれば、十分に「Python 的」であると私は言います。
他の回答に同意しますが、場合によっては、その回答の方が読みやすい場合があります。以下に例を示します。 lambda
N次元に遭遇し続けるユースケースでは便利です defaultdict
.
以下に例を示します。
from collections import defaultdict
d = defaultdict(lambda: defaultdict(list))
d['Foo']['Bar'].append(something)
を作成するよりも読みやすいと思います def
二次元の場合。これは高次元ではさらに重要です。
ラムダの主な用途は常に、単純なコールバック関数と、引数として関数を必要とするマップ、リデュース、フィルターです。リスト内包表記が標準になり、次のような場合に追加が許可されるようになりました。
x = [f for f in range(1, 40) if f % 2]
ラムダを日常的に使用する実際のケースを想像するのは困難です。結果として、ラムダを避けて入れ子関数を作成すると思います。
ラムダの重要な制限は、式以外のものを含めることができないことです。ラムダ式が些細な副作用以外の何かを生成することはほぼ不可能です。ラムダ式は、ラムダ式ほど豊かな本体を持つことができないためです。 def
'ed関数。
そうは言っても、Lua は私のプログラミング スタイルに匿名関数の広範な使用に影響を与えており、私はコードに匿名関数を散りばめています。それに加えて、私はリスト内包表記やジェネレーターを考慮せずに、map/reduce を抽象演算子として考える傾向があり、あたかもそれらの演算子を使用して実装の決定を明示的に延期しているかのように考えます。
編集: これはかなり古い質問であり、この問題に関する私の意見は多少変わりました。
まず第一に、私は割り当てに対して強い偏見を持っています。 lambda
変数への式。Python にはそのための特別な構文があるため (ヒント、 def
)。それに加えて、ラムダの使用法の多くは、名前が付いていなくても、事前定義された (そしてより効率的な) 実装を持っています。たとえば、問題の例は次のように省略できます。 (1).__add__
, で包む必要はありません。 lambda
または def
. 。他の多くの一般的な用途は、以下の組み合わせで実現できます。 operator
, itertools
そして functools
モジュール。
より望ましい:ラムダ関数または入れ子関数 (
def
)?
ラムダを使用することには、通常の関数 (式の中で作成される) よりも利点が 1 つありますが、欠点もいくつかあります。そのため、私は関数を作成することを好みます。 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
- であることに注意してください とても 無視できるほど。
結論
ラムダは、特異点を作成することを優先してコード行を最小限に抑えたい非公式な状況に適しています。
ラムダは、後から来るコード編集者に明確にする必要がある、より正式な状況、特にそれが自明ではない場合には適していません。
私たちは、オブジェクトに適切な名前を付ける必要があることを知っています。オブジェクトが持っている場合、どうやってそれを行うことができますか? いいえ 名前?
これらすべての理由から、私は次のような関数を作成することを好みます。 def
の代わりに lambda
.
- 計算時間。
- 名前のない関数。
- 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
ローカル スコープ内の変数にラムダを代入するだけの場合は、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)
私が見つけたラムダの使用法が 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) 無駄がありません。
ノスクロさんの意見に同意します。ちなみに、 一度使ったら捨てる 関数を使用する場合、ほとんどの場合、演算子モジュールの何かを使用したいだけです。
例:
次のシグネチャを持つ関数があります。myFunction(データ、コールバック関数)。
2つの要素を追加する関数を渡したいとします。
ラムダの使用:
myFunction(data, (lambda x, y : x + y))
Pythonの方法:
import operator
myFunction(data, operator.add)
もちろん、これは単純な例ですが、リストや辞書の項目セッター/ゲッターなど、演算子モジュールが提供するものはたくさんあります。すごくかっこいい。
lambda は新しい関数を生成するのに役立ちます。
def somefunc(x): return lambda y: x+y
f = somefunc(10)
f(2)
>>> 12
f(4)
>>> 14
大きな違いは、 def
関数はインラインで使用されます。私の意見では、これが最も便利な使用例です。 lambda
関数。たとえば、オブジェクトのリストを並べ替える場合は次のようになります。
my_list.sort(key=lambda o: o.x)
したがって、ラムダの使用はこの種の簡単な操作に留めることをお勧めします。これは、関数に名前を付けることで提供される自動ドキュメントの恩恵もあまり受けません。