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

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

新規登録して質問してみよう
ただいま回答率
85.50%
Tkinter

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

Q&A

解決済

1回答

2246閲覧

unbind()に間違ったidを渡したのにunbindされてしまう

gottadiveintopy

総合スコア736

Tkinter

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

1グッド

1クリップ

投稿2018/11/10 05:06

編集2018/11/11 05:51

tkinterにおいてwidgetのbind()の戻り値をunbind()に渡すことで解除できると思うのですが、以下のように1つ目のbind()時のidを渡しているのに2つ目のbind()が解除できてしまうのはどうしてですか?

python3

1from tkinter import * 2 3root = Tk() 4button = Button(root, text='Push Me', font=('', 40)) 5button.pack() 6 7bind_id = button.bind('<Button-1>', lambda event: print('1st callback')) 8button.unbind('<Button-1>', bind_id) 9bind_id2 = button.bind('<Button-1>', lambda event: print('2nd callback')) 10button.unbind('<Button-1>', bind_id) # 一つ目のbind()時のidを渡している 11 12root.mainloop()

screenshot

出てきたButtonを押しても何も出力されないので二つ目のbind()も解除されているように思えます。

環境

  • OS ... LinuxMint18.2 MATE Edition (Ubuntu16.04LTS派生)
  • Python 3.6.2
  • tkinter.TkVersion ... 8.6
  • tkinter.TclVersion ... 8.6

追記

上のコードでは指摘された通りbind_idbind_id2が同じになっていました。なのでErrorが怒らなかったものと思われます。ただそれでもはやりunbind()が正しく機能していないように思えます。理由は以下のコードで

python

1from tkinter import * 2 3 4root = Tk() 5button = Button(root, text='Push Me', font=('', 40)) 6button.pack() 7 8bind_id = button.bind('<Button-1>', lambda event: print('1st callback'), '+') 9print(bind_id) 10bind_id2 = button.bind('<Button-1>', lambda event: print('2nd callback'), '+') 11print(bind_id2) 12 13button.unbind('<Button-1>', bind_id) # A 14# button.unbind('<Button-1>', bind_id2) # B 15 16root.mainloop()

A行とB行のどちらか一つを有効にしただけで、callback関数は両方共呼ばれなくなってしまうからです。

追記2

上の問題に関してはstackoverflowにあったこの投稿を参考にunbind()を修正した所、期待通りの動作をしてくれました。

python

1from tkinter import * 2 3 4def new_unbind(self, sequence, funcid=None): 5 """Unbind for this widget for event SEQUENCE the 6 function identified with FUNCID.""" 7 if not funcid: 8 self.tk.call('bind', self._w, sequence, '') 9 return 10 func_callbacks = self.tk.call('bind', self._w, sequence, None).split('\n') 11 new_callbacks = [l for l in func_callbacks if l[6:6 + len(funcid)] != funcid] 12 self.tk.call('bind', self._w, sequence, '\n'.join(new_callbacks)) 13 self.deletecommand(funcid) 14 15 16old_unbind = Misc.unbind 17Misc.unbind = new_unbind 18 19 20root = Tk() 21button = Button(root, text='Push Me', font=('', 40)) 22button.pack() 23 24bind_id = button.bind('<Button-1>', lambda event: print('1st callback'), '+') 25print(bind_id) 26bind_id2 = button.bind('<Button-1>', lambda event: print('2nd callback'), '+') 27print(bind_id2) 28 29# button.unbind('<Button-1>', bind_id) # A 30button.unbind('<Button-1>', bind_id2) # B 31 32root.mainloop()
KSwordOfHaste👍を押しています

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

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

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

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

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

guest

回答1

0

ベストアンサー

訂正: 質問にある二番目の問題がWindows/Linuxの両方で起きることや質問者さんの追記にあるstackoverflowの内容を見る限りどちらかといえばtkinterの不具合 or 制限と認識すべきという考えに変わりました。いずれにせよ元の回答は中途半端な内容であると思います。

以下元の回答

「ん?」と思ったのでご質問のスクリプトを実行してみたのですが・・・どうも環境/バージョンによる仕様であるような気がしました。

windows 10
Python 3.7.0 (CPython)
Tcl/Tkのバージョン 8.6 (import tkinter as tk;print(tk.TclVersion, tk.TkVersion))

だと、bind_idとbind_id2は異なる値となりbind_idを複数unbindするとエラーが起きます。

_tkinter.TclError: can't delete Tcl command

一方

Ubuntu 18.04 LTS
Python 6.3.3
Tcl/Tkのバージョン 8.6

だと、bind_idとbind_id2は同一の値となりbind_idを複数unbindしてもエラーは起きませんでした。(MacintoshもUnix系ですので似た現象になるかも?)

そこで「もしや」と思って次のようにしたところ期待どおり(?)、

  • bind_idとbind_id2が異なる値になる
  • 2回目のunbindでエラーになる

という動作になりました。

Python

1from tkinter import * 2 3root = Tk() 4button = Button(root, text='Push Me', font=('', 40)) 5button.pack() 6 7bind_id = button.bind('<Button-1>', lambda event: print('1st callback')) 8print(bind_id) 9bind_id2 = button.bind('<Button-1>', lambda event: print('2nd callback')) 10print(bind_id2) 11 12button.unbind('<Button-1>', bind_id) 13button.unbind('<Button-1>', bind_id) # 一つ目のbind()時のidを渡している 14 15root.mainloop()

つまりたまたまWindowsでは一度unbindしたbind_idは再利用されず(されにくく?)、Unix系の方ではunbind後にbindすると同じbind_idが即座に(?)使いまわされるのではないかと思いました。その動作の違いから「同じbind_idが複数回削除できてしまうように見えた」だけで実は「異なるバインディングを解除していた」ということのようです。

投稿2018/11/10 09:06

編集2018/11/11 03:15
KSwordOfHaste

総合スコア18392

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

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

gottadiveintopy

2018/11/10 14:14

回答ありがとうございます。 私の環境はUbuntuなのですが、KSwordOfHasteさんの検証と同じでbind_idとbind_id2が同一でした。(環境情報載せ忘れてました、すいません)。ただそれでもまだよく分からない部分があるので、教えてもらえないでしょうか?(別の問題を追記しました。)
KSwordOfHaste

2018/11/10 14:44

うーん、おっしゃるとおりですね。もっと突っ込んで調べてみないことには自分もわかりません。 回避策は考えられると思うのですが、そもそもこれが仕様なのか不具合なのかはっきりしないと回避策を採るは早計ですよね・・・ ほとんど根拠がない単なる勘ですが、tkinterというよりbackend側(tcl/tk?)の動作の違いに起因するような気がします。bindの実装をちょっとみただけですが引数からパラメーターをこしらえてtclを呼び出しているだけのように見えるので。
gottadiveintopy

2018/11/10 15:39 編集

二つ目の問題はunbind()のコードを修正する事で一応期待通りに動作はしてくれました。コードを見るにidの連なった文字列から該当のidを探してそこから取り除く作業を自分でやってるように思います。不具合かもですね。
KSwordOfHaste

2018/11/10 15:39

「まずは動かす」てのは大事ですね・・・ ネットを少し探してみたのですが自分ではちょっと力不足のようでそれらしい情報に行き当たれてません。これはすぐには無理そうです。どなたか情報をおよせくださるとうれしいのですが・・・
gottadiveintopy

2018/11/11 00:54

> ネットを少し探してみたのですが自分ではちょっと力不足のようで そんなことないですよ、すごい助かってます。 二つ目の問題に関してですが引き続き調べてみて何か分かったら追記します。色々と検証していただき本当にありがとうございました!
KSwordOfHaste

2018/11/11 03:09

二つ目の問題の追記を見逃してました! 「どなたか情報を...」なんて間抜けなことを書いてしまい失礼しました。stackoverflowを見る限りは「バグだ」と言ってますね。また確認してみますとLinux/Windowsともに二つ目の問題が起きますね。先のコメントではLinuxしか確認してませんでした。先入観のため色々確認し損ねてます。反省... 少なくともPython 3.7.0(?)ではまだこの問題が対策されてはいないということかと思います。
gottadiveintopy

2018/11/11 05:47

いえこちらこそ追記したことを書き忘れてました、ごめんなさい。 なるほどWindowでも起こるのならtk/tcl/tkinter側の問題と見て良さそうですね。一応tkinterのソースコードを読んだんですが、ほとんどのmethodがtk/tcl側に処理を委ねてるだけで、しかもその委ねた先はソースコードが無くて原因が追えないので、実行時にMisc.unbind()を差し替えるの方法でいこうかなぁと考えてます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問