リスト内包表記のラムダ関数


149

flambda関数は同じであるにもかかわらず、次の2つのリスト内包表記の出力が異なるのはなぜですか?

f = lambda x: x*x
[f(x) for x in range(10)]

そして

[lambda x: x*x for x in range(10)]

どちらも同じタイプtype(f)type(lambda x: x*x)返すことに注意してください。


[lambda x: x*x for x in range(10)]外部ループ関数fを繰り返し呼び出さないため、最初の関数より高速です。
リザ

@Selinap:...いいえ、代わりに、ループを介して毎回新しい関数を作成しています。...そしてこの新しい関数を作成するオーバーヘッドがあり、呼び出しは少し遅くなります(とにかく私のシステムでは)。
Gerrat、

@Gerrat:オーバーヘッドがあっても、まだ高速です。しかし、もちろん[x*x for x in range(10)]良いです。
リザ

34
私はグーグルfoobarアクセス​​を取得するためにここに入力しました:)
Gal Margalit

回答:


267

最初のものは、単一のラムダ関数を作成し、それを10回呼び出します。

2つ目は関数を呼び出しません。10の異なるラムダ関数を作成します。それらすべてをリストに入れます。最初のものと同等にするには、次のものが必要です。

[(lambda x: x*x)(x) for x in range(10)]

またはもっと良い:

[x*x for x in range(10)]

13
またはmap(lambda x: x*x, range(10))、そもそもOPが最初に意味したものでしょう。
ダニエルローズマン、

ええ、ラムダx:x * x ..(x)は信条のようです。
静的または2013

[lambda x:x * x for x in range(10)]は、基本的にはhaskellの関数です
moshe beeri

@DanielRoseman、または正確にははlist(map(lambda x: x*x, range(10)))あなたを与えるだろう[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
rrlamichhane

108

この質問は、「有名な」「明白な」Python構文の非常に臭い部分に触れます-優先されるもの、ラムダ、またはfor forリストの理解。

OPの目的は0から9の正方形のリストを生成することではなかったと思います。その場合は、さらに多くの解決策を提供できます。

squares = []
for x in range(10): squares.append(x*x)
  • これは命令構文の良い方法です。

しかし、それは重要ではありません。重要なのは、W(hy)TFはこのあいまいな表現なので直観に反するのでしょうか。そして、私は最後にばかげたケースを持っているので、あまりにも早く私の答えを却下しないでください(私は就職の面接でそれをしました)。

したがって、OPの内包表記はラムダのリストを返しました。

[(lambda x: x*x) for x in range(10)]

もちろん、これは2乗関数の10個の異なるコピーです。以下を参照してください。

>>> [lambda x: x*x for _ in range(3)]
[<function <lambda> at 0x00000000023AD438>, <function <lambda> at 0x00000000023AD4A8>, <function <lambda> at 0x00000000023AD3C8>]

ラムダのメモリアドレスに注意してください -それらはすべて異なります!

もちろん、この式のより「最適な」バージョン(ハハ)を作成することもできます。

>>> [lambda x: x*x] * 3
[<function <lambda> at 0x00000000023AD2E8>, <function <lambda> at 0x00000000023AD2E8>, <function <lambda> at 0x00000000023AD2E8>]

見る?同じラムダの3倍。

変数_として使用したことに注意してくださいfor。それはxin とは何の関係もありませんlambda(字句的に影が薄くなっています!)。それを得る?

構文の優先順位がそうではない理由を説明しませんが、それはすべて次のことを意味します。

[lambda x: (x*x for x in range(10))]

これは、、[[0, 1, 4, ..., 81]]または[(0, 1, 4, ..., 81)]、または私が最も論理的であると思う場合、これはlist1つの要素generatorのa-値を返します。それは事実ではありません、言語はこのように機能しません。

しかし、どうしたら...

for変数に影を落とさずに、変数で使用する場合はどうなりますlambdaか???

さて、がらくたが起こります。これを見てください:

[lambda x: x * i for i in range(4)]

これはもちろん意味します:

[(lambda x: x * i) for i in range(4)]

しかし、それは意味しません:

[(lambda x: x * 0), (lambda x: x * 1), ... (lambda x: x * 3)]

これはクレイジーです!

リスト内包表記のラムダは、この内包表記の範囲を閉鎖するものです。字句彼らはを参照してクロージャ、iそれらが評価された際の基準ではなく、その値を経由して!

したがって、この式:

[(lambda x: x * i) for i in range(4)]

次とほぼ同等です:

[(lambda x: x * 3), (lambda x: x * 3), ... (lambda x: x * 3)]

Pythonデコンパイラー(つまり、disモジュールなど)を使用すると、ここでさらに多くを見ることができると思いますが、Python-VMに依存しない議論にはこれで十分です。就職の面接の質問はこれで終わりです。

listでは、実際に連続した整数を乗算する乗数ラムダを作成するにはどうすればよいですか?まあ、受け入れられた答えと同様に、リスト内包表現式の中で呼び出さているilambdaのでラップすることで、直接のつながりを壊す必要があります。

前:

>>> a = [(lambda x: x * i) for i in (1, 2)]
>>> a[1](1)
2
>>> a[0](1)
2

後:

>>> a = [(lambda y: (lambda x: y * x))(i) for i in (1, 2)]
>>> a[1](1)
2
>>> a[0](1)
1

(外側のラムダ変数も= iでしたが、これがより明確な解決策であると判断しました-私yたちは誰がどの魔女がどれであるかを確認できるように導入しました)。

2019-08-30を編集:

@sheridpによる回答にも含まれている@josolerの提案に従います-リスト内包の「ループ変数」の値はオブジェクト内に「埋め込む」ことができます-キーは適切なタイミングでアクセスするためのキーです。上記の「後」のセクションでは、別のコードでラップlambdaし、現在の値ですぐに呼び出していますi。別の方法(少し読みやすい-'WAT'効果はありません)はipartialオブジェクトの内部に値を格納し、「内部」(元の)lambdaにそれを引数として渡す(partialオブジェクトでオブジェクトから渡される)呼び出しの時間)、すなわち:

2の後:

>>> from functools import partial
>>> a = [partial(lambda y, x: y * x, i) for i in (1, 2)]
>>> a[0](2), a[1](2)
(2, 4)

すばらしいですが、まだ少しひねりがあります!コードリーダーで簡単にしたくない場合は、名前で(のキーワード引数として)因子を渡しpartialます。名前を変更しましょう:

2.5以降:

>>> a = [partial(lambda coef, x: coef * x, coef=i) for i in (1, 2)]
>>> a[0](1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: <lambda>() got multiple values for argument 'coef'

WAT?

>>> a[0]()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: <lambda>() missing 1 required positional argument: 'x'

待って...引数の数を1つ変更して、「多すぎる」から「少なすぎる」に変更しますか?

まあ、それは我々が通過したときに、本当のWATではありませんcoefpartial、このように、それは、位置の後に来なければならないので、それは、キーワード引数になりxそうのように、引数:

3の後:

>>> a = [partial(lambda x, coef: coef * x, coef=i) for i in (1, 2)]
>>> a[0](2), a[1](2)
(2, 4)

私はネストされたラムダよりも最後のバージョンを好みますが、それぞれ独自のものより...


22
それは残酷で珍しい就職の面接の質問です。
szeitlin

1
私の同僚が尋ねなかった場合、私はおそらくこの答えを検索することは決してないでしょう
piggybox '10 / 11/16

8
ワオ。私はこの不条理な振る舞いにひどく噛まれました。投稿ありがとうございます!
Ant

1
すばらしい答えです。私もこの問題に遭遇しました。一方ではPythonの制限を示していますが、他方ではコードの臭いインジケータでもある可能性があります。私はこのソリューションをおもちゃのプロジェクトに使用していますが、実稼働環境で再構築する必要があるかもしれません。
アホタ

2
:明確さと完全を期すために次のような最後のリスト内包書くことができます[partial(lambda i, x: i * x, i) for i in (1, 2)]
josoler

19

大きな違いは、最初の例では実際にlambdaが呼び出されるのf(x)に対し、2番目の例では呼び出されないことです。

最初の例はと同等ですが[(lambda x: x*x)(x) for x in range(10)]、2番目の例はと同等[f for x in range(10)]です。


11

最初の1つ

f = lambda x: x*x
[f(x) for x in range(10)]

f()範囲内の各値に対して実行されるため、各値に対して実行されf(x)ます

二番目

[lambda x: x*x for x in range(10)]

リストの各値に対してラムダを実行するので、これらの関数がすべて生成されます。


11

人々は良い答えを出しましたが、私の意見の中で最も重要な部分を述べるのを忘れていました:2番目の例でXは、リスト内包表記Xlambda機能は関数の機能と同じではなく、それらは完全に無関係です。したがって、2番目の例は実際には次のようになります。

[Lambda X: X*X for I in range(10)]

の内部反復 range(10)は、リストに10個の類似のラムダ関数を作成することのみを担当します(10個の個別の関数ですが、完全に類似しています-各入力の2のべき乗を返します)。

一方、最初の例はまったく異なります。反復のXは結果と相互に作用するため、反復ごとに値は次のX*Xようになるためです。[0,1,4,9,16,25, 36, 49, 64 ,81]


これは重要なポイントです。私はあなたを賛成し、私の答えでそれについて詳しく述べました。
Tomasz Gandor

6

他の答えは正しいですが、後で実行できる、それぞれが異なるパラメーターを持つ関数のリストを作成しようとしている場合、次のコードがそれを行います。

import functools
a = [functools.partial(lambda x: x*x, x) for x in range(10)]

b = []
for i in a:
    b.append(i())

In [26]: b
Out[26]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

この例は人為的なものですが、それぞれが異なるものを出力する関数のリストが必要な場合、つまり、

import functools
a = [functools.partial(lambda x: print(x), x) for x in range(10)]

for i in a:
    i()
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.