質問: ("",20)) label.pack() label.bind("", lambda event : click_word(event)) root.mainloop() ``` 補足 -- 関数の定義「def ~」をfor in の中に書いてみても、同じようになりました。 関数を定義するときに引数に「word」を渡してみても、同じようになりました。 実際に作っているものはこれではないのですが、実際に作っているもののコードをのせると長いので、ここのソースコードは同じ状況を作ったものをのせています。 ラベルの文字をクリックすると何かの処理をするのに、ラベル一つ一つに対して関数を作ればうまくいくのかもしれませんが、実際に作っているものは、リストの要素の中で条件を満たしたものだけをラベルで表示し、 それをクリックして何かの処理をするといった感じで、条件を満たしたものが何なのか、数はいくつなのかがわからないので、このやり方でやろうと思いました。 プログラミング教室などに通っているわけでもなく、趣味で気の向くままにやっているので、 大事な知識や考え方などが欠けていると思います・・・
回答: 本件の場合、外側のスコープの変数を参照せずとも「イベントの発生に関わる情報」がeventに渡されるのでそこから情報を引き出せばよいです。そのような目的があるからこそイベントハンドラーにはeventという引数が渡されるわけです。 ``` ... def click_word(event) : print(event.widget['text']) ... ``` しかし・・・変数のスコープの問題を「きちんと理解する」ことで上記の方法ではない方法でも実現は可能です。質問者さんの疑問はその点にあると思うのでそれについてもコメントしてみます。 > > どうしてこうなってしまったのでしょうか。 > > (A)関数の定義「def ~」をfor in の中に書いてみても、同じようになりました。 > > (B)関数を定義するときに引数に「word」を渡してみても、同じようになりました。 > > > このコードの動きを理解するには変数wordのスコープの範囲を知っておく必要があります。 本件の場合wordは`__main__`モジュールのグローバル変数になります。要はfor文の繰り返し部分にとどまらずfor文の前後で参照した場合も常に同じグローバル変数のアクセスという意味になります。 for文による繰り返しが全て完了した後、wordの値は最後の要素の値`'こ'`になっており、それ以降wordに他の値を代入しない限りその値は常に`'こ'`にしかなりません。 (A),(B)のいずれにせよ変数wordのスコープ範囲は変わらないので結果は同様になります。 (A) ``` ... for word in word_list: def click_word(event): print(word) ... label.bind("", lambda event: click_word(event)) ... ``` 上記のようにfor文の内側に関数を書いたとしても関数本体で参照しているwordはグローバル変数であることに変わりはないので結果は同じです。 (B) ``` def click_word(event, w): print(w) # 引数w for word in word_list: # グローバル変数word ... label.bind("", lambda event: click_word(event, word)) # グローバル変数word ... ``` 上記のようにwordを関数の引数に渡そうとしても、lambda式(それも結局は関数と同じ)の内側で参照している変数wordがグローバル変数の参照であるという意味は変わらないので結局(A)と同じ結果にしかなりません。 ループの各繰り返しを実行している時点での変数wordの値を関数へ渡すには「wordのその時点の値を別のローカル変数に束縛しておく」必要があります。Python言語ではある値に束縛されたローカル変数を形成するには「関数の呼び出し」を行うのが基本になります(他の言語ですと変数のスコープを形成するような構文があったりしますがPythonにはあまりないです※)。もちろん「クリックしたときに起動される関数」を呼び出してはならないので、「クリックしたときに起動される関数を返すような別の関数を呼び出す」という考え方にします。 ``` def click_function(w): def click_word(event): print(w) # 外側の関数の引数w return click_word for word in word_list: # グローバル変数word ... label.bind("", click_function(word)) ... ``` Pythonの関数(Python以外の言語の関数も同様だが)は通常「その関数の内側で宣言されたローカル変数以外に関数の外側のスコープにある変数(グローバル変数や外側の関数のローカル変数)」も参照する能力を持っています。 外にあるスコープを参照する能力のある関数のことを特にクロージャーと呼ぶことがあります。クロージャーは元としたローカル変数wの定義元であるclick_functionの実行が終わった後でもwの束縛情報を保存し続けることができるのですね。つまりclick_functionの実行時にしか存在しないはずのローカル変数がclick_word関数の中に「密かに記録されている」わけです。 なお上記はlambda式を用いてもっと短く書くこともできます。defによって関数を入れ子で定義したのと同様にlambdaの入れ子になるので慣れないとちょっと戸惑うかも知れません。 ``` def click_word(event, w): print(w) for word in word_list: ... label.bind("", (lambda x: lambda ev: click_word(ev, x))(word)) ... ``` 脱線: ※他の言語でのローカル変数を形成する構文 例えばJavaScript(ECMAScript 2015)だと次のように`{}`と`let`を組み合わせた書き方ができます。 ``` for (let word of word_list) { ... label.addEventListener('click', ev -> onClick(ev, word)); } ``` この場合wordはPythonのようにグローバル変数ではなくfor文の繰り返し本体(`{}`の中)でのみ有効な局所的な変数になり、質問者さんがやろうとした(A)や(B)のアプローチで期待どうりに動いてくれます。残念ながらPythonはこのように手軽に変数のスコープを形成する手段がありませんので「一旦別の関数を呼び出す」ということが必要になります。