質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

2回答

3777閲覧

python tkinter 「ラベルの文字をクリックするとその文字をprint()で表示する」ことをしたい

hikaru632

総合スコア21

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

1クリップ

投稿2019/04/06 16:00

##実現したいこと
「ラベルの文字をクリックするとその文字をprint()で表示する」ということをしたいです。

##困っていること
for in でリストから取り出した文字を表示させるとき、
ラベルではうまく取り出した文字を表示させるのに対して、
そのラベルをクリックしたときにprint()で表示させる文字は、
どの文字をクリックしても、リストの一番最後になってしまいます・・・

どうやったら、クリックして表示させる文字をラベルの文字と同じにできるのでしょうか。
また、これはどうしてこうなってしまったのでしょうか。

##ソースコード

python

1import tkinter as tk 2 3root = tk.Tk() 4root.geometry("200x400") 5 6word_list = ["あ","い","う","え","お","か","き","く","け","こ"] 7 8#ラベルの文字を押すとクリックされた文字を表示する(はずだった)関数の定義 9def click_word(event) : 10 print(word) 11 12for word in word_list : 13 label = tk.Label(root, text = f"{word}", font = ("",20)) 14 label.pack() 15 label.bind("<Button-1>", lambda event : click_word(event)) 16 17root.mainloop()

##補足
関数の定義「def ~」をfor in の中に書いてみても、同じようになりました。
関数を定義するときに引数に「word」を渡してみても、同じようになりました。

実際に作っているものはこれではないのですが、実際に作っているもののコードをのせると長いので、ここのソースコードは同じ状況を作ったものをのせています。
ラベルの文字をクリックすると何かの処理をするのに、ラベル一つ一つに対して関数を作ればうまくいくのかもしれませんが、実際に作っているものは、リストの要素の中で条件を満たしたものだけをラベルで表示し、
それをクリックして何かの処理をするといった感じで、条件を満たしたものが何なのか、数はいくつなのかがわからないので、このやり方でやろうと思いました。
プログラミング教室などに通っているわけでもなく、趣味で気の向くままにやっているので、
大事な知識や考え方などが欠けていると思います・・・

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

本件の場合、外側のスコープの変数を参照せずとも「イベントの発生に関わる情報」がeventに渡されるのでそこから情報を引き出せばよいです。そのような目的があるからこそイベントハンドラーにはeventという引数が渡されるわけです。

python

1... 2 3def click_word(event) : 4 print(event.widget['text']) 5 6...

しかし・・・変数のスコープの問題を「きちんと理解する」ことで上記の方法ではない方法でも実現は可能です。質問者さんの疑問はその点にあると思うのでそれについてもコメントしてみます。

どうしてこうなってしまったのでしょうか。
(A)関数の定義「def ~」をfor in の中に書いてみても、同じようになりました。
(B)関数を定義するときに引数に「word」を渡してみても、同じようになりました。

このコードの動きを理解するには変数wordのスコープの範囲を知っておく必要があります。
本件の場合wordは__main__モジュールのグローバル変数になります。要はfor文の繰り返し部分にとどまらずfor文の前後で参照した場合も常に同じグローバル変数のアクセスという意味になります。

for文による繰り返しが全て完了した後、wordの値は最後の要素の値'こ'になっており、それ以降wordに他の値を代入しない限りその値は常に'こ'にしかなりません。

(A),(B)のいずれにせよ変数wordのスコープ範囲は変わらないので結果は同様になります。

(A)

Python

1... 2 3for word in word_list: 4 5 def click_word(event): 6 print(word) 7 8 ... 9 label.bind("<Button-1>", lambda event: click_word(event)) 10 11...

上記のようにfor文の内側に関数を書いたとしても関数本体で参照しているwordはグローバル変数であることに変わりはないので結果は同じです。

(B)

Python

1def click_word(event, w): 2 print(w) # 引数w 3 4for word in word_list: # グローバル変数word 5 ... 6 label.bind("<Button-1>", lambda event: click_word(event, word)) # グローバル変数word 7 8...

上記のようにwordを関数の引数に渡そうとしても、lambda式(それも結局は関数と同じ)の内側で参照している変数wordがグローバル変数の参照であるという意味は変わらないので結局(A)と同じ結果にしかなりません。

ループの各繰り返しを実行している時点での変数wordの値を関数へ渡すには「wordのその時点の値を別のローカル変数に束縛しておく」必要があります。Python言語ではある値に束縛されたローカル変数を形成するには「関数の呼び出し」を行うのが基本になります(他の言語ですと変数のスコープを形成するような構文があったりしますがPythonにはあまりないです※)。もちろん「クリックしたときに起動される関数」を呼び出してはならないので、「クリックしたときに起動される関数を返すような別の関数を呼び出す」という考え方にします。

Python

1def click_function(w): 2 3 def click_word(event): 4 print(w) # 外側の関数の引数w 5 6 return click_word 7 8for word in word_list: # グローバル変数word 9 ... 10 label.bind("<Button-1>", click_function(word)) 11 12...

Pythonの関数(Python以外の言語の関数も同様だが)は通常「その関数の内側で宣言されたローカル変数以外に関数の外側のスコープにある変数(グローバル変数や外側の関数のローカル変数)」も参照する能力を持っています。

外にあるスコープを参照する能力のある関数のことを特にクロージャーと呼ぶことがあります。クロージャーは元としたローカル変数wの定義元であるclick_functionの実行が終わった後でもwの束縛情報を保存し続けることができるのですね。つまりclick_functionの実行時にしか存在しないはずのローカル変数がclick_word関数の中に「密かに記録されている」わけです。

なお上記はlambda式を用いてもっと短く書くこともできます。defによって関数を入れ子で定義したのと同様にlambdaの入れ子になるので慣れないとちょっと戸惑うかも知れません。

python

1def click_word(event, w): 2 print(w) 3 4for word in word_list: 5 ... 6 label.bind("<Button-1>", (lambda x: lambda ev: click_word(ev, x))(word)) 7 8...

脱線:
※他の言語でのローカル変数を形成する構文

例えばJavaScript(ECMAScript 2015)だと次のように{}letを組み合わせた書き方ができます。

js

1for (let word of word_list) { 2 ... 3 label.addEventListener('click', ev -> onClick(ev, word)); 4}

この場合wordはPythonのようにグローバル変数ではなくfor文の繰り返し本体({}の中)でのみ有効な局所的な変数になり、質問者さんがやろうとした(A)や(B)のアプローチで期待どうりに動いてくれます。残念ながらPythonはこのように手軽に変数のスコープを形成する手段がありませんので「一旦別の関数を呼び出す」ということが必要になります。

投稿2019/04/06 22:51

KSwordOfHaste

総合スコア18394

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hikaru632

2019/04/07 02:47 編集

関数に渡されるのがグローバル変数であるがために、for in でグローバル変数が変わってしまうと、関数で使われる値も新しいグローバル変数になってしまい、 しかもイベントが発生しない限り関数は実行されないので、for in が実行され終わってしまい、最後にグローバル変数を定義した「こ」になっていた。 それを回避するために、クロージャーという方法を用いると、外側の関数で受け取ったグローバル変数は内側の関数ではローカル変数となるので、for in でグローバル変数を変えてしまっても、ローカル変数は変わらず、変数を束縛・保存しておくことができるということでしょうか?
KSwordOfHaste

2019/04/07 03:14

概念としての変数が何かといえば「変数名とその値のペア情報」と考えられます。 グローバル変数であれ外側のローカル変数であれ「変数名とその値のペア」であることに変わりはありません。どちらの場合でも参照しようとしている変数の値が後から変化すれば参照結果もかわります。 click_functionのローカル変数wは「その呼び出しに関しては後から値を変更していない」ためclick_word関数から参照したとき常に同じ値が得られるということです。click_functionから返される関数click_wordはclick_functionを呼び出すたびに新たに生成される別々の関数になる点に注意してください。その別々の関数で「それぞれ異なるwの値を表すクロージャー」になっているわけです。
hikaru632

2019/04/07 03:48 編集

なるほど! この問題を解決するいくつかの方法を紹介してくださった上、ちょうど頭の中でごちゃごちゃとしてよく理解していなかったスコープや、クロージャーをわかりやすく、こんなに丁寧で詳しく説明してくださって本当に感激です!! 概念やロジックを理解した上で、問題を解決し、それを方法だけでなく概念やロジックなどとともにわかりやすく教えられるなんてかっこいいなと思いました。 KSwordOfHasteさんみたいにできるようになりたいです。 私はまだ子供なので、プログラミングは趣味の範囲でしかやっておらず、とりあえず作れればいいかという感じで、根本的な理解はガン無視していましたが、今後は概念や色々な方法の勉強もしていこうと思いました。 実際に作っているプログラムもここで教えていただいたことを使ってうまくできました! ありがとうございました!!
guest

0

クリックされた文字というのを(どこからか)取得して表示する、ということをしなければなりませんが、提示のコードではそうなってませんね。
このコードではどういう操作をしている(つもり)のか、説明ができるでしょうか

投稿2019/04/06 22:35

y_waiwai

総合スコア87749

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問