Serhiy Storchaka added the comment:

> "list or tuple" (of such strings.)  Can we say 'sequence' (or even set or
> iterable) or is the requirement specifically tuple or list?

No. The call() method accepted only tuples, and since issue21525 it now 
supports lists.

> Does tk call the traceback function with any args it supplies(other than
> those passed in)? (In other words, is the callback affected by the #22214
> proposal?)

Tcl calls the traceback function with stringified arguments. No, this is not 
affected by the #22214 proposal. And only types which are supported by Tkinter 
(int, bool, float, str, bytes, tuple, list, Tcl_Obj) are allowed. But this 
behavior can be changed in future versions of Tcl..

> "Should be same as were specified in trace_add()." Is a subset not allowed
> to remove a subset of the registrations currently allowed?  Does trying to
> delete other modes raise TclError?  If both are so, I would say something
> like "Must be a subset of current trace modes.".

Unfortunately both are no. If you pass wrong mode, trace_remove() will 
silently finished, but next attempt to call a callback will raise TclError 
(because a command is already deleted). Fixed in next patch and should be 
backported.

> That aside, remembering and passing the
> arguments back seems like a bizarre requirement that makes not sense. What
> happens if one does not, or gets it wrong?

Same bad things as when pass wrong mode.

> It the args are really required
> exactly, then is seems that trace_add should return (cbname,) + args to be
> passed back to trace_remove as cbname_args.

Looks good.

> Alternatively, could *args (and mode) be retrieved from trace_info?

You can retrieve mode, cbname and (stringified) args from trace_info. Currently 
following code removes all callbacks from variable:

for mode, cbname_args in v.trace_info():
    v.trace_remove(mode, *cbname_args)

With next patch '*' is not needed.

Thank you for the review. Updated patch addresses all your comments.

----------
Added file: http://bugs.python.org/file36404/tkinter_trace_variable_4.patch

_______________________________________
Python tracker <rep...@bugs.python.org>
<http://bugs.python.org/issue22115>
_______________________________________
diff -r 66c7f30fe8c7 Lib/tkinter/__init__.py
--- a/Lib/tkinter/__init__.py   Sun Aug 17 23:01:33 2014 -0500
+++ b/Lib/tkinter/__init__.py   Mon Aug 18 15:37:33 2014 +0300
@@ -242,16 +242,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__
@@ -265,6 +258,65 @@
         if self._tclCommands is None:
             self._tclCommands = []
         self._tclCommands.append(cbname)
+        return cbname
+
+    def trace_add(self, mode, callback, *args):
+        """Define a trace callback for the variable.
+
+        Mode is one of "array", "read", "write", "unset", or a list or tuple
+        of such strings.
+        Callback must be a function which is called when the variable is
+        accessed or modified via the array command, read, written or unset.
+        Additional arguments are passed to the traceback call.
+
+        Return a tuple containg the name of the callback and additional
+        arguments.
+        """
+        cbname = self._register(callback)
+        self._tk.call('trace', 'add', 'variable',
+                      self._name, mode, (cbname,) + args)
+        return (cbname,) + args
+
+    def trace_remove(self, mode, cbname_args):
+        """Delete the trace callback for a variable.
+
+        Mode is one of "array", "read", "write", "unset" or a list or tuple
+        of such strings.  Must be same as were specified in trace_add().
+        Cbname_args must be a tuple returned from trace_add().
+        """
+        self._tk.call('trace', 'remove', 'variable',
+                      self._name, mode, cbname_args)
+        cbname = cbname_args[0]
+        for m, ca in self.trace_info():
+            if 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), splitlist(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
@@ -273,15 +325,29 @@
 
         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.split(x) for x in self._tk.splitlist(
             self._tk.call("trace", "vinfo", self._name))]
     def __eq__(self, other):
diff -r 66c7f30fe8c7 Lib/tkinter/test/test_tkinter/test_variables.py
--- a/Lib/tkinter/test/test_tkinter/test_variables.py   Sun Aug 17 23:01:33 
2014 -0500
+++ b/Lib/tkinter/test/test_tkinter/test_variables.py   Mon Aug 18 15:37:33 
2014 +0300
@@ -1,6 +1,6 @@
 import unittest
-
-from tkinter import Variable, StringVar, IntVar, DoubleVar, BooleanVar, Tk
+import gc
+from tkinter import Variable, StringVar, IntVar, DoubleVar, BooleanVar, Tk, 
TclError
 
 
 class Var(Variable):
@@ -86,6 +86,106 @@
         v.set("value")
         self.assertTrue(v.side_effect)
 
+    def test_trace_old(self):
+        v = Var()
+        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()
+        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, 42)
+        tr2 = v.trace_add(['write', 'unset'], write_tracer)
+        self.assertEqual(sorted(v.trace_info()), [
+                         (('read',), (tr1[0], '42')),
+                         (('write', 'unset'), tr2)])
+        self.assertEqual(trace, [])
+
+        v.set('spam')
+        self.assertEqual(trace, [('write', vname, '', 'write')])
+
+        trace = []
+        v.get()
+        self.assertEqual(trace, [('read', '42', 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.trace_remove('read', (tr1[0], 43)) # Wrong arguments
+        self.assertEqual(sorted(v.trace_info()), info)
+        v.get()
+        self.assertEqual(trace, [('read', '42', 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):
 
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to