Skip to content
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

gh-131885: Updates docs to make max and min iterable param positional only #131868

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

ekohilas
Copy link
Contributor

@ekohilas ekohilas commented Mar 29, 2025

This PR updates the documentation of max and min to match their implementation like how sorted's implementation matches it's documentation.

Sorted's function definition in the documentation is as follows:

sorted(iterable, /, *, key=None, reverse=False)

When attempted to call with iterable as a keyword argument, the following occurs:

>>> sorted(iterable=[1])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: sorted expected 1 argument, got 0

However, looking at the documentation for max:

max(iterable, *, key=None)

And comparing to their implementation:

>>> max(iterable=[1])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: max expected at least 1 argument, got 0

The same follows for min


📚 Documentation preview 📚: https://cpython-previews--131868.org.readthedocs.build/

@bedevere-app bedevere-app bot added docs Documentation in the Doc dir skip news labels Mar 29, 2025
@ekohilas ekohilas marked this pull request as ready for review March 29, 2025 05:26
@ekohilas
Copy link
Contributor Author

Actually, I'm finding more examples of these cases such as map and range. If the above looks good I'll add more commits to include those too.

Copy link
Member

@skirpichev skirpichev left a comment

Choose a reason for hiding this comment

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

Actually, I'm finding more examples of these cases such as map and range. If the above looks good I'll add more commits to include those too.

Previously similar change was reverted: #99476 See also #125945

While devguide suggests now using correct function signatures, I'm not sure we shold push this that way.

We definitely need an issue, e.g. to adjust all signatures on the builtin functions page. We should also consider if we could actually simplify function signatures to avoid '/' and/or '*' syntax. See https://discuss.python.org/t/81003

CC @AA-Turner

@@ -2019,7 +2019,7 @@ builtin_min(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *k
}

PyDoc_STRVAR(min_doc,
"min(iterable, *[, default=obj, key=func]) -> value\n\
"min(iterable, /, *[, default=obj, key=func]) -> value\n\
Copy link
Member

Choose a reason for hiding this comment

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

This is not a valid Python syntax. Same for max().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I understand the issue being that the square brackets make it invalid syntax, and that without this change, the documentation would still be wrong.

The reason for change here is to ensure that the web docs align with the code docs, similar to sorted:

"sorted($module, iterable, /, *, key=None, reverse=False)\n"

Would it be more correct to instead write:

Suggested change
"min(iterable, /, *[, default=obj, key=func]) -> value\n\
"min(iterable, /, *, key=None) -> value\n\
min(iterable, /, *, default, key=None) -> value\n\
min(arg1, arg2, *args, /, *, key=None) -> value\n\

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@skirpichev Could you please help me understand why -> value should be removed?

Copy link
Member

Choose a reason for hiding this comment

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

Rationale is just same as for sphinx docs: it's not a valid syntax (you will get a NameError trying to parse such signature with inspect.signature()).

Copy link
Member

@picnixz picnixz Mar 30, 2025

Choose a reason for hiding this comment

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

I understand the issue being that the square brackets make it invalid syntax, and that without this change, the documentation would still be wrong.

Actually, it's a valid Sphinx syntax. It's the "legacy" way of writing signatures. Many built-ins are written that way. As for ->, I think it was meant for "readability" even if it's unparsable. I would personally leave this untouched in this PR (namely, only add positional-only marker but leave out the ->). In a follow-up, we could clean-up the way "invalid" syntaxes, but this is something different than adding the markers.

@ekohilas
Copy link
Contributor Author

Actually, I'm finding more examples of these cases such as map and range. If the above looks good I'll add more commits to include those too.

Previously similar change was reverted: #99476 See also #125945

Thanks for these references!

While devguide suggests now using correct function signatures, I'm not sure we shold push this that way.

We definitely need an issue, e.g. to adjust all signatures on the builtin functions page. We should also consider if we could actually simplify function signatures to avoid '/' and/or '*' syntax. See https://discuss.python.org/t/81003

Thank you for linking this read!

I too would like to see simplifying these built-in function signatures, and have functions like range allow for keyword arguments.

As I understand it, doing so would be a one day door that would require additional thought and decisions, as if it went through and started to be used, it would no longer be backwards compatible.

Until then, this seems like the next best step. It's what the Editorial board has agreed on, it brings consistency, and reverting these changes in the future if and when they are simplified would be easy.

As it stands, without these changes:

  • The documentation is inconsistent and causes confusion. e.g.
    • For max, * is used to highlight how the function works, but / isn't, even though it behaves like it is.
    • There is no reference in the docs to specify that max doesn't allow some arguments as keyword.
  • It's difficult for users to understand what the correct usage is, or how to re-create these functions themselves. If a user copies this function definition to do so, it would not match the real definition.

@@ -2019,7 +2019,7 @@ builtin_min(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *k
}

PyDoc_STRVAR(min_doc,
"min(iterable, *[, default=obj, key=func]) -> value\n\
"min(iterable, /, *[, default=obj, key=func]) -> value\n\
Copy link
Member

@skirpichev skirpichev Mar 30, 2025

Choose a reason for hiding this comment

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

Suggested change
"min(iterable, /, *[, default=obj, key=func]) -> value\n\
"min(iterable, /, *, key=None)\n\
min(iterable, /, *, default, key=None)\n\
min(arg1, arg2, *args, /, key=None)\n\

just like sphinx docs

Co-authored-by: Evan Kohilas <ekohilas@users.noreply.github.com>
@ekohilas
Copy link
Contributor Author

Created issue: #131885

@skirpichev skirpichev changed the title Updates docs to make max and min iterable param positional only gh-131885: Updates docs to make max and min iterable param positional only Mar 30, 2025
@picnixz
Copy link
Member

picnixz commented Mar 30, 2025

While devguide suggests now using correct function signatures, I'm not sure we shold push this that way.

Personally, I read this is as requirement and not a suggestion. Namely,

If a function accepts positional-only or keyword-only arguments, include the slash and the star in the signature as appropriate:

Here, the include is an imperative form and not a recommendation IMO. In addition, the rationale is (emphasis mine)

Although the syntax is terse, it is precise about the allowable ways to call the function and is taken from Python itself.

So I think it's fine to accept those changes. The reversal of previous changes was mostly done because it was incorrect, albeit eval() could have used a positional-only at the end.

@@ -2036,7 +2036,7 @@ builtin_max(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *k
}

PyDoc_STRVAR(max_doc,
"max(iterable, *[, default=obj, key=func]) -> value\n\
"max(iterable, /, *[, default=obj, key=func]) -> value\n\
max(arg1, arg2, *args, *[, key=func]) -> value\n\
Copy link
Member

Choose a reason for hiding this comment

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

This needs a change as well.

@@ -2019,7 +2019,7 @@ builtin_min(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *k
}

PyDoc_STRVAR(min_doc,
"min(iterable, *[, default=obj, key=func]) -> value\n\
"min(iterable, /, *[, default=obj, key=func]) -> value\n\
min(arg1, arg2, *args, *[, key=func]) -> value\n\
Copy link
Member

Choose a reason for hiding this comment

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

This needs a change as well.

Copy link
Member

Choose a reason for hiding this comment

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

Suggestion was above;)

Copy link
Member

Choose a reason for hiding this comment

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

Ups, missed it :')

@skirpichev
Copy link
Member

So I think it's fine to accept those changes.

Probably, yes. But rather than mechanically accept "complex" signatures - we should revisit on case-by-case basis if we could simplify them. E.g. support bool(x) instead of bool(x, /).

The reversal of previous changes was mostly done because it was incorrect

@picnixz, are you about #99476? Reversion was argued not by correctness: #98340

@picnixz
Copy link
Member

picnixz commented Mar 30, 2025

Wait, were you talking about #100547 or another reversal?

@picnixz
Copy link
Member

picnixz commented Mar 30, 2025

Ah no I see. Ok you were talking about another PR. My bad. Well, the EB decision is quite recent and the reversal was done before that decision so I think it doesn't apply here.

@AA-Turner
Copy link
Member

Probably, yes. But rather than mechanically accept "complex" signatures - we should revisit on case-by-case basis if we could simplify them. E.g. support bool(x) instead of bool(x, /).

For this reason, I'm not sure the tracking issue is helpful -- it will encourage people to create many PRs to 'fix' the issue, when it is not just a mechanical transformation at hand.

@picnixz
Copy link
Member

picnixz commented Mar 30, 2025

I'm not sure the tracking issue is helpful

This is the reason why I didn't add the "easy" label. But tracking is fine I think. I can add a warning note to say that it's not an easy one as the transformation is not just mechanical

@skirpichev
Copy link
Member

I'm not sure the tracking issue is helpful -- it will encourage people to create many PRs to 'fix' the issue, when it is not just a mechanical transformation at hand.

Someone could comment on issue to clarify it further.

@picnixz
Copy link
Member

picnixz commented Mar 30, 2025

I've added a red caution box, hopefully it will be seen

Copy link
Member

@AA-Turner AA-Turner left a comment

Choose a reason for hiding this comment

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

We should avoid mixing C-style [,optional-arg] with the / etc markers.

I think we should also not conflate changes to the documentation, which support a wider range of syntax, with changes to function signatures in docstrings, which are interpreted by pydoc, help(), etc. See Raymond's Discourse post on improving support for signatures, and Serhiy's draft work on multi-signature support for inspect.

For now, please revert the changes to bltinmodule.c, and we can start with iterative improvements to the rST documentation.

A

@bedevere-app
Copy link

bedevere-app bot commented Mar 30, 2025

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

And if you don't make the requested changes, you will be put in the comfy chair!

@AA-Turner AA-Turner removed the request for review from ericsnowcurrently March 30, 2025 17:19
@skirpichev
Copy link
Member

I've added a red caution box, hopefully it will be seen

Thanks! One thought; maybe we could mention that it's better to start with cases, when function signatures are simple, i.e. can be expressed with usual syntax for function definition in Python (e.g. round()).

@AA-Turner, honestly I don't sure I get why we should keep old signatures in docstrings.

First, we will have different signatures in the sphinx docs wrt docstrings - as an eternal source of bugs. Second, [,optional-arg]-syntax is not documented.

we should also not conflate changes to the documentation, which support a wider range of syntax, with changes to function signatures in docstrings, which are interpreted by pydoc, help(), etc.

Why not? I think it's a good idea to keep them in sync. BTW, I don't think that "C-style" signatures are interpreted somewhere.

See Raymond's Discourse post on improving support for signatures, and Serhiy's draft work on multi-signature support for inspect.

I doubt that current shortcomings of the inspect module really relevant here.

Yes, in the given example we can't accurately describe each function just by one signature with a correct Python syntax. But can with multiple, just as it's proposed in sphinx docs!

That's how it looks on top of the #117671 (I did some conflict resolution in skirpichev#7):

>>> help(max)
Help on built-in function max in module builtins:

max(iterable, /, *, key=None)
max(iterable, /, *, default, key=None)
max(arg1, arg2, /, *args, key=None)
    With a single iterable argument, return its biggest item. The
    default keyword-only argument specifies an object to return if
    the provided iterable is empty.
    With two or more positional arguments, return the largest argument.

>>> help(min)
Help on built-in function min in module builtins:

min(iterable, /, *, key=None)
min(iterable, /, *, default, key=None)
min(arg1, arg2, /, *args, key=None)
    With a single iterable argument, return its smallest item. The
    default keyword-only argument specifies an object to return if
    the provided iterable is empty.
    With two or more positional arguments, return the smallest argument.

>>> import inspect
>>> inspect.signatures(max)
<MultiSignature (iterable, /, *, key=None)|(iterable, /, *, default, key=None)|(arg1, arg2, /, *args, key=None)>

As you can see, function signatures exactly follow to proposal in this pr.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
awaiting changes docs Documentation in the Doc dir skip news
Projects
Status: Todo
Development

Successfully merging this pull request may close these issues.

4 participants