From 745a407df88f2c5baacb4e61d8e37e7ed5d4671f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 26 Jun 2016 17:42:23 +0300 Subject: [PATCH] Issue #22115: Fixed tracing Tkinter variables: * tracing in the "u" mode now works * trace_vdelete() with wrong mode no longer break tracing * trace_vinfo() now always returns a list of pairs of strings --- Lib/tkinter/__init__.py | 19 ++++--- .../test/test_tkinter/test_variables.py | 51 ++++++++++++++++++- Misc/NEWS | 4 ++ 3 files changed, 66 insertions(+), 8 deletions(-) diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index aa646dc8edc..55bfb7f4146 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -271,7 +271,7 @@ def trace_variable(self, mode, callback): Return the name of the callback. """ - f = CallWrapper(callback, None, self).__call__ + f = CallWrapper(callback, None, self._root).__call__ cbname = repr(id(f)) try: callback = callback.__func__ @@ -295,14 +295,19 @@ def trace_vdelete(self, mode, cbname): CBNAME is the name of the callback returned from trace_variable or trace. """ self._tk.call("trace", "vdelete", self._name, mode, cbname) - self._tk.deletecommand(cbname) - try: - self._tclCommands.remove(cbname) - except ValueError: - pass + cbname = self._tk.splitlist(cbname)[0] + for m, ca in self.trace_vinfo(): + if self._tk.splitlist(ca)[0] == cbname: + break + else: + self._tk.deletecommand(cbname) + try: + self._tclCommands.remove(cbname) + except ValueError: + pass def trace_vinfo(self): """Return all trace callback information.""" - return [self._tk.split(x) for x in self._tk.splitlist( + return [self._tk.splitlist(x) for x in self._tk.splitlist( self._tk.call("trace", "vinfo", self._name))] def __eq__(self, other): """Comparison for equality (==). diff --git a/Lib/tkinter/test/test_tkinter/test_variables.py b/Lib/tkinter/test/test_tkinter/test_variables.py index abdce969985..1f74453cea6 100644 --- a/Lib/tkinter/test/test_tkinter/test_variables.py +++ b/Lib/tkinter/test/test_tkinter/test_variables.py @@ -1,5 +1,5 @@ import unittest - +import gc from tkinter import (Variable, StringVar, IntVar, DoubleVar, BooleanVar, Tcl, TclError) @@ -87,6 +87,55 @@ def test_initialize(self): v.set("value") self.assertTrue(v.side_effect) + def test_trace(self): + v = Variable(self.root) + vname = str(v) + trace = [] + def read_tracer(*args): + trace.append(('read',) + args) + def write_tracer(*args): + trace.append(('write',) + args) + cb1 = v.trace_variable('r', read_tracer) + cb2 = v.trace_variable('wu', write_tracer) + self.assertEqual(sorted(v.trace_vinfo()), [('r', cb1), ('wu', cb2)]) + self.assertEqual(trace, []) + + v.set('spam') + self.assertEqual(trace, [('write', vname, '', 'w')]) + + trace = [] + v.get() + self.assertEqual(trace, [('read', vname, '', 'r')]) + + trace = [] + info = sorted(v.trace_vinfo()) + v.trace_vdelete('w', cb1) # Wrong mode + self.assertEqual(sorted(v.trace_vinfo()), info) + with self.assertRaises(TclError): + v.trace_vdelete('r', 'spam') # Wrong command name + self.assertEqual(sorted(v.trace_vinfo()), info) + v.trace_vdelete('r', (cb1, 43)) # Wrong arguments + self.assertEqual(sorted(v.trace_vinfo()), info) + v.get() + self.assertEqual(trace, [('read', vname, '', 'r')]) + + trace = [] + v.trace_vdelete('r', cb1) + self.assertEqual(v.trace_vinfo(), [('wu', cb2)]) + v.get() + self.assertEqual(trace, []) + + trace = [] + del write_tracer + gc.collect() + v.set('eggs') + self.assertEqual(trace, [('write', vname, '', 'w')]) + + trace = [] + del v + gc.collect() + self.assertEqual(trace, [('write', vname, '', 'u')]) + class TestStringVar(TestBase): diff --git a/Misc/NEWS b/Misc/NEWS index f580afe174e..cc53bcd7d80 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -13,6 +13,10 @@ Core and Builtins Library ------- +- Issue #22115: Fixed tracing Tkinter variables: trace_vdelete() with wrong + mode no longer break tracing, trace_vinfo() now always returns a list of + pairs of strings, tracing in the "u" mode now works. + - Fix a scoping issue in importlib.util.LazyLoader which triggered an UnboundLocalError when lazy-loading a module that was already put into sys.modules.