Skip to content

Commit 65f5e58

Browse files
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.
1 parent b60d4c0 commit 65f5e58

File tree

7 files changed

+60
-25
lines changed

7 files changed

+60
-25
lines changed

Doc/whatsnew/3.13.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1859,6 +1859,16 @@ Changes in the Python API
18591859
to :c:func:`PyUnstable_Code_GetFirstFree`.
18601860
(Contributed by Bogdan Romanyuk in :gh:`115781`.)
18611861

1862+
* Callbacks registered in the :mod:`tkinter` module now take arguments as
1863+
various Python objects (``int``, ``float``, ``bytes``, ``tuple``),
1864+
not just ``str``.
1865+
To restore the previous behavior set :mod:`!tkinter` module global
1866+
:data:`!wantobject` to ``1`` before creating the
1867+
:class:`!Tk` object or call the :meth:`!wantobject`
1868+
method of the :class:`!Tk` object with argument ``1``.
1869+
Calling it with argument ``2`` restores the current default behavior.
1870+
(Contributed by Serhiy Storchaka in :gh:`66410`.)
1871+
18621872

18631873
Build Changes
18641874
=============

Lib/idlelib/redirector.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ def dispatch(self, operation, *args):
106106
to *args to accomplish that. For an example, see colorizer.py.
107107
108108
'''
109+
operation = str(operation) # can be a Tcl_Obj
109110
m = self._operations.get(operation)
110111
try:
111112
if m:

Lib/test/test_tcl.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -482,29 +482,36 @@ def testfunc(arg):
482482
return arg
483483
self.interp.createcommand('testfunc', testfunc)
484484
self.addCleanup(self.interp.tk.deletecommand, 'testfunc')
485-
def check(value, expected=None, *, eq=self.assertEqual):
486-
if expected is None:
487-
expected = value
485+
def check(value, expected1=None, expected2=None, *, eq=self.assertEqual):
486+
expected = value
487+
if self.wantobjects >= 2:
488+
if expected2 is not None:
489+
expected = expected2
490+
expected_type = type(expected)
491+
else:
492+
if expected1 is not None:
493+
expected = expected1
494+
expected_type = str
488495
nonlocal result
489496
result = None
490497
r = self.interp.call('testfunc', value)
491-
self.assertIsInstance(result, str)
498+
self.assertIsInstance(result, expected_type)
492499
eq(result, expected)
493-
self.assertIsInstance(r, str)
500+
self.assertIsInstance(r, expected_type)
494501
eq(r, expected)
495502
def float_eq(actual, expected):
496503
self.assertAlmostEqual(float(actual), expected,
497504
delta=abs(expected) * 1e-10)
498505

499-
check(True, '1')
500-
check(False, '0')
506+
check(True, '1', 1)
507+
check(False, '0', 0)
501508
check('string')
502509
check('string\xbd')
503510
check('string\u20ac')
504511
check('string\U0001f4bb')
505512
if sys.platform != 'win32':
506-
check('<\udce2\udc82\udcac>', '<\u20ac>')
507-
check('<\udced\udca0\udcbd\udced\udcb2\udcbb>', '<\U0001f4bb>')
513+
check('<\udce2\udc82\udcac>', '<\u20ac>', '<\u20ac>')
514+
check('<\udced\udca0\udcbd\udced\udcb2\udcbb>', '<\U0001f4bb>', '<\U0001f4bb>')
508515
check('')
509516
check(b'string', 'string')
510517
check(b'string\xe2\x82\xac', 'string\xe2\x82\xac')
@@ -526,9 +533,13 @@ def float_eq(actual, expected):
526533
check(float('inf'), eq=float_eq)
527534
check(-float('inf'), eq=float_eq)
528535
# XXX NaN representation can be not parsable by float()
529-
check((), '')
530-
check((1, (2,), (3, 4), '5 6', ()), '1 2 {3 4} {5 6} {}')
531-
check([1, [2,], [3, 4], '5 6', []], '1 2 {3 4} {5 6} {}')
536+
check((), '', '')
537+
check((1, (2,), (3, 4), '5 6', ()),
538+
'1 2 {3 4} {5 6} {}',
539+
(1, (2,), (3, 4), '5 6', ''))
540+
check([1, [2,], [3, 4], '5 6', []],
541+
'1 2 {3 4} {5 6} {}',
542+
(1, (2,), (3, 4), '5 6', ''))
532543

533544
def test_splitlist(self):
534545
splitlist = self.interp.tk.splitlist

Lib/tkinter/__init__.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
from tkinter.constants import *
4141
import re
4242

43-
wantobjects = 1
43+
wantobjects = 2
4444
_debug = False # set to True to print executed Tcl/Tk commands
4545

4646
TkVersion = float(_tkinter.TK_VERSION)
@@ -1762,7 +1762,10 @@ def getint_event(s):
17621762
try:
17631763
e.type = EventType(T)
17641764
except ValueError:
1765-
e.type = T
1765+
try:
1766+
e.type = EventType(str(T)) # can be int
1767+
except ValueError:
1768+
e.type = T
17661769
try:
17671770
e.widget = self._nametowidget(W)
17681771
except KeyError:
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Callbacks registered in the :mod:`tkinter` module now take arguments as
2+
various Python objects (``int``, ``float``, ``bytes``, ``tuple``), not just
3+
``str``. To restore the previous behavior set :mod:`!tkinter` module global
4+
:data:`~tkinter.wantobject` to ``1`` before creating the
5+
:class:`~tkinter.Tk` object or call the :meth:`~tkinter.Tk.wantobject`
6+
method of the :class:`!Tk` object with argument ``1``. Calling it with
7+
argument ``2`` restores the current default behavior.

Modules/_tkinter.c

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2248,7 +2248,7 @@ _tkinter_tkapp_splitlist(TkappObject *self, PyObject *arg)
22482248

22492249
/* Client data struct */
22502250
typedef struct {
2251-
PyObject *self;
2251+
TkappObject *self;
22522252
PyObject *func;
22532253
} PythonCmd_ClientData;
22542254

@@ -2272,6 +2272,7 @@ PythonCmd(ClientData clientData, Tcl_Interp *interp,
22722272
PyObject *args, *res;
22732273
int i;
22742274
Tcl_Obj *obj_res;
2275+
int objargs = data->self->wantobjects >= 2;
22752276

22762277
ENTER_PYTHON
22772278

@@ -2280,7 +2281,8 @@ PythonCmd(ClientData clientData, Tcl_Interp *interp,
22802281
return PythonCmd_Error(interp);
22812282

22822283
for (i = 0; i < (objc - 1); i++) {
2283-
PyObject *s = unicodeFromTclObj(objv[i + 1]);
2284+
PyObject *s = objargs ? FromObj(data->self, objv[i + 1])
2285+
: unicodeFromTclObj(objv[i + 1]);
22842286
if (!s) {
22852287
Py_DECREF(args);
22862288
return PythonCmd_Error(interp);
@@ -2383,7 +2385,8 @@ _tkinter_tkapp_createcommand_impl(TkappObject *self, const char *name,
23832385
data = PyMem_NEW(PythonCmd_ClientData, 1);
23842386
if (!data)
23852387
return PyErr_NoMemory();
2386-
data->self = Py_NewRef(self);
2388+
Py_INCREF(self);
2389+
data->self = self;
23872390
data->func = Py_NewRef(func);
23882391
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
23892392
Tcl_Condition cond = NULL;
@@ -2897,10 +2900,10 @@ Tkapp_WantObjects(PyObject *self, PyObject *args)
28972900
{
28982901

28992902
int wantobjects = -1;
2900-
if (!PyArg_ParseTuple(args, "|p:wantobjects", &wantobjects))
2903+
if (!PyArg_ParseTuple(args, "|i:wantobjects", &wantobjects))
29012904
return NULL;
29022905
if (wantobjects == -1)
2903-
return PyBool_FromLong(((TkappObject*)self)->wantobjects);
2906+
return PyLong_FromLong(((TkappObject*)self)->wantobjects);
29042907
((TkappObject*)self)->wantobjects = wantobjects;
29052908

29062909
Py_RETURN_NONE;
@@ -3086,7 +3089,7 @@ _tkinter.create
30863089
baseName: str = ""
30873090
className: str = "Tk"
30883091
interactive: bool = False
3089-
wantobjects: bool = False
3092+
wantobjects: int = 0
30903093
wantTk: bool = True
30913094
if false, then Tk_Init() doesn't get called
30923095
sync: bool = False
@@ -3102,7 +3105,7 @@ _tkinter_create_impl(PyObject *module, const char *screenName,
31023105
const char *baseName, const char *className,
31033106
int interactive, int wantobjects, int wantTk, int sync,
31043107
const char *use)
3105-
/*[clinic end generated code: output=e3315607648e6bb4 input=09afef9adea70a19]*/
3108+
/*[clinic end generated code: output=e3315607648e6bb4 input=7e382ba431bed537]*/
31063109
{
31073110
/* XXX baseName is not used anymore;
31083111
* try getting rid of it. */

Modules/clinic/_tkinter.c.h

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)