mirror of https://github.com/python/cpython
gh-66410: Do not stringify arguments of Tkinter callback (GH-98592)
Callbacks registered in the tkinter module now take arguments as various Python objects (int, float, bytes, tuple), not just str. To restore the previous behavior set tkinter module global wantobject to 1 before creating the Tk object or call the wantobject() method of the Tk object with argument 1. Calling it with argument 2 restores the current default behavior.
This commit is contained in:
parent
b60d4c0d53
commit
65f5e586a1
|
@ -1859,6 +1859,16 @@ Changes in the Python API
|
|||
to :c:func:`PyUnstable_Code_GetFirstFree`.
|
||||
(Contributed by Bogdan Romanyuk in :gh:`115781`.)
|
||||
|
||||
* Callbacks registered in the :mod:`tkinter` module now take arguments as
|
||||
various Python objects (``int``, ``float``, ``bytes``, ``tuple``),
|
||||
not just ``str``.
|
||||
To restore the previous behavior set :mod:`!tkinter` module global
|
||||
:data:`!wantobject` to ``1`` before creating the
|
||||
:class:`!Tk` object or call the :meth:`!wantobject`
|
||||
method of the :class:`!Tk` object with argument ``1``.
|
||||
Calling it with argument ``2`` restores the current default behavior.
|
||||
(Contributed by Serhiy Storchaka in :gh:`66410`.)
|
||||
|
||||
|
||||
Build Changes
|
||||
=============
|
||||
|
|
|
@ -106,6 +106,7 @@ class WidgetRedirector:
|
|||
to *args to accomplish that. For an example, see colorizer.py.
|
||||
|
||||
'''
|
||||
operation = str(operation) # can be a Tcl_Obj
|
||||
m = self._operations.get(operation)
|
||||
try:
|
||||
if m:
|
||||
|
|
|
@ -482,29 +482,36 @@ class TclTest(unittest.TestCase):
|
|||
return arg
|
||||
self.interp.createcommand('testfunc', testfunc)
|
||||
self.addCleanup(self.interp.tk.deletecommand, 'testfunc')
|
||||
def check(value, expected=None, *, eq=self.assertEqual):
|
||||
if expected is None:
|
||||
expected = value
|
||||
def check(value, expected1=None, expected2=None, *, eq=self.assertEqual):
|
||||
expected = value
|
||||
if self.wantobjects >= 2:
|
||||
if expected2 is not None:
|
||||
expected = expected2
|
||||
expected_type = type(expected)
|
||||
else:
|
||||
if expected1 is not None:
|
||||
expected = expected1
|
||||
expected_type = str
|
||||
nonlocal result
|
||||
result = None
|
||||
r = self.interp.call('testfunc', value)
|
||||
self.assertIsInstance(result, str)
|
||||
self.assertIsInstance(result, expected_type)
|
||||
eq(result, expected)
|
||||
self.assertIsInstance(r, str)
|
||||
self.assertIsInstance(r, expected_type)
|
||||
eq(r, expected)
|
||||
def float_eq(actual, expected):
|
||||
self.assertAlmostEqual(float(actual), expected,
|
||||
delta=abs(expected) * 1e-10)
|
||||
|
||||
check(True, '1')
|
||||
check(False, '0')
|
||||
check(True, '1', 1)
|
||||
check(False, '0', 0)
|
||||
check('string')
|
||||
check('string\xbd')
|
||||
check('string\u20ac')
|
||||
check('string\U0001f4bb')
|
||||
if sys.platform != 'win32':
|
||||
check('<\udce2\udc82\udcac>', '<\u20ac>')
|
||||
check('<\udced\udca0\udcbd\udced\udcb2\udcbb>', '<\U0001f4bb>')
|
||||
check('<\udce2\udc82\udcac>', '<\u20ac>', '<\u20ac>')
|
||||
check('<\udced\udca0\udcbd\udced\udcb2\udcbb>', '<\U0001f4bb>', '<\U0001f4bb>')
|
||||
check('')
|
||||
check(b'string', 'string')
|
||||
check(b'string\xe2\x82\xac', 'string\xe2\x82\xac')
|
||||
|
@ -526,9 +533,13 @@ class TclTest(unittest.TestCase):
|
|||
check(float('inf'), eq=float_eq)
|
||||
check(-float('inf'), eq=float_eq)
|
||||
# XXX NaN representation can be not parsable by float()
|
||||
check((), '')
|
||||
check((1, (2,), (3, 4), '5 6', ()), '1 2 {3 4} {5 6} {}')
|
||||
check([1, [2,], [3, 4], '5 6', []], '1 2 {3 4} {5 6} {}')
|
||||
check((), '', '')
|
||||
check((1, (2,), (3, 4), '5 6', ()),
|
||||
'1 2 {3 4} {5 6} {}',
|
||||
(1, (2,), (3, 4), '5 6', ''))
|
||||
check([1, [2,], [3, 4], '5 6', []],
|
||||
'1 2 {3 4} {5 6} {}',
|
||||
(1, (2,), (3, 4), '5 6', ''))
|
||||
|
||||
def test_splitlist(self):
|
||||
splitlist = self.interp.tk.splitlist
|
||||
|
|
|
@ -40,7 +40,7 @@ TclError = _tkinter.TclError
|
|||
from tkinter.constants import *
|
||||
import re
|
||||
|
||||
wantobjects = 1
|
||||
wantobjects = 2
|
||||
_debug = False # set to True to print executed Tcl/Tk commands
|
||||
|
||||
TkVersion = float(_tkinter.TK_VERSION)
|
||||
|
@ -1762,7 +1762,10 @@ class Misc:
|
|||
try:
|
||||
e.type = EventType(T)
|
||||
except ValueError:
|
||||
e.type = T
|
||||
try:
|
||||
e.type = EventType(str(T)) # can be int
|
||||
except ValueError:
|
||||
e.type = T
|
||||
try:
|
||||
e.widget = self._nametowidget(W)
|
||||
except KeyError:
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
Callbacks registered in the :mod:`tkinter` module now take arguments as
|
||||
various Python objects (``int``, ``float``, ``bytes``, ``tuple``), not just
|
||||
``str``. To restore the previous behavior set :mod:`!tkinter` module global
|
||||
:data:`~tkinter.wantobject` to ``1`` before creating the
|
||||
:class:`~tkinter.Tk` object or call the :meth:`~tkinter.Tk.wantobject`
|
||||
method of the :class:`!Tk` object with argument ``1``. Calling it with
|
||||
argument ``2`` restores the current default behavior.
|
|
@ -2248,7 +2248,7 @@ _tkinter_tkapp_splitlist(TkappObject *self, PyObject *arg)
|
|||
|
||||
/* Client data struct */
|
||||
typedef struct {
|
||||
PyObject *self;
|
||||
TkappObject *self;
|
||||
PyObject *func;
|
||||
} PythonCmd_ClientData;
|
||||
|
||||
|
@ -2272,6 +2272,7 @@ PythonCmd(ClientData clientData, Tcl_Interp *interp,
|
|||
PyObject *args, *res;
|
||||
int i;
|
||||
Tcl_Obj *obj_res;
|
||||
int objargs = data->self->wantobjects >= 2;
|
||||
|
||||
ENTER_PYTHON
|
||||
|
||||
|
@ -2280,7 +2281,8 @@ PythonCmd(ClientData clientData, Tcl_Interp *interp,
|
|||
return PythonCmd_Error(interp);
|
||||
|
||||
for (i = 0; i < (objc - 1); i++) {
|
||||
PyObject *s = unicodeFromTclObj(objv[i + 1]);
|
||||
PyObject *s = objargs ? FromObj(data->self, objv[i + 1])
|
||||
: unicodeFromTclObj(objv[i + 1]);
|
||||
if (!s) {
|
||||
Py_DECREF(args);
|
||||
return PythonCmd_Error(interp);
|
||||
|
@ -2383,7 +2385,8 @@ _tkinter_tkapp_createcommand_impl(TkappObject *self, const char *name,
|
|||
data = PyMem_NEW(PythonCmd_ClientData, 1);
|
||||
if (!data)
|
||||
return PyErr_NoMemory();
|
||||
data->self = Py_NewRef(self);
|
||||
Py_INCREF(self);
|
||||
data->self = self;
|
||||
data->func = Py_NewRef(func);
|
||||
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
|
||||
Tcl_Condition cond = NULL;
|
||||
|
@ -2897,10 +2900,10 @@ Tkapp_WantObjects(PyObject *self, PyObject *args)
|
|||
{
|
||||
|
||||
int wantobjects = -1;
|
||||
if (!PyArg_ParseTuple(args, "|p:wantobjects", &wantobjects))
|
||||
if (!PyArg_ParseTuple(args, "|i:wantobjects", &wantobjects))
|
||||
return NULL;
|
||||
if (wantobjects == -1)
|
||||
return PyBool_FromLong(((TkappObject*)self)->wantobjects);
|
||||
return PyLong_FromLong(((TkappObject*)self)->wantobjects);
|
||||
((TkappObject*)self)->wantobjects = wantobjects;
|
||||
|
||||
Py_RETURN_NONE;
|
||||
|
@ -3086,7 +3089,7 @@ _tkinter.create
|
|||
baseName: str = ""
|
||||
className: str = "Tk"
|
||||
interactive: bool = False
|
||||
wantobjects: bool = False
|
||||
wantobjects: int = 0
|
||||
wantTk: bool = True
|
||||
if false, then Tk_Init() doesn't get called
|
||||
sync: bool = False
|
||||
|
@ -3102,7 +3105,7 @@ _tkinter_create_impl(PyObject *module, const char *screenName,
|
|||
const char *baseName, const char *className,
|
||||
int interactive, int wantobjects, int wantTk, int sync,
|
||||
const char *use)
|
||||
/*[clinic end generated code: output=e3315607648e6bb4 input=09afef9adea70a19]*/
|
||||
/*[clinic end generated code: output=e3315607648e6bb4 input=7e382ba431bed537]*/
|
||||
{
|
||||
/* XXX baseName is not used anymore;
|
||||
* try getting rid of it. */
|
||||
|
|
|
@ -676,7 +676,7 @@ PyDoc_STRVAR(_tkinter__flatten__doc__,
|
|||
|
||||
PyDoc_STRVAR(_tkinter_create__doc__,
|
||||
"create($module, screenName=None, baseName=\'\', className=\'Tk\',\n"
|
||||
" interactive=False, wantobjects=False, wantTk=True, sync=False,\n"
|
||||
" interactive=False, wantobjects=0, wantTk=True, sync=False,\n"
|
||||
" use=None, /)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
|
@ -777,8 +777,8 @@ _tkinter_create(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
|
|||
if (nargs < 5) {
|
||||
goto skip_optional;
|
||||
}
|
||||
wantobjects = PyObject_IsTrue(args[4]);
|
||||
if (wantobjects < 0) {
|
||||
wantobjects = PyLong_AsInt(args[4]);
|
||||
if (wantobjects == -1 && PyErr_Occurred()) {
|
||||
goto exit;
|
||||
}
|
||||
if (nargs < 6) {
|
||||
|
@ -888,4 +888,4 @@ exit:
|
|||
#ifndef _TKINTER_TKAPP_DELETEFILEHANDLER_METHODDEF
|
||||
#define _TKINTER_TKAPP_DELETEFILEHANDLER_METHODDEF
|
||||
#endif /* !defined(_TKINTER_TKAPP_DELETEFILEHANDLER_METHODDEF) */
|
||||
/*[clinic end generated code: output=86a515890d48a2ce input=a9049054013a1b77]*/
|
||||
/*[clinic end generated code: output=d90c1a9850c63249 input=a9049054013a1b77]*/
|
||||
|
|
Loading…
Reference in New Issue