52

Python で定数のリストからラムダ オブジェクトのリストを作成したいと考えています。例えば:

listOfNumbers = [1,2,3,4,5]
square = lambda x: x * x
listOfLambdas = [lambda: square(i) for i in listOfNumbers]

ただし、これを実行すると、ラムダ オブジェクトのリストが作成されます。

for f in listOfLambdas:
    print f(),

私はそれが印刷されることを期待します

1 4 9 16 25

代わりに、次のように出力します。

25 25 25 25 25

ラムダにすべて間違ったパラメータが与えられているようです。私は何か間違ったことをしましたか?それを修正する方法はありますか? 私はPython 2.4にいると思います。

編集:もう少し試してみると、これが思いつきました:

listOfLambdas = []
for num in listOfNumbers:
    action = lambda: square(num)
    listOfLambdas.append(action)
    print action()

予想される 1 から 25 までの正方形を出力しますが、以前の print ステートメントを使用します。

for f in listOfLambdas:
    print f(),

それでも私にすべて25のsを与えます。これら 2 つの印刷呼び出しの間で、既存のラムダ オブジェクトはどのように変化したのでしょうか?

関連する質問: map() とリスト内包表記の結果が異なるのはなぜですか?

4

9 に答える 9

39

あなたが持っている:

listOfLambdas = [lambda: i*i for i in range(6)]

for f in listOfLambdas:
    print f()

出力:

25
25
25
25
25
25

カリー化が必要です!おいしいだけでなく、このデフォルト値の「ハック」を使用します。

listOfLambdas = [lambda i=i: i*i for i in range(6)]

for f in listOfLambdas:
    print f()

出力:

0
1
4
9
16
25

に注意してくださいi=i。ここで魔法が起こります。

于 2009-01-17T01:51:59.027 に答える
34

リスト内包表記で作成しているラムダは、最終的に5になる変数iにバインドされていると思います。したがって、事後にラムダを評価すると、すべて5にバインドされ、計算されます。 25.2番目の例のnumでも同じことが起こっています。ループ内のラムダを評価すると、numは変更されていないため、正しい値が得られます。ループの後、numは5です。

何をしようとしているのかよくわからないので、解決策を提案する方法がわかりません。これはどう?

def square(x): return lambda : x*x
listOfLambdas = [square(i) for i in [1,2,3,4,5]]
for f in listOfLambdas: print f()

これにより、期待される出力が得られます。

1
4
9
16
25

これを考える別の方法は、ラムダが作成された時点でその字句環境を「キャプチャ」することです。したがって、numを指定すると、呼び出されるまでその値は実際には解決されません。これは混乱を招き、強力です。

于 2009-01-17T01:37:51.473 に答える
9

関数ステートメントが実行されると、それらは (字句的に) 囲んでいるスコープにバインドされます。

スニペットでは、ラムダはグローバル スコープにバインドされています。これforは、Python ではスイートが独立したスコープのユニットとして実行されないためです。forループの最後で、numは外側のスコープにバインドされます。デモ:

for num in range(1, 6):
    pass
assert num == 5 # num is now bound in the enclosing scope

したがって、forループ内で識別子をバインドすると、実際には囲んでいるスコープを操作しています。

for num in range(1, 6):
    spam = 12
assert num == 5 # num is now bound in the enclosing scope
assert spam == 12 # spam is also bound in the enclosing scope

リスト内包表記についても同じです:

[num for num in range(1, 6)]
assert num == 5

心が吹いています、私は知っています。Anywho、私たちの新たな知識により、作成しているラムダがnum、囲んでいるスコープにバインドされた (単一の) 識別子を参照していると判断できます。それはこれをより意味のあるものにするはずです:

functions = []
for number in range(1, 6):
    def fun():
        return number
    functions.append(fun)
assert all(fun() == 5 for fun in functions)
assert all(fun() is number for fun in functions)

そして、これをさらに実証する最もクールな部分は次のとおりです。

# Same as above -- commented out for emphasis.
#functions = []
#for number in range(1, 6):
#    def fun():
#        return number
#    functions.append(fun)
#assert all(fun() == 5 for fun in functions)
#assert all(fun() is number for fun in functions)
number = 6 # Rebind 6 in the scope and see how it affects the results.
assert all(fun() == 6 for fun in functions) 

したがって、もちろん、これに対する解決策は、numberバインドするそれぞれに対して新しいエンクロージング スコープを作成することです。Python では、モジュール、クラス、および関数を含む新しいエンクロージング スコープを作成できます。別の関数を囲む新しいスコープを作成するためだけに関数を使用するのが一般的です。

Python では、クロージャは別の関数を返す関数です。関数コンストラクタのようなもの。get_fun次の例で確認してください。

def get_fun(value):
    """:return: A function that returns :param:`value`."""
    def fun(): # Bound to get_fun's scope
        return value
    return fun

functions = []
for number in range(1, 6):
    functions.append(get_fun(number))
assert [fun() for fun in functions] == range(1, 6)

は関数であるためget_fun、独自の内部スコープを持つようになります。値を指定して呼び出すたびget_funに、その中のバインディングを追跡するために小さなテーブルが作成されます。つまり、「このスコープ内で、value識別子は渡されたものを指している」と言います。そのスコープは、ぶらぶらする理由がない限り、関数の実行の最後に消えます。

スコープ内から関数を返す場合、それは「スコープ テーブル」の一部がハングアップする正当な理由です。返される関数は、後で呼び出すときに、そのスコープ テーブルから何かを参照する可能性があります。そのため、Pythonfun内で が作成されたとき は、必要なときに便利なのスコープ テーブルについて説明します。get_funfunget_funfun

実行モデルの Python ドキュメントで、詳細と技術用語 (少し柔らかくしています) について詳しく読むことができます。で関数が参照する囲みスコープの部分を見ることもできますprint fun.__closure__value上記では、たまたま int であるへの参照が表示されます。

# Same as before, commented out for emphasis.
#functions = []
#for number in range(1, 6):
#    functions.append(get_fun(number))
#assert [fun() for fun in functions] == range(1, 6)
print functions[0].__closure__
# Produces: (<cell at 0x8dc30: int object at 0x1004188>,)
于 2009-01-17T11:13:42.747 に答える
3

[] の代わりに () を使用してみてください。

listOfLambdas = (lambda: square(i) for i in listOfNumbers)

そして、あなたは得るでしょう:

1
4
9
16
25
于 2014-05-15T18:58:02.863 に答える
2
listOfLambdas = [lambda i=i: square(i) for i in listOfNumbers]

または

listOfLambdas = map(lambda i: lambda: square(i), listOfNumbers)
于 2009-01-17T01:52:49.790 に答える
1

関数オブジェクトの実際のクラスを定義すると、何が起こっているのかを理解しやすくなることがあります。

>>> class square(object):
...   def __init__(self, val):
...     self.val = val
...   def __call__(self):
...     return self.val * self.val
...
>>> l = [1,2,3,4,5]
>>> funcs = [square(i) for i in l]
>>> for f in funcs:
...   print f()
...
1
4
9
16
25
>>>

確かに、ラムダやクロージャを使用するよりも少し冗長ですが、関数を使用して非自明なことをしようとすると、これが理解しやすくなります。

于 2009-01-17T01:46:37.633 に答える
0

次のこともできます。

>>> def squares():
...     for i in [1,2,3,4,5]:
...         yield lambda:i*i
... 
>>> print [square() for square in squares()]
[1, 4, 9, 16, 25]
于 2015-07-14T04:15:05.690 に答える
0

追加のコメントとして、sympy 行列からラムダ関数のリストを生成する可能性について概説したいと思います (それが最善の方法かどうかはわかりませんが、これが私のやり方であり、便利だと思います)。

import sympy as sp
sp.var('Ksi')
# generate sympy expressions for Berstein's polynomials
B_expr = sp.Matrix([sp.binomial(3, i) * Ksi**i * (1 - Ksi)**(3-i) for i in range(4)])
# lambdify them 
B = [sp.lambdify((Ksi), B_expr[i]) for i in range(4) ]
于 2017-01-10T08:02:39.067 に答える