[Python-checkins] cpython: Issue #22115: Added methods trace_add, trace_remove and trace_info in the

serhiy.storchaka python-checkins at python.org
Sun Jun 26 02:47:20 EDT 2016


https://hg.python.org/cpython/rev/a201180c0f77
changeset:   102180:a201180c0f77
user:        Serhiy Storchaka <storchaka at gmail.com>
date:        Sun Jun 26 09:46:57 2016 +0300
summary:
  Issue #22115: Added methods trace_add, trace_remove and trace_info in the
tkinter.Variable class.  They replace old methods trace_variable, trace,
trace_vdelete and trace_vinfo that use obsolete Tcl commands and might
not work in future versions of Tcl.

files:
  Doc/whatsnew/3.6.rst                            |   13 +
  Lib/idlelib/configdialog.py                     |   38 +-
  Lib/tkinter/__init__.py                         |   99 ++++++++-
  Lib/tkinter/test/test_tkinter/test_variables.py |  101 +++++++++-
  Misc/NEWS                                       |    5 +
  5 files changed, 220 insertions(+), 36 deletions(-)


diff --git a/Doc/whatsnew/3.6.rst b/Doc/whatsnew/3.6.rst
--- a/Doc/whatsnew/3.6.rst
+++ b/Doc/whatsnew/3.6.rst
@@ -391,6 +391,19 @@
 Stéphane Wirtel in :issue:`25485`).
 
 
+tkinter
+-------
+
+Added methods :meth:`~tkinter.Variable.trace_add`,
+:meth:`~tkinter.Variable.trace_remove` and :meth:`~tkinter.Variable.trace_info`
+in the :class:`tkinter.Variable` class.  They replace old methods
+:meth:`~tkinter.Variable.trace_variable`, :meth:`~tkinter.Variable.trace`,
+:meth:`~tkinter.Variable.trace_vdelete` and
+:meth:`~tkinter.Variable.trace_vinfo` that use obsolete Tcl commands and might
+not work in future versions of Tcl.
+(Contributed by Serhiy Storchaka in :issue:`22115`).
+
+
 typing
 ------
 
diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py
--- a/Lib/idlelib/configdialog.py
+++ b/Lib/idlelib/configdialog.py
@@ -465,24 +465,24 @@
         return frame
 
     def AttachVarCallbacks(self):
-        self.fontSize.trace_variable('w', self.VarChanged_font)
-        self.fontName.trace_variable('w', self.VarChanged_font)
-        self.fontBold.trace_variable('w', self.VarChanged_font)
-        self.spaceNum.trace_variable('w', self.VarChanged_spaceNum)
-        self.colour.trace_variable('w', self.VarChanged_colour)
-        self.builtinTheme.trace_variable('w', self.VarChanged_builtinTheme)
-        self.customTheme.trace_variable('w', self.VarChanged_customTheme)
-        self.themeIsBuiltin.trace_variable('w', self.VarChanged_themeIsBuiltin)
-        self.highlightTarget.trace_variable('w', self.VarChanged_highlightTarget)
-        self.keyBinding.trace_variable('w', self.VarChanged_keyBinding)
-        self.builtinKeys.trace_variable('w', self.VarChanged_builtinKeys)
-        self.customKeys.trace_variable('w', self.VarChanged_customKeys)
-        self.keysAreBuiltin.trace_variable('w', self.VarChanged_keysAreBuiltin)
-        self.winWidth.trace_variable('w', self.VarChanged_winWidth)
-        self.winHeight.trace_variable('w', self.VarChanged_winHeight)
-        self.startupEdit.trace_variable('w', self.VarChanged_startupEdit)
-        self.autoSave.trace_variable('w', self.VarChanged_autoSave)
-        self.encoding.trace_variable('w', self.VarChanged_encoding)
+        self.fontSize.trace_add('write', self.VarChanged_font)
+        self.fontName.trace_add('write', self.VarChanged_font)
+        self.fontBold.trace_add('write', self.VarChanged_font)
+        self.spaceNum.trace_add('write', self.VarChanged_spaceNum)
+        self.colour.trace_add('write', self.VarChanged_colour)
+        self.builtinTheme.trace_add('write', self.VarChanged_builtinTheme)
+        self.customTheme.trace_add('write', self.VarChanged_customTheme)
+        self.themeIsBuiltin.trace_add('write', self.VarChanged_themeIsBuiltin)
+        self.highlightTarget.trace_add('write', self.VarChanged_highlightTarget)
+        self.keyBinding.trace_add('write', self.VarChanged_keyBinding)
+        self.builtinKeys.trace_add('write', self.VarChanged_builtinKeys)
+        self.customKeys.trace_add('write', self.VarChanged_customKeys)
+        self.keysAreBuiltin.trace_add('write', self.VarChanged_keysAreBuiltin)
+        self.winWidth.trace_add('write', self.VarChanged_winWidth)
+        self.winHeight.trace_add('write', self.VarChanged_winHeight)
+        self.startupEdit.trace_add('write', self.VarChanged_startupEdit)
+        self.autoSave.trace_add('write', self.VarChanged_autoSave)
+        self.encoding.trace_add('write', self.VarChanged_encoding)
 
     def remove_var_callbacks(self):
         "Remove callbacks to prevent memory leaks."
@@ -493,7 +493,7 @@
                 self.keyBinding, self.builtinKeys, self.customKeys,
                 self.keysAreBuiltin, self.winWidth, self.winHeight,
                 self.startupEdit, self.autoSave, self.encoding,):
-            var.trace_vdelete('w', var.trace_vinfo()[0][1])
+            var.trace_remove('write', var.trace_info()[0][1])
 
     def VarChanged_font(self, *params):
         '''When one font attribute changes, save them all, as they are
diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py
--- a/Lib/tkinter/__init__.py
+++ b/Lib/tkinter/__init__.py
@@ -343,16 +343,9 @@
     def get(self):
         """Return value of variable."""
         return self._tk.globalgetvar(self._name)
-    def trace_variable(self, mode, callback):
-        """Define a trace callback for the variable.
-
-        MODE is one of "r", "w", "u" for read, write, undefine.
-        CALLBACK must be a function which is called when
-        the variable is read, written or undefined.
-
-        Return the name of the callback.
-        """
-        f = CallWrapper(callback, None, self).__call__
+
+    def _register(self, callback):
+        f = CallWrapper(callback, None, self._root).__call__
         cbname = repr(id(f))
         try:
             callback = callback.__func__
@@ -366,25 +359,99 @@
         if self._tclCommands is None:
             self._tclCommands = []
         self._tclCommands.append(cbname)
+        return cbname
+
+    def trace_add(self, mode, callback):
+        """Define a trace callback for the variable.
+
+        Mode is one of "read", "write", "unset", or a list or tuple of
+        such strings.
+        Callback must be a function which is called when the variable is
+        read, written or unset.
+
+        Return the name of the callback.
+        """
+        cbname = self._register(callback)
+        self._tk.call('trace', 'add', 'variable',
+                      self._name, mode, (cbname,))
+        return cbname
+
+    def trace_remove(self, mode, cbname):
+        """Delete the trace callback for a variable.
+
+        Mode is one of "read", "write", "unset" or a list or tuple of
+        such strings.  Must be same as were specified in trace_add().
+        cbname is the name of the callback returned from trace_add().
+        """
+        self._tk.call('trace', 'remove', 'variable',
+                      self._name, mode, cbname)
+        for m, ca in self.trace_info():
+            if self._tk.splitlist(ca)[0] == cbname:
+                break
+        else:
+            self._tk.deletecommand(cbname)
+            try:
+                self._tclCommands.remove(cbname)
+            except ValueError:
+                pass
+
+    def trace_info(self):
+        """Return all trace callback information."""
+        splitlist = self._tk.splitlist
+        return [(splitlist(k), v) for k, v in map(splitlist,
+            splitlist(self._tk.call('trace', 'info', 'variable', self._name)))]
+
+    def trace_variable(self, mode, callback):
+        """Define a trace callback for the variable.
+
+        MODE is one of "r", "w", "u" for read, write, undefine.
+        CALLBACK must be a function which is called when
+        the variable is read, written or undefined.
+
+        Return the name of the callback.
+
+        This deprecated method wraps a deprecated Tcl method that will
+        likely be removed in the future.  Use trace_add() instead.
+        """
+        # TODO: Add deprecation warning
+        cbname = self._register(callback)
         self._tk.call("trace", "variable", self._name, mode, cbname)
         return cbname
+
     trace = trace_variable
+
     def trace_vdelete(self, mode, cbname):
         """Delete the trace callback for a variable.
 
         MODE is one of "r", "w", "u" for read, write, undefine.
         CBNAME is the name of the callback returned from trace_variable or trace.
+
+        This deprecated method wraps a deprecated Tcl method that will
+        likely be removed in the future.  Use trace_remove() instead.
         """
+        # TODO: Add deprecation warning
         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_info():
+            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 all trace callback information.
+
+        This deprecated method wraps a deprecated Tcl method that will
+        likely be removed in the future.  Use trace_info() instead.
+        """
+        # TODO: Add deprecation warning
         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
--- 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,105 @@
         v.set("value")
         self.assertTrue(v.side_effect)
 
+    def test_trace_old(self):
+        # Old interface
+        v = Var(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')])
+
+    def test_trace(self):
+        v = Var(self.root)
+        vname = str(v)
+        trace = []
+        def read_tracer(*args):
+            trace.append(('read',) + args)
+        def write_tracer(*args):
+            trace.append(('write',) + args)
+        tr1 = v.trace_add('read', read_tracer)
+        tr2 = v.trace_add(['write', 'unset'], write_tracer)
+        self.assertEqual(sorted(v.trace_info()), [
+                         (('read',), tr1),
+                         (('write', 'unset'), tr2)])
+        self.assertEqual(trace, [])
+
+        v.set('spam')
+        self.assertEqual(trace, [('write', vname, '', 'write')])
+
+        trace = []
+        v.get()
+        self.assertEqual(trace, [('read', vname, '', 'read')])
+
+        trace = []
+        info = sorted(v.trace_info())
+        v.trace_remove('write', tr1)  # Wrong mode
+        self.assertEqual(sorted(v.trace_info()), info)
+        with self.assertRaises(TclError):
+            v.trace_remove('read', 'spam')  # Wrong command name
+        self.assertEqual(sorted(v.trace_info()), info)
+        v.get()
+        self.assertEqual(trace, [('read', vname, '', 'read')])
+
+        trace = []
+        v.trace_remove('read', tr1)
+        self.assertEqual(v.trace_info(), [(('write', 'unset'), tr2)])
+        v.get()
+        self.assertEqual(trace, [])
+
+        trace = []
+        del write_tracer
+        gc.collect()
+        v.set('eggs')
+        self.assertEqual(trace, [('write', vname, '', 'write')])
+
+        trace = []
+        del v
+        gc.collect()
+        self.assertEqual(trace, [('write', vname, '', 'unset')])
+
 
 class TestStringVar(TestBase):
 
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,11 @@
 Library
 -------
 
+- Issue #22115: Added methods trace_add, trace_remove and trace_info in the
+  tkinter.Variable class.  They replace old methods trace_variable, trace,
+  trace_vdelete and trace_vinfo that use obsolete Tcl commands and might
+  not work in future versions of Tcl.
+
 - Issue #26243: Only the level argument to zlib.compress() is keyword argument
   now.  The first argument is positional-only.
 

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list