Skip to content

gh-79037: Make the repr of lambda containing the signature and body expression. #9647

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Doc/library/bdb.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 ``"<lambda>"``.
* The function name.
* The input arguments.
* The return value.
* The line of code (if it exists).
Expand Down
3 changes: 1 addition & 2 deletions Doc/library/pickle.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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: ``<lambda>``.
.. [#] 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.
Expand Down
4 changes: 2 additions & 2 deletions Doc/library/unittest.mock.rst
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ code if they are used incorrectly:
>>> mock_function('wrong arguments')
Traceback (most recent call last):
...
TypeError: <lambda>() 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
Expand Down Expand Up @@ -2236,7 +2236,7 @@ we try to call it incorrectly:
>>> req = request.Request()
Traceback (most recent call last):
...
TypeError: <lambda>() 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):
Expand Down
12 changes: 12 additions & 0 deletions Doc/whatsnew/3.8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whatsnew entry must be added to the current main file, not the 3.8 file.

the signature and body expression. For example::

>>> f = lambda x, y=1: x+y
>>> f
<lambda x, y=1: x + y at 0x7f437cd27890>
>>> f.__name__
'<lambda x, y=1: x + y>'

(Contributed by Serhiy Storchaka in :issue:`32489`.)


New Modules
===========

Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ def test_compile_ast(self):
['<ifblock>', """if True:\n pass\n"""],
['<forblock>', """for n in [1, 2, 3]:\n print(n)\n"""],
['<deffunc>', """def foo():\n pass\nfoo()\n"""],
['<lambda>', """f = lambda x: x+1"""],
[fname, fcontents],
]

Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_docxmlrpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ def test_lambda(self):
self.client.request("GET", "/")
response = self.client.getresponse()

self.assertIn((b'<dl><dt><a name="-&lt;lambda&gt;"><strong>'
b'&lt;lambda&gt;</strong></a>(x, y)</dt></dl>'),
self.assertIn((b'<dl><dt><a name="-&lt;lambda x, y: x - y&gt;"><strong>'
b'&lt;lambda x, y: x - y&gt;</strong></a>(x, y)</dt></dl>'),
response.read())

@make_request_and_skipIf(sys.flags.optimize >= 2,
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_funcattrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.<locals>.<lambda>')
'<lambda: inner_function>')
self.assertEqual(global_function()().__qualname__,
'global_function.<locals>.inner_function')
self.assertEqual(global_function()()().__qualname__,
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_reprlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -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("<function ReprTests.test_lambda.<locals>.<lambda"), r)
r = repr(lambda x, y=1: x+y)
self.assertTrue(r.startswith("<lambda x, y=1: x + y "), r)
# XXX anonymous functions? see func_repr

def test_builtin_function(self):
Expand Down
9 changes: 6 additions & 3 deletions Lib/tkinter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions Lib/unittest/test/test_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -1299,11 +1299,11 @@ def Stub():

def testAssertNotRaisesRegex(self):
self.assertRaisesRegex(
self.failureException, '^Exception not raised by <lambda>$',
self.failureException, '^Exception not raised by <lambda: None>$',
self.assertRaisesRegex, Exception, re.compile('x'),
lambda: None)
self.assertRaisesRegex(
self.failureException, '^Exception not raised by <lambda>$',
self.failureException, '^Exception not raised by <lambda: None>$',
self.assertRaisesRegex, Exception, 'x',
lambda: None)
# Custom message
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The repr of lambda now contains the signature and body expression.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The repr of lambda now contains the signature and body expression.
Add signature and body expression to the repr of lambda.

I think that this would also be a better PR title.

15 changes: 13 additions & 2 deletions Objects/funcobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -554,8 +554,19 @@ func_dealloc(PyFunctionObject *op)
static PyObject*
func_repr(PyFunctionObject *op)
{
return PyUnicode_FromFormat("<function %U at %p>",
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("<function %U at %p>", name, op);
}

static int
Expand Down
31 changes: 20 additions & 11 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this, add C equivalent of, say,
if len(exprstr) > 36: exprstr = exprstr[0:36] + ' ...'

Py_DECREF(exprstr);
if (!name) {
name = PyUnicode_InternFromString("<lambda>");
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);
Expand All @@ -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;
Expand Down