Title: Tkinter widget.unbind(sequence, funcid) unbind all bindings
Type: enhancement Stage: patch review
Components: Tkinter Versions: Python 3.10
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: glombardo, j-4321-i, serhiy.storchaka, terry.reedy
Priority: normal Keywords: patch

Created on 2017-09-15 13:57 by j-4321-i, last changed 2020-05-26 07:51 by terry.reedy.

Pull Requests
URL Status Linked Edit
PR 17954 closed python-dev, 2020-01-11 21:30
Messages (10)
msg302256 - (view) Author: Juliette Monsel (j-4321-i) * Date: 2017-09-15 13:57
I am using python 3.6.2 with tk 8.6.7 in Linux and when I call widget.unbind(<Sequence>, funcid), it unbinds all bindings for <Sequence>, while I would expect it to unbind only funcid. Here is an example reproducing the problem:

import tkinter as tk

root = tk.Tk()

def cmd1(event):

def cmd2(event):

def unbind1():
    l.unbind('<Motion>', b1)

def unbind2():
    l.unbind('<Motion>', b2)

l = tk.Label(root, text='Hover')

b1 = l.bind('<Motion>', cmd1)
b2 = l.bind('<Motion>', cmd2, True)

tk.Button(root, text='Unbind 1', command=unbind1).pack()
tk.Button(root, text='Unbind 2', command=unbind2).pack()


In this example, clicking on one of the buttons unbinds both bindings instead of only one.
msg302258 - (view) Author: Juliette Monsel (j-4321-i) * Date: 2017-09-15 14:37
I have found a workaround to unbind a single binding (inspired by

def unbind(widget, seq, funcid):
    bindings = {x.split()[1][3:]: x for x in widget.bind(seq).splitlines() if x.strip()}
        del bindings[funcid]
    except KeyError:
        raise tk.TclError('Binding "%s" not defined.' % funcid)
    widget.bind(seq, '\n'.join(list(bindings.values())))
msg302262 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-09-15 16:21
Tk do not provide a way of unbinding a specific command. It supports binding a script with replacing an existing binding, appending a script to an existing binding, and destroying an existing binding. Unbinding a specific command can be implemented in Python with a code similar to your example, but more complex due to taking to account different corner cases.

Do you want to write a patch and open a pull request Juliette?
msg302293 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2017-09-15 19:47
The unbind docstring, "Unbind for this widget for event SEQUENCE the
function identified with FUNCID" implies to me the behavior Juliette expected.  The NMT reference unpacks this to two sentences.  Reading the code'bind', self._w, sequence, '')
        if funcid:

the docstring should be something like "Unbind for this widget the event SEQUENCE.  If funcid is given, delete the command also"  This part of the bind docstring, "Bind will return an identifier to allow deletion of the bound function with unbind without memory leak." implies that the unbind behavior is deliberate.

Serhiy, are you proposing to add a new method that does what some people thought unbind would do?
msg302299 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-09-15 20:49
I don't think a new method is needed. We can make unbind() destroying all bindings if FUNCID is not given (note that registered commands are leaked in this case), and only the specified command if it is specified. We can add a boolean parameter (similar to the one in bind()) for restoring the old behavior if this is needed.
msg302487 - (view) Author: Juliette Monsel (j-4321-i) * Date: 2017-09-18 20:35
I don't mind writing a patch and opening a pull request, however I don't know which corner cases I need to take into account.
msg358227 - (view) Author: Giovanni Lombardo (glombardo) * Date: 2019-12-10 21:28
msg359369 - (view) Author: Giovanni Lombardo (glombardo) * Date: 2020-01-05 18:22
I propose the below fix:

    def unbind(self, sequence, funcid=None):
        """Unbind for this widget for event SEQUENCE  the
        function identified with FUNCID."""
        bound = ''
        if funcid:
            funcs ='bind', self._w, sequence, None).split('\n')
            bound = '\n'.join([f for f in funcs if not f.startswith('if {{"[{0}'.format(funcid))])'bind', self._w, sequence, bound)
msg360142 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2020-01-16 20:47
Serhiy, if I understand your last message (msg302299), the docstring should read:

       Unbind for this widget the event SEQUENCE.
       If FUNCID is given, only unbind the function identified with FUNCID
       and also delete that command.

The main doc change is to specify what happens when funcid is not given while the code change in the patch is to no longer unbind everything when funcid *is* given.
msg360145 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2020-01-16 21:37
Serhiy, given that the code did not match the docstring and that we are changing the code to match the clarified docstring, should we consider this a bugfix, and, should we backport it?
Date User Action Args
2020-05-26 07:51:27terry.reedysetversions: + Python 3.10, - Python 3.7
2020-01-16 21:37:07terry.reedysetmessages: + msg360145
2020-01-16 20:47:31terry.reedysetmessages: + msg360142
2020-01-11 21:30:44python-devsetkeywords: + patch
stage: patch review
pull_requests: + pull_request17364
2020-01-05 18:22:42glombardosetmessages: + msg359369
2019-12-10 21:28:55glombardosetnosy: + glombardo
messages: + msg358227
2017-09-18 20:35:37j-4321-isetmessages: + msg302487
2017-09-15 20:49:28serhiy.storchakasetmessages: + msg302299
2017-09-15 19:47:57terry.reedysetversions: + Python 3.7, - Python 3.6
nosy: + terry.reedy

messages: + msg302293

type: behavior -> enhancement
2017-09-15 16:21:41serhiy.storchakasetnosy: + serhiy.storchaka
messages: + msg302262
2017-09-15 14:37:29j-4321-isetmessages: + msg302258
2017-09-15 13:57:14j-4321-icreate