From 2ead3913827b8d7f7806a0caa4ccaf24ae9a3042 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 30 Sep 2018 19:03:28 +0300 Subject: [PATCH 1/2] bpo-34856: Make the repr of lambda containing the signature and body expression. --- Doc/library/bdb.rst | 2 +- Doc/library/pickle.rst | 3 +- Doc/library/unittest.mock.rst | 4 +-- Doc/whatsnew/3.8.rst | 12 +++++++ Lib/test/test_compile.py | 1 + Lib/test/test_docxmlrpc.py | 4 +-- Lib/test/test_funcattrs.py | 2 +- Lib/test/test_reprlib.py | 4 +-- Lib/unittest/test/test_case.py | 4 +-- .../2018-09-30-21-15-54.bpo-34856.uQBZD7.rst | 1 + Objects/funcobject.c | 15 +++++++-- Python/compile.c | 31 ++++++++++++------- 12 files changed, 58 insertions(+), 25 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2018-09-30-21-15-54.bpo-34856.uQBZD7.rst diff --git a/Doc/library/bdb.rst b/Doc/library/bdb.rst index 116ffcf88e3b69..c3e5c2f2759539 100644 --- a/Doc/library/bdb.rst +++ b/Doc/library/bdb.rst @@ -320,7 +320,7 @@ The :mod:`bdb` module also defines two classes: ``(frame, lineno)`` tuple: * The canonical form of the filename which contains the frame. - * The function name, or ``""``. + * The function name. * The input arguments. * The return value. * The line of code (if it exists). diff --git a/Doc/library/pickle.rst b/Doc/library/pickle.rst index 4f9d3596b649db..53da9ac27acb43 100644 --- a/Doc/library/pickle.rst +++ b/Doc/library/pickle.rst @@ -922,8 +922,7 @@ The following example reads the resulting pickled data. :: .. [#] Don't confuse this with the :mod:`marshal` module -.. [#] This is why :keyword:`lambda` functions cannot be pickled: all - :keyword:`lambda` functions share the same name: ````. +.. [#] This is why :keyword:`lambda` functions cannot be pickled. .. [#] The exception raised will likely be an :exc:`ImportError` or an :exc:`AttributeError` but it could be something else. diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index 06009e4a09716e..27a3432d447609 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -172,7 +172,7 @@ code if they are used incorrectly: >>> mock_function('wrong arguments') Traceback (most recent call last): ... - TypeError: () takes exactly 3 arguments (1 given) + TypeError: missing a required argument: 'b' :func:`create_autospec` can also be used on classes, where it copies the signature of the ``__init__`` method, and on callable objects where it copies the signature of @@ -2236,7 +2236,7 @@ we try to call it incorrectly: >>> req = request.Request() Traceback (most recent call last): ... - TypeError: () takes at least 2 arguments (1 given) + TypeError: missing a required argument: 'url' The spec also applies to instantiated classes (i.e. the return value of specced mocks): diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index a146249178f40d..bb1f8d3eccd079 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -107,6 +107,18 @@ Other Language Changes and :keyword:`return` statements. (Contributed by David Cuthbert and Jordan Chapman in :issue:`32117`.) +* The repr and the name of :keyword:`lambda` functions will now contain + the signature and body expression. For example:: + + >>> f = lambda x, y=1: x+y + >>> f + + >>> f.__name__ + '' + + (Contributed by Serhiy Storchaka in :issue:`32489`.) + + New Modules =========== diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 6b45a24334381c..99a778ab7b9d18 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -431,6 +431,7 @@ def test_compile_ast(self): ['', """if True:\n pass\n"""], ['', """for n in [1, 2, 3]:\n print(n)\n"""], ['', """def foo():\n pass\nfoo()\n"""], + ['', """f = lambda x: x+1"""], [fname, fcontents], ] diff --git a/Lib/test/test_docxmlrpc.py b/Lib/test/test_docxmlrpc.py index f077f05f5b4f71..44b9af9c53e01a 100644 --- a/Lib/test/test_docxmlrpc.py +++ b/Lib/test/test_docxmlrpc.py @@ -114,8 +114,8 @@ def test_lambda(self): self.client.request("GET", "/") response = self.client.getresponse() - self.assertIn((b'
' - b'<lambda>(x, y)
'), + self.assertIn((b'
' + b'<lambda x, y: x - y>(x, y)
'), response.read()) @make_request_and_skipIf(sys.flags.optimize >= 2, diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py index 35fd657ec0ba56..8bbfd5f36d7c44 100644 --- a/Lib/test/test_funcattrs.py +++ b/Lib/test/test_funcattrs.py @@ -135,7 +135,7 @@ def test___qualname__(self): self.assertEqual(FuncAttrsTest.setUp.__qualname__, 'FuncAttrsTest.setUp') self.assertEqual(global_function.__qualname__, 'global_function') self.assertEqual(global_function().__qualname__, - 'global_function..') + '') self.assertEqual(global_function()().__qualname__, 'global_function..inner_function') self.assertEqual(global_function()()().__qualname__, diff --git a/Lib/test/test_reprlib.py b/Lib/test/test_reprlib.py index 4bf91945ea4f4a..98ffce1464a2c1 100644 --- a/Lib/test/test_reprlib.py +++ b/Lib/test/test_reprlib.py @@ -145,8 +145,8 @@ def test_instance(self): self.assertIn(s.find("..."), [12, 13]) def test_lambda(self): - r = repr(lambda x: x) - self.assertTrue(r.startswith(".$', + self.failureException, '^Exception not raised by $', self.assertRaisesRegex, Exception, re.compile('x'), lambda: None) self.assertRaisesRegex( - self.failureException, '^Exception not raised by $', + self.failureException, '^Exception not raised by $', self.assertRaisesRegex, Exception, 'x', lambda: None) # Custom message diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-09-30-21-15-54.bpo-34856.uQBZD7.rst b/Misc/NEWS.d/next/Core and Builtins/2018-09-30-21-15-54.bpo-34856.uQBZD7.rst new file mode 100644 index 00000000000000..120b4c8ca981c3 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-09-30-21-15-54.bpo-34856.uQBZD7.rst @@ -0,0 +1 @@ +The repr of lambda now contains the signature and body expression. diff --git a/Objects/funcobject.c b/Objects/funcobject.c index c2f79c05dbb21b..0c4a4076b977b1 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -554,8 +554,19 @@ func_dealloc(PyFunctionObject *op) static PyObject* func_repr(PyFunctionObject *op) { - return PyUnicode_FromFormat("", - op->func_qualname, op); + PyObject *name = op->func_qualname; + Py_ssize_t len = PyUnicode_GET_LENGTH(name); + if (len > 2 + && PyUnicode_READ_CHAR(name, 0) == '<' + && PyUnicode_READ_CHAR(name, len-1) == '>') + { + PyObject *repr = PyUnicode_Substring(name, 0, len-1); + if (repr) { + Py_SETREF(repr, PyUnicode_FromFormat("%U at %p>", repr, op)); + } + return repr; + } + return PyUnicode_FromFormat("", name, op); } static int diff --git a/Python/compile.c b/Python/compile.c index 096b762f36bb0d..1fe4b7558d73c9 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -2306,31 +2306,40 @@ static int compiler_lambda(struct compiler *c, expr_ty e) { PyCodeObject *co; - PyObject *qualname; - static identifier name; + PyObject *name, *exprstr; Py_ssize_t funcflags; arguments_ty args = e->v.Lambda.args; assert(e->kind == Lambda_kind); + exprstr = _PyAST_ExprAsUnicode(e); + if (!exprstr) { + return 0; + } + name = PyUnicode_FromFormat("<%U>", exprstr); + Py_DECREF(exprstr); if (!name) { - name = PyUnicode_InternFromString(""); - if (!name) - return 0; + return 0; } funcflags = compiler_default_arguments(c, args); if (funcflags == -1) { + Py_DECREF(name); return 0; } if (!compiler_enter_scope(c, name, COMPILER_SCOPE_LAMBDA, (void *)e, e->lineno)) + { + Py_DECREF(name); return 0; + } /* Make None the first constant, so the lambda can't have a docstring. */ - if (compiler_add_const(c, Py_None) < 0) + if (compiler_add_const(c, Py_None) < 0) { + Py_DECREF(name); return 0; + } c->u->u_argcount = asdl_seq_LEN(args->args); c->u->u_kwonlyargcount = asdl_seq_LEN(args->kwonlyargs); @@ -2342,14 +2351,14 @@ compiler_lambda(struct compiler *c, expr_ty e) ADDOP_IN_SCOPE(c, RETURN_VALUE); co = assemble(c, 1); } - qualname = c->u->u_qualname; - Py_INCREF(qualname); compiler_exit_scope(c); - if (co == NULL) + if (co == NULL) { + Py_DECREF(name); return 0; + } - compiler_make_closure(c, co, funcflags, qualname); - Py_DECREF(qualname); + compiler_make_closure(c, co, funcflags, name); + Py_DECREF(name); Py_DECREF(co); return 1; From 4afc44b32c4c301022f5b1136895da542e53046e Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 1 Oct 2018 10:22:34 +0300 Subject: [PATCH 2/2] Avoid using invalid command names. --- Lib/tkinter/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index ff85f837d1d594..ae9c157baf281e 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -1360,15 +1360,18 @@ def _register(self, func, subst=None, needcleanup=1): be executed. An optional function SUBST can be given which will be executed before FUNC.""" f = CallWrapper(func, subst, self).__call__ - name = repr(id(f)) try: func = func.__func__ except AttributeError: pass try: - name = name + func.__name__ + name = func.__name__ except AttributeError: - pass + name = '' + else: + if not re.fullmatch('[a-zA-Z0-9_]+', name): + name = '' + name = repr(id(f)) + name self.tk.createcommand(name, f) if needcleanup: if self._tclCommands is None: