web-dev-qa-db-ja.com

Pythonラムダ(リスト内包/ forループ内)のリストを作成するにはどうすればよいですか?

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(),

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

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

37
Smashery

リスト内包表記で作成しているラムダは、最終的に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呼び出されるまで、実際にはその値は解決されません。これは混乱を招き、強力です。

25
Dave Ray

あなたが持っている:

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に注意してください。ここで魔法が起こります。

27
recursive

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

スニペットでは、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

心が吹く、私は知っています。誰でも、私たちの新たな知識により、作成しているラムダが、囲んでいるスコープにバインドされた(単一の)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識別子は渡されたものを指します」と表示されます。そのスコープは、それがぶらぶらする理由がない限り、関数の実行の終わりに消えます。

スコープ内から関数を返す場合、それが「スコープテーブル」の一部がぶらぶらする良い理由です。返される関数は、後で呼び出すときにそのスコープテーブルからのものを参照する可能性があります。そのため、funget_fun内に作成されるとPythonはfunget_funのスコープテーブルについて通知します。これは、funが必要なときに便利です。

詳細と技術用語(少し和らげました)の詳細については、 実行モデルに関する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>,)
4
cdleary
listOfLambdas = [lambda i=i: square(i) for i in listOfNumbers]

または

listOfLambdas = map(lambda i: lambda: square(i), listOfNumbers)
2
jfs

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

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

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

1
4
9
16
25
2
potar

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

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

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

1
Jason Baker

次のこともできます。

>>> 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]
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) ]
0
MarcoMag