mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
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
7 changed files with 60 additions and 25 deletions
|
@ -1859,6 +1859,16 @@ Changes in the Python API
|
||||||
to :c:func:`PyUnstable_Code_GetFirstFree`.
|
to :c:func:`PyUnstable_Code_GetFirstFree`.
|
||||||
(Contributed by Bogdan Romanyuk in :gh:`115781`.)
|
(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
|
Build Changes
|
||||||
=============
|
=============
|
||||||
|
|
|
@ -106,6 +106,7 @@ class WidgetRedirector:
|
||||||
to *args to accomplish that. For an example, see colorizer.py.
|
to *args to accomplish that. For an example, see colorizer.py.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
operation = str(operation) # can be a Tcl_Obj
|
||||||
m = self._operations.get(operation)
|
m = self._operations.get(operation)
|
||||||
try:
|
try:
|
||||||
if m:
|
if m:
|
||||||
|
|
|
@ -482,29 +482,36 @@ class TclTest(unittest.TestCase):
|
||||||
return arg
|
return arg
|
||||||
self.interp.createcommand('testfunc', testfunc)
|
self.interp.createcommand('testfunc', testfunc)
|
||||||
self.addCleanup(self.interp.tk.deletecommand, 'testfunc')
|
self.addCleanup(self.interp.tk.deletecommand, 'testfunc')
|
||||||
def check(value, expected=None, *, eq=self.assertEqual):
|
def check(value, expected1=None, expected2=None, *, eq=self.assertEqual):
|
||||||
if expected is None:
|
expected = value
|
||||||
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
|
nonlocal result
|
||||||
result = None
|
result = None
|
||||||
r = self.interp.call('testfunc', value)
|
r = self.interp.call('testfunc', value)
|
||||||
self.assertIsInstance(result, str)
|
self.assertIsInstance(result, expected_type)
|
||||||
eq(result, expected)
|
eq(result, expected)
|
||||||
self.assertIsInstance(r, str)
|
self.assertIsInstance(r, expected_type)
|
||||||
eq(r, expected)
|
eq(r, expected)
|
||||||
def float_eq(actual, expected):
|
def float_eq(actual, expected):
|
||||||
self.assertAlmostEqual(float(actual), expected,
|
self.assertAlmostEqual(float(actual), expected,
|
||||||
delta=abs(expected) * 1e-10)
|
delta=abs(expected) * 1e-10)
|
||||||
|
|
||||||
check(True, '1')
|
check(True, '1', 1)
|
||||||
check(False, '0')
|
check(False, '0', 0)
|
||||||
check('string')
|
check('string')
|
||||||
check('string\xbd')
|
check('string\xbd')
|
||||||
check('string\u20ac')
|
check('string\u20ac')
|
||||||
check('string\U0001f4bb')
|
check('string\U0001f4bb')
|
||||||
if sys.platform != 'win32':
|
if sys.platform != 'win32':
|
||||||
check('<\udce2\udc82\udcac>', '<\u20ac>')
|
check('<\udce2\udc82\udcac>', '<\u20ac>', '<\u20ac>')
|
||||||
check('<\udced\udca0\udcbd\udced\udcb2\udcbb>', '<\U0001f4bb>')
|
check('<\udced\udca0\udcbd\udced\udcb2\udcbb>', '<\U0001f4bb>', '<\U0001f4bb>')
|
||||||
check('')
|
check('')
|
||||||
check(b'string', 'string')
|
check(b'string', 'string')
|
||||||
check(b'string\xe2\x82\xac', 'string\xe2\x82\xac')
|
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)
|
||||||
check(-float('inf'), eq=float_eq)
|
check(-float('inf'), eq=float_eq)
|
||||||
# XXX NaN representation can be not parsable by float()
|
# XXX NaN representation can be not parsable by float()
|
||||||
check((), '')
|
check((), '', '')
|
||||||
check((1, (2,), (3, 4), '5 6', ()), '1 2 {3 4} {5 6} {}')
|
check((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} {}',
|
||||||
|
(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):
|
def test_splitlist(self):
|
||||||
splitlist = self.interp.tk.splitlist
|
splitlist = self.interp.tk.splitlist
|
||||||
|
|
|
@ -40,7 +40,7 @@ TclError = _tkinter.TclError
|
||||||
from tkinter.constants import *
|
from tkinter.constants import *
|
||||||
import re
|
import re
|
||||||
|
|
||||||
wantobjects = 1
|
wantobjects = 2
|
||||||
_debug = False # set to True to print executed Tcl/Tk commands
|
_debug = False # set to True to print executed Tcl/Tk commands
|
||||||
|
|
||||||
TkVersion = float(_tkinter.TK_VERSION)
|
TkVersion = float(_tkinter.TK_VERSION)
|
||||||
|
@ -1762,7 +1762,10 @@ class Misc:
|
||||||
try:
|
try:
|
||||||
e.type = EventType(T)
|
e.type = EventType(T)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
e.type = T
|
try:
|
||||||
|
e.type = EventType(str(T)) # can be int
|
||||||
|
except ValueError:
|
||||||
|
e.type = T
|
||||||
try:
|
try:
|
||||||
e.widget = self._nametowidget(W)
|
e.widget = self._nametowidget(W)
|
||||||
except KeyError:
|
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 */
|
/* Client data struct */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject *self;
|
TkappObject *self;
|
||||||
PyObject *func;
|
PyObject *func;
|
||||||
} PythonCmd_ClientData;
|
} PythonCmd_ClientData;
|
||||||
|
|
||||||
|
@ -2272,6 +2272,7 @@ PythonCmd(ClientData clientData, Tcl_Interp *interp,
|
||||||
PyObject *args, *res;
|
PyObject *args, *res;
|
||||||
int i;
|
int i;
|
||||||
Tcl_Obj *obj_res;
|
Tcl_Obj *obj_res;
|
||||||
|
int objargs = data->self->wantobjects >= 2;
|
||||||
|
|
||||||
ENTER_PYTHON
|
ENTER_PYTHON
|
||||||
|
|
||||||
|
@ -2280,7 +2281,8 @@ PythonCmd(ClientData clientData, Tcl_Interp *interp,
|
||||||
return PythonCmd_Error(interp);
|
return PythonCmd_Error(interp);
|
||||||
|
|
||||||
for (i = 0; i < (objc - 1); i++) {
|
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) {
|
if (!s) {
|
||||||
Py_DECREF(args);
|
Py_DECREF(args);
|
||||||
return PythonCmd_Error(interp);
|
return PythonCmd_Error(interp);
|
||||||
|
@ -2383,7 +2385,8 @@ _tkinter_tkapp_createcommand_impl(TkappObject *self, const char *name,
|
||||||
data = PyMem_NEW(PythonCmd_ClientData, 1);
|
data = PyMem_NEW(PythonCmd_ClientData, 1);
|
||||||
if (!data)
|
if (!data)
|
||||||
return PyErr_NoMemory();
|
return PyErr_NoMemory();
|
||||||
data->self = Py_NewRef(self);
|
Py_INCREF(self);
|
||||||
|
data->self = self;
|
||||||
data->func = Py_NewRef(func);
|
data->func = Py_NewRef(func);
|
||||||
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
|
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
|
||||||
Tcl_Condition cond = NULL;
|
Tcl_Condition cond = NULL;
|
||||||
|
@ -2897,10 +2900,10 @@ Tkapp_WantObjects(PyObject *self, PyObject *args)
|
||||||
{
|
{
|
||||||
|
|
||||||
int wantobjects = -1;
|
int wantobjects = -1;
|
||||||
if (!PyArg_ParseTuple(args, "|p:wantobjects", &wantobjects))
|
if (!PyArg_ParseTuple(args, "|i:wantobjects", &wantobjects))
|
||||||
return NULL;
|
return NULL;
|
||||||
if (wantobjects == -1)
|
if (wantobjects == -1)
|
||||||
return PyBool_FromLong(((TkappObject*)self)->wantobjects);
|
return PyLong_FromLong(((TkappObject*)self)->wantobjects);
|
||||||
((TkappObject*)self)->wantobjects = wantobjects;
|
((TkappObject*)self)->wantobjects = wantobjects;
|
||||||
|
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
|
@ -3086,7 +3089,7 @@ _tkinter.create
|
||||||
baseName: str = ""
|
baseName: str = ""
|
||||||
className: str = "Tk"
|
className: str = "Tk"
|
||||||
interactive: bool = False
|
interactive: bool = False
|
||||||
wantobjects: bool = False
|
wantobjects: int = 0
|
||||||
wantTk: bool = True
|
wantTk: bool = True
|
||||||
if false, then Tk_Init() doesn't get called
|
if false, then Tk_Init() doesn't get called
|
||||||
sync: bool = False
|
sync: bool = False
|
||||||
|
@ -3102,7 +3105,7 @@ _tkinter_create_impl(PyObject *module, const char *screenName,
|
||||||
const char *baseName, const char *className,
|
const char *baseName, const char *className,
|
||||||
int interactive, int wantobjects, int wantTk, int sync,
|
int interactive, int wantobjects, int wantTk, int sync,
|
||||||
const char *use)
|
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;
|
/* XXX baseName is not used anymore;
|
||||||
* try getting rid of it. */
|
* try getting rid of it. */
|
||||||
|
|
8
Modules/clinic/_tkinter.c.h
generated
8
Modules/clinic/_tkinter.c.h
generated
|
@ -676,7 +676,7 @@ PyDoc_STRVAR(_tkinter__flatten__doc__,
|
||||||
|
|
||||||
PyDoc_STRVAR(_tkinter_create__doc__,
|
PyDoc_STRVAR(_tkinter_create__doc__,
|
||||||
"create($module, screenName=None, baseName=\'\', className=\'Tk\',\n"
|
"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"
|
" use=None, /)\n"
|
||||||
"--\n"
|
"--\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -777,8 +777,8 @@ _tkinter_create(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
|
||||||
if (nargs < 5) {
|
if (nargs < 5) {
|
||||||
goto skip_optional;
|
goto skip_optional;
|
||||||
}
|
}
|
||||||
wantobjects = PyObject_IsTrue(args[4]);
|
wantobjects = PyLong_AsInt(args[4]);
|
||||||
if (wantobjects < 0) {
|
if (wantobjects == -1 && PyErr_Occurred()) {
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
if (nargs < 6) {
|
if (nargs < 6) {
|
||||||
|
@ -888,4 +888,4 @@ exit:
|
||||||
#ifndef _TKINTER_TKAPP_DELETEFILEHANDLER_METHODDEF
|
#ifndef _TKINTER_TKAPP_DELETEFILEHANDLER_METHODDEF
|
||||||
#define _TKINTER_TKAPP_DELETEFILEHANDLER_METHODDEF
|
#define _TKINTER_TKAPP_DELETEFILEHANDLER_METHODDEF
|
||||||
#endif /* !defined(_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…
Add table
Add a link
Reference in a new issue