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

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

ただいまの
回答率

90.40%

  • Tkinter

    204questions

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

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

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 93

gottadiveintopy

score 322

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

from tkinter import *

root = Tk()
button = Button(root, text='Push Me', font=('', 40))
button.pack()

bind_id = button.bind('<Button-1>', lambda event: print('1st callback'))
button.unbind('<Button-1>', bind_id)
bind_id2 = button.bind('<Button-1>', lambda event: print('2nd callback'))
button.unbind('<Button-1>', bind_id)  # 一つ目のbind()時のidを渡している

root.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()が正しく機能していないように思えます。理由は以下のコードで

from tkinter import *


root = Tk()
button = Button(root, text='Push Me', font=('', 40))
button.pack()

bind_id = button.bind('<Button-1>', lambda event: print('1st callback'), '+')
print(bind_id)
bind_id2 = button.bind('<Button-1>', lambda event: print('2nd callback'), '+')
print(bind_id2)

button.unbind('<Button-1>', bind_id)  # A
# button.unbind('<Button-1>', bind_id2)  # B

root.mainloop()

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

 追記2

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

from tkinter import *


def new_unbind(self, sequence, funcid=None):
    """Unbind for this widget for event SEQUENCE  the
    function identified with FUNCID."""
    if not funcid:
        self.tk.call('bind', self._w, sequence, '')
        return
    func_callbacks = self.tk.call('bind', self._w, sequence, None).split('\n')
    new_callbacks = [l for l in func_callbacks if l[6:6 + len(funcid)] != funcid]
    self.tk.call('bind', self._w, sequence, '\n'.join(new_callbacks))
    self.deletecommand(funcid)


old_unbind = Misc.unbind
Misc.unbind = new_unbind


root = Tk()
button = Button(root, text='Push Me', font=('', 40))
button.pack()

bind_id = button.bind('<Button-1>', lambda event: print('1st callback'), '+')
print(bind_id)
bind_id2 = button.bind('<Button-1>', lambda event: print('2nd callback'), '+')
print(bind_id2)

# button.unbind('<Button-1>', bind_id)  # A
button.unbind('<Button-1>', bind_id2)  # B

root.mainloop()
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

訂正: 質問にある二番目の問題が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でエラーになる

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

from tkinter import *

root = Tk()
button = Button(root, text='Push Me', font=('', 40))
button.pack()

bind_id = button.bind('<Button-1>', lambda event: print('1st callback'))
print(bind_id)
bind_id2 = button.bind('<Button-1>', lambda event: print('2nd callback'))
print(bind_id2)

button.unbind('<Button-1>', bind_id)
button.unbind('<Button-1>', bind_id)  # 一つ目のbind()時のidを渡している

root.mainloop()

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/11/10 23:14

    回答ありがとうございます。

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

    キャンセル

  • 2018/11/10 23:44

    うーん、おっしゃるとおりですね。もっと突っ込んで調べてみないことには自分もわかりません。
    回避策は考えられると思うのですが、そもそもこれが仕様なのか不具合なのかはっきりしないと回避策を採るは早計ですよね・・・

    ほとんど根拠がない単なる勘ですが、tkinterというよりbackend側(tcl/tk?)の動作の違いに起因するような気がします。bindの実装をちょっとみただけですが引数からパラメーターをこしらえてtclを呼び出しているだけのように見えるので。

    キャンセル

  • 2018/11/11 00:19 編集

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

    キャンセル

  • 2018/11/11 00:39

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

    キャンセル

  • 2018/11/11 09:54

    > ネットを少し探してみたのですが自分ではちょっと力不足のようで

    そんなことないですよ、すごい助かってます。


    二つ目の問題に関してですが引き続き調べてみて何か分かったら追記します。色々と検証していただき本当にありがとうございました!

    キャンセル

  • 2018/11/11 12:09

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

    キャンセル

  • 2018/11/11 14:47

    いえこちらこそ追記したことを書き忘れてました、ごめんなさい。

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

    キャンセル

同じタグがついた質問を見る

  • Tkinter

    204questions

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