Skip to content

Tkinter widget.unbind(sequence, funcid) unbind all bindings #75666

Description

@j4321
mannequin
BPO 31485
Nosy @terryjreedy, @serhiy-storchaka, @j4321, @GiovaLomba
PRs
  • gh-75666: Fix Misc.unbind behaviour when funcid is given #17954
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = None
    created_at = <Date 2017-09-15.13:57:14.322>
    labels = ['type-feature', 'expert-tkinter', '3.10']
    title = 'Tkinter widget.unbind(sequence, funcid) unbind all bindings'
    updated_at = <Date 2020-05-26.07:51:27.964>
    user = 'https://github.com/j4321'

    bugs.python.org fields:

    activity = <Date 2020-05-26.07:51:27.964>
    actor = 'terry.reedy'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = ['Tkinter']
    creation = <Date 2017-09-15.13:57:14.322>
    creator = 'j-4321-i'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 31485
    keywords = ['patch']
    message_count = 10.0
    messages = ['302256', '302258', '302262', '302293', '302299', '302487', '358227', '359369', '360142', '360145']
    nosy_count = 4.0
    nosy_names = ['terry.reedy', 'serhiy.storchaka', 'j-4321-i', 'glombardo']
    pr_nums = ['17954']
    priority = 'normal'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = 'enhancement'
    url = 'https://bugs.python.org/issue31485'
    versions = ['Python 3.10']

    Linked PRs

    Activity

    1. j4321 commented on Sep 15, 2017

      j4321mannequin
      MannequinAuthor

      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):
          print('1')
      
      def cmd2(event):
          print('2')
      
      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)
      
      l.pack()
      tk.Button(root, text='Unbind 1', command=unbind1).pack()
      tk.Button(root, text='Unbind 2', command=unbind2).pack()
      
      root.mainloop()

      In this example, clicking on one of the buttons unbinds both bindings instead of only one.

    2. j4321 commented on Sep 15, 2017

      j4321mannequin
      MannequinAuthor

      I have found a workaround to unbind a single binding (inspired by https://mail.python.org/pipermail/tkinter-discuss/2012-May/003151.html):

      def unbind(widget, seq, funcid):
          bindings = {x.split()[1][3:]: x for x in widget.bind(seq).splitlines() if x.strip()}
          try:
              del bindings[funcid]
          except KeyError:
              raise tk.TclError('Binding "%s" not defined.' % funcid)
          widget.bind(seq, '\n'.join(list(bindings.values())))
    3. serhiy-storchaka commented on Sep 15, 2017

      @serhiy-storchaka
      Member

      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?

    4. terryjreedy commented on Sep 15, 2017

      @terryjreedy
      Member

      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
      self.tk.call('bind', self._w, sequence, '')
      if funcid:
      self.deletecommand(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?

    5. added
      type-featureA feature request or enhancement
      and removed
      type-bugAn unexpected behavior, bug, or error
      on Sep 15, 2017
    6. serhiy-storchaka commented on Sep 15, 2017

      @serhiy-storchaka
      Member

      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.

    7. j4321 commented on Sep 18, 2017

      j4321mannequin
      MannequinAuthor

      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.

    8. GiovaLomba commented on Dec 10, 2019

      GiovaLombamannequin
      Mannequin
    9. GiovaLomba commented on Jan 5, 2020

      GiovaLombamannequin
      Mannequin

      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:
                  self.deletecommand(funcid)
                  funcs = self.tk.call('bind', self._w, sequence, None).split('\n')
                  bound = '\n'.join([f for f in funcs if not f.startswith('if {{"[{0}'.format(funcid))])
              self.tk.call('bind', self._w, sequence, bound)
      
    10. terryjreedy commented on Jan 16, 2020

      @terryjreedy
      Member

      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.

    11. terryjreedy commented on Jan 16, 2020

      @terryjreedy
      Member

      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?

    12. 29 remaining items

    13. hugovk commented on Jan 11, 2024

      @hugovk
    14. serhiy-storchaka commented on Jan 11, 2024

      @serhiy-storchaka
    15. added 2 commits that reference this issue on Feb 11, 2024
    16. added 2 commits that reference this issue on Sep 2, 2024
    17. serhiy-storchaka commented on Jun 20, 2026

      @serhiy-storchaka
      Member

      Opened #151808.

      I wrote it almost 3 years ago, but there was a little issue with IDLE, so I put it aside and then forgot about it. Turned out, the bug was in IDLE tests. It was hidden by that bug in unbind() and was exposed when it was fixed. The change does not affect IDLE, and I hope it will not break much user code.

    18. added a commit that references this issue on Jun 23, 2026
    19. serhiy-storchaka commented on Jun 23, 2026

      @serhiy-storchaka
      Member

      Still, it is safer to only backport this fix to 3.15. The leak was always.

    20. added a commit that references this issue on Jun 23, 2026
    21. added 2 commits that reference this issue on Jul 4, 2026
    22. added a commit that references this issue on Jul 5, 2026
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions