web-dev-qa-db-ja.com

Pythonループ内のラムダ

次のコードスニペットを検討してください。

# directorys == {'login': <object at ...>, 'home': <object at ...>}
for d in directorys:
    self.command["cd " + d] = (lambda : self.root.change_directory(d))

次の2つの関数の辞書を作成する予定です。

# Expected :
self.command == {
    "cd login": lambda: self.root.change_directory("login"),
    "cd home": lambda: self.root.change_directory("home")
}

しかし、生成された2つのラムダ関数はまったく同じように見えます:

# Result :
self.command == {
    "cd login": lambda: self.root.change_directory("login"),
    "cd home": lambda: self.root.change_directory("login")   # <- Why login ?
}

なぜか分からない。何か提案はありますか ?

52
FunkySayu

作成された関数ごとにdをバインドする必要があります。それを行う1つの方法は、デフォルト値を持つパラメーターとして渡すことです。

lambda d=d: self.root.change_directory(d)

これで、関数内のdは同じ名前であるにもかかわらず、パラメーターを使用し、そのデフォルト値は関数の作成時に評価されます。これを見やすくするために:

lambda bound_d=d: self.root.change_directory(bound_d)

オブジェクトをバインドしているため、リストや辞書などの可変オブジェクトの場合のデフォルト値の動作を覚えておいてください。

このデフォルト値を持つパラメーターのイディオムは十分に一般的ですが、関数パラメーターをイントロスペクトし、それらの存在に基づいて何をすべきかを決定すると失敗する場合があります。別のクロージャーでパラメーターを回避できます。

(lambda d=d: lambda: self.root.change_directory(d))()
# or
(lambda d: lambda: self.root.change_directory(d))(d)
69
Roger Pate

これは、dがバインドされているポイントによるものです。ラムダ関数はすべて、現在のvalueではなく、variabledを指しているため、更新するとd次の反復で、この更新はすべての関数で見られます。

より簡単な例:

funcs = []
for x in [1,2,3]:
  funcs.append(lambda: x)

for f in funcs:
  print f()

# output:
3
3
3

これを回避するには、次のような追加の関数を追加します。

def makeFunc(x):
  return lambda: x

funcs = []
for x in [1,2,3]:
  funcs.append(makeFunc(x))

for f in funcs:
  print f()

# output:
1
2
3

ラムダ式内のスコープを修正することもできます

lambda bound_x=x: bound_x

ただし、一般的に、これはnotであり、関数のシグネチャを変更したため、良い習慣です。

20
robbie_c

同じ問題に遭遇しました。選択したソリューションは私を大いに助けましたが、問題のコードを機能的にするために精度を追加する必要があると考えています:ループの外側でラムダ関数を定義します。ところで、デフォルト値は必要ありません。

foo = lambda d: lambda : self.root.change_directory(d)
for d in directorys:
    self.command["cd " + d] = (foo(d))

または、lambdaの代わりに、 functools.partial これは、私の意見では、より簡潔な構文を持っています。

の代わりに:

for d in directorys:
    self.command["cd " + d] = (lambda d=d: self.root.change_directory(d))

そうなる:

for d in directorys:
    self.command["cd " + d] = partial(self.root.change_directory, d)

または、別の簡単な例を示します。

numbers = [1, 2, 3]

lambdas = [lambda: print(number) 
           for number in numbers]
lambdas_with_binding = [lambda number=number: print(number) 
                        for number in numbers]
partials = [partial(print, number) 
            for number in numbers]

for function in lambdas:
    function()
# 3 3 3
for function in lambdas_with_binding:
    function()
# 1 2 3
for function in partials:
    function()
# 1 2 3
0
Georgy