Skip to content

gh-111999: Add signatures and improve docstrings for builtins #112000

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

Merged
merged 3 commits into from
Nov 13, 2023
Merged
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
11 changes: 8 additions & 3 deletions Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -4734,12 +4734,13 @@ def test_builtins_have_signatures(self):
needs_null = {"anext"}
no_signature |= needs_null
# These need *args support in Argument Clinic
needs_varargs = {"breakpoint", "min", "max", "__build_class__"}
needs_varargs = {"min", "max", "__build_class__"}
no_signature |= needs_varargs
# These builtin types are expected to provide introspection info
types_with_signatures = {
'complex', 'enumerate', 'float', 'list', 'memoryview', 'object',
'property', 'reversed', 'tuple',
'bool', 'classmethod', 'complex', 'enumerate', 'filter', 'float',
'frozenset', 'list', 'map', 'memoryview', 'object', 'property',
'reversed', 'set', 'staticmethod', 'tuple', 'zip'
}
# Check the signatures we expect to be there
ns = vars(builtins)
Expand All @@ -4753,6 +4754,10 @@ def test_builtins_have_signatures(self):
if (name in no_signature):
# Not yet converted
continue
if name in {'classmethod', 'staticmethod'}:
# Bug gh-112006: inspect.unwrap() does not work with types
# with the __wrapped__ data descriptor.
continue
with self.subTest(builtin=name):
self.assertIsNotNone(inspect.signature(obj))
# Check callables that haven't been converted don't claim a signature
Expand Down
5 changes: 3 additions & 2 deletions Objects/boolobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,10 @@ bool_xor(PyObject *a, PyObject *b)
/* Doc string */

PyDoc_STRVAR(bool_doc,
"bool(x) -> bool\n\
"bool(object=False, /)\n\
Copy link
Member

Choose a reason for hiding this comment

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

Sphinx docs shows this as bool(x=False) (give impression that this is a positional-or-keyword argument). Probably, this should be adjusted too?

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe in other issue. The documentation not always uses syntax for positional-only parameters. It is relatively new.

--\n\
\n\
Returns True when the argument x is true, False otherwise.\n\
Returns True when the argument is true, False otherwise.\n\
The builtins True and False are the only two instances of the class bool.\n\
The class bool is a subclass of the class int, and cannot be subclassed.");

Expand Down
3 changes: 3 additions & 0 deletions Objects/descrobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1541,6 +1541,9 @@ property_deleter(PyObject *self, PyObject *deleter)


PyDoc_STRVAR(set_name_doc,
"__set_name__($self, owner, name, /)\n"
"--\n"
"\n"
"Method to set name of a property.");

static PyObject *
Expand Down
6 changes: 4 additions & 2 deletions Objects/funcobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1164,7 +1164,8 @@ cm_repr(classmethod *cm)
}

PyDoc_STRVAR(classmethod_doc,
"classmethod(function) -> method\n\
"classmethod(function, /)\n\
--\n\
\n\
Convert a function to be a class method.\n\
\n\
Expand Down Expand Up @@ -1359,7 +1360,8 @@ sm_repr(staticmethod *sm)
}

PyDoc_STRVAR(staticmethod_doc,
"staticmethod(function) -> method\n\
"staticmethod(function, /)\n\
--\n\
\n\
Convert a function to be a static method.\n\
\n\
Expand Down
48 changes: 32 additions & 16 deletions Objects/setobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -942,7 +942,10 @@ set_update(PySetObject *so, PyObject *args)
}

PyDoc_STRVAR(update_doc,
"Update a set with the union of itself and others.");
"update($self, /, *others)\n\
--\n\
\n\
Update the set, adding elements from all others.");

/* XXX Todo:
If aligned memory allocations become available, make the
Expand Down Expand Up @@ -1141,9 +1144,10 @@ set_union(PySetObject *so, PyObject *args)
}

PyDoc_STRVAR(union_doc,
"Return the union of sets as a new set.\n\
"union($self, /, *others)\n\
--\n\
\n\
(i.e. all elements that are in either set.)");
Return a new set with elements from the set and all others.");

static PyObject *
set_or(PySetObject *so, PyObject *other)
Expand Down Expand Up @@ -1281,9 +1285,10 @@ set_intersection_multi(PySetObject *so, PyObject *args)
}

PyDoc_STRVAR(intersection_doc,
"Return the intersection of two sets as a new set.\n\
"intersection($self, /, *others)\n\
--\n\
\n\
(i.e. all elements that are in both sets.)");
Return a new set with elements common to the set and all others.");

static PyObject *
set_intersection_update(PySetObject *so, PyObject *other)
Expand Down Expand Up @@ -1312,7 +1317,10 @@ set_intersection_update_multi(PySetObject *so, PyObject *args)
}

PyDoc_STRVAR(intersection_update_doc,
"Update a set with the intersection of itself and another.");
"intersection_update($self, /, *others)\n\
--\n\
\n\
Update the set, keeping only elements found in it and all others.");

static PyObject *
set_and(PySetObject *so, PyObject *other)
Expand Down Expand Up @@ -1470,7 +1478,10 @@ set_difference_update(PySetObject *so, PyObject *args)
}

PyDoc_STRVAR(difference_update_doc,
"Remove all elements of another set from this set.");
"difference_update($self, /, *others)\n\
--\n\
\n\
Update the set, removing elements found in others.");

static PyObject *
set_copy_and_difference(PySetObject *so, PyObject *other)
Expand Down Expand Up @@ -1587,9 +1598,10 @@ set_difference_multi(PySetObject *so, PyObject *args)
}

PyDoc_STRVAR(difference_doc,
"Return the difference of two or more sets as a new set.\n\
"difference($self, /, *others)\n\
--\n\
\n\
(i.e. all elements that are in this set but not the others.)");
Return a new set with elements in the set that are not in the others.");
static PyObject *
set_sub(PySetObject *so, PyObject *other)
{
Expand Down Expand Up @@ -1673,7 +1685,10 @@ set_symmetric_difference_update(PySetObject *so, PyObject *other)
}

PyDoc_STRVAR(symmetric_difference_update_doc,
"Update a set with the symmetric difference of itself and another.");
"symmetric_difference_update($self, other, /)\n\
--\n\
\n\
Update the set, keeping only elements found in either set, but not in both.");

static PyObject *
set_symmetric_difference(PySetObject *so, PyObject *other)
Expand All @@ -1694,9 +1709,10 @@ set_symmetric_difference(PySetObject *so, PyObject *other)
}

PyDoc_STRVAR(symmetric_difference_doc,
"Return the symmetric difference of two sets as a new set.\n\
"symmetric_difference($self, other, /)\n\
--\n\
\n\
(i.e. all elements that are in exactly one of the sets.)");
Return a new set with elements in either the set or other but not both.");
Copy link
Member

@skirpichev skirpichev Nov 12, 2023

Choose a reason for hiding this comment

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

BTW, for other set methods it seems there are signatures. But...

An example:

>>> help(set.add)
Help on method_descriptor:

add(self, object, /)
    Add an element to a set.

    This has no effect if the element is already present.

c.f. the sphinx docs:

add(elem)
    Add element elem to the set.

Copy link
Member Author

Choose a reason for hiding this comment

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

Signatures for methods without arguments and with a single positional argument are now autogenerated.

Copy link
Member

Choose a reason for hiding this comment

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

Anyway, this looks like a bug.

Copy link
Member Author

Choose a reason for hiding this comment

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

What exactly?

Copy link
Member

Choose a reason for hiding this comment

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

I was hoping is obvious. Instead of mystic name object, I would expect something like this

>>> inspect.signature(set.add)
<Signature (self, element, /)>
>>> help(set.add)
Help on method_descriptor:

add(self, element, /)
    Add an element to a set.

    This has no effect if the element is already present.

I realize, parameter name here is not a part of API, but I think it should be meaningful, if possible.

Copy link
Member Author

@serhiy-storchaka serhiy-storchaka Nov 12, 2023

Choose a reason for hiding this comment

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

It could be improved, but it is not a bug. You can add explicit signatures for these methods, but for me it has low priority. I worked on support of multi-signatures, and this PR is a side product, the improvements that do not need multi-signatures (with multi-signatures and autogenerated signatures almost 100% of builtin functions and classes will have signatures).

Copy link
Member

Choose a reason for hiding this comment

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

I think 'object', an instance of the base object class, is as correct as 'element', which is a set-theory specific synonym. We might say that 'a list has items', but item is a synonym for object. Here also, I appreciate the technically correct list.append(self, object, /). I think the consistency of the auto-generated signatures is good.

Copy link
Member

Choose a reason for hiding this comment

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

One could argue that it should be set.add(self, hashable), as not any object can be added, whereas any object can be appended. The math term 'element' does not imply the restriction any more than 'object'.


static PyObject *
set_xor(PySetObject *so, PyObject *other)
Expand Down Expand Up @@ -2100,8 +2116,8 @@ static PyNumberMethods set_as_number = {
};

PyDoc_STRVAR(set_doc,
"set() -> new empty set object\n\
set(iterable) -> new set object\n\
"set(iterable=(), /)\n\
--\n\
\n\
Build an unordered collection of unique elements.");

Expand Down Expand Up @@ -2201,8 +2217,8 @@ static PyNumberMethods frozenset_as_number = {
};

PyDoc_STRVAR(frozenset_doc,
"frozenset() -> empty frozenset object\n\
frozenset(iterable) -> frozenset object\n\
"frozenset(iterable=(), /)\n\
--\n\
\n\
Build an immutable unordered collection of unique elements.");

Expand Down
6 changes: 4 additions & 2 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -5254,8 +5254,10 @@ static PyMethodDef type_methods[] = {
TYPE___SUBCLASSES___METHODDEF
{"__prepare__", _PyCFunction_CAST(type_prepare),
METH_FASTCALL | METH_KEYWORDS | METH_CLASS,
PyDoc_STR("__prepare__() -> dict\n"
"used to create the namespace for the class statement")},
PyDoc_STR("__prepare__($cls, name, bases, /, **kwds)\n"
"--\n"
"\n"
"Create the namespace for the class statement")},
TYPE___INSTANCECHECK___METHODDEF
TYPE___SUBCLASSCHECK___METHODDEF
TYPE___DIR___METHODDEF
Expand Down
12 changes: 7 additions & 5 deletions Objects/unicodeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -13390,15 +13390,17 @@ _PyUnicodeWriter_Dealloc(_PyUnicodeWriter *writer)
#include "stringlib/unicode_format.h"

PyDoc_STRVAR(format__doc__,
"S.format(*args, **kwargs) -> str\n\
"format($self, /, *args, **kwargs)\n\
--\n\
\n\
Return a formatted version of S, using substitutions from args and kwargs.\n\
Return a formatted version of the string, using substitutions from args and kwargs.\n\
The substitutions are identified by braces ('{' and '}').");

PyDoc_STRVAR(format_map__doc__,
"S.format_map(mapping) -> str\n\
"format_map($self, /, mapping)\n\
--\n\
\n\
Return a formatted version of S, using substitutions from mapping.\n\
Return a formatted version of the string, using substitutions from mapping.\n\
The substitutions are identified by braces ('{' and '}').");

/*[clinic input]
Expand Down Expand Up @@ -14696,7 +14698,7 @@ errors is specified, then the object must expose a data buffer\n\
that will be decoded using the given encoding and error handler.\n\
Otherwise, returns the result of object.__str__() (if defined)\n\
or repr(object).\n\
encoding defaults to sys.getdefaultencoding().\n\
encoding defaults to 'utf-8'.\n\
errors defaults to 'strict'.");

static PyObject *unicode_iter(PyObject *seq);
Expand Down
22 changes: 13 additions & 9 deletions Python/bltinmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@ builtin_breakpoint(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyOb

PyDoc_STRVAR(breakpoint_doc,
"breakpoint(*args, **kws)\n\
--\n\
\n\
Call sys.breakpointhook(*args, **kws). sys.breakpointhook() must accept\n\
whatever arguments are passed.\n\
Expand Down Expand Up @@ -626,7 +627,8 @@ static PyMethodDef filter_methods[] = {
};

PyDoc_STRVAR(filter_doc,
"filter(function or None, iterable) --> filter object\n\
"filter(function, iterable, /)\n\
--\n\
\n\
Return an iterator yielding those items of iterable for which function(item)\n\
is true. If function is None, return the items that are true.");
Expand Down Expand Up @@ -1449,7 +1451,8 @@ static PyMethodDef map_methods[] = {


PyDoc_STRVAR(map_doc,
"map(func, *iterables) --> map object\n\
"map(function, /, *iterables)\n\
--\n\
\n\
Make an iterator that computes the function using arguments from\n\
each of the iterables. Stops when the shortest iterable is exhausted.");
Expand Down Expand Up @@ -1888,7 +1891,7 @@ min(arg1, arg2, *args, *[, key=func]) -> value\n\
With a single iterable argument, return its smallest item. The\n\
default keyword-only argument specifies an object to return if\n\
the provided iterable is empty.\n\
With two or more arguments, return the smallest argument.");
With two or more positional arguments, return the smallest argument.");


/* AC: cannot convert yet, waiting for *args support */
Expand All @@ -1905,7 +1908,7 @@ max(arg1, arg2, *args, *[, key=func]) -> value\n\
With a single iterable argument, return its biggest item. The\n\
default keyword-only argument specifies an object to return if\n\
the provided iterable is empty.\n\
With two or more arguments, return the largest argument.");
With two or more positional arguments, return the largest argument.");


/*[clinic input]
Expand Down Expand Up @@ -2958,18 +2961,19 @@ static PyMethodDef zip_methods[] = {
};

PyDoc_STRVAR(zip_doc,
"zip(*iterables, strict=False) --> Yield tuples until an input is exhausted.\n\
\n\
>>> list(zip('abcdefg', range(3), range(4)))\n\
[('a', 0, 0), ('b', 1, 1), ('c', 2, 2)]\n\
"zip(*iterables, strict=False)\n\
--\n\
\n\
The zip object yields n-length tuples, where n is the number of iterables\n\
passed as positional arguments to zip(). The i-th element in every tuple\n\
comes from the i-th iterable argument to zip(). This continues until the\n\
shortest argument is exhausted.\n\
\n\
If strict is true and one of the arguments is exhausted before the others,\n\
raise a ValueError.");
raise a ValueError.\n\
\n\
>>> list(zip('abcdefg', range(3), range(4)))\n\
[('a', 0, 0), ('b', 1, 1), ('c', 2, 2)]");

PyTypeObject PyZip_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
Expand Down