Skip to content

Conversation

@vstinner
Copy link
Member

@vstinner vstinner commented Mar 15, 2024

Add Py_GetConstantRef() and Py_GetConstant() functions.

In the limited C API version 3.13, getting Py_None, Py_False, Py_True, Py_Ellipsis and Py_NotImplemented singletons is now implemented as function calls at the stable ABI level to hide implementation details. Getting these constants still return borrowed references.

Add _testlimitedcapi/object.c and test_capi/test_object.py to test Py_GetConstantRef() and Py_GetConstant() functions.


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

@vstinner
Copy link
Member Author

About the function name Py_GetConstantRef(): sometimes documentation refer to them as "constants", sometimes as "singletons":

C API:

Python API:

In PyPy, are these constants guaranteed to be unique singletons? I added Py_IsNone() for PyPy and HPy.

@cfbolz wrote:

Just chiming in to say that for PyPy this API would be extremely useful, because PyPy's "is" is not implementable with a pointer comparison on the C level (due to unboxing we need to compare integers, floats, etc by value). Right now, C extension code that compares pointers is subtly broken and cannot be fixed by us.

To be more friendly with other Python implementations which will implement these functions, I would prefer to avoid being specific about "singletons" and stick to "constant" name.

@vstinner
Copy link
Member Author

See also the discussion in 2020 about per-interpreter singletons: #83692 At the end, it was decided to go with PEP 683 Immortal objects.

@vstinner vstinner removed request for a team, encukou and ericsnowcurrently March 17, 2024 16:32
@zooba
Copy link
Member

zooba commented Mar 18, 2024

Thanks for trying it out!

I only have two comments about the naming. I'd rather GetConstantRef be the shorter/easier name, so that people are encouraged to get the sensible behaviour instead of borrowed references (we could make the current GetConstant into a _Py function to further discourage using it?).

I also think that we need an extra part to the enum names - Py_NONE_CONSTANT or Py_NONE_INDEX or something like that. Otherwise, I will accidentally use Py_NONE instead of Py_None at some point, and it will be embarrassing 😆

@vstinner
Copy link
Member Author

I'd rather GetConstantRef be the shorter/easier name, so that people are encouraged to get the sensible behaviour instead of borrowed references

Do you have a name to suggest? In practice, you can just use Py_None and it calls Py_GetConstant(Py_NONE) for you. It should be an uncommon operation to call it directly. And you can write code in a way to only call it once.

(we could make the current GetConstant into a _Py function to further discourage using it?).

Currently, Py_None, Py_True, etc. are borrowed references. For backward compatibility, I would prefer to keep a public function replacing them. Moreover, it looks easy to upgrade code from PyConstant() to PyConstantRef(), since you just have to add Ref suffix.

I also think that we need an extra part to the enum names - Py_NONE_CONSTANT or Py_NONE_INDEX or something like that. Otherwise, I will accidentally use Py_NONE instead of Py_None at some point, and it will be embarrassing 😆

Py_GetConstantRef(Py_NONE_CONSTANT) sounds redundant: "constant" is written twice. I'm fine with Py_GetConstantRef(Py_NONE_IDX) and Py_GetConstantRef(Py_NONE_CST).

@zooba
Copy link
Member

zooba commented Mar 18, 2024

Py_NONE_IDX is good. As you mentioned first, we're not expecting people to have to type this themselves.

As for the function naming, I think I'd be happy with Py_GetConstant returning a strong reference and Py_GetConstantBorrowed returning a borrowed reference (or _Py_GetConstantBorrowed). If we have another word we already use instead of "borrowed" then that would be fine too.

Languages other than C are going to have to use this function, as they can't use our macros. And I want them to choose the "right" function (strong reference) because it's obvious.

@vstinner
Copy link
Member Author

I updated my PR:

  • Add _Py_GetConstant_Init(): initialize the array at once at startup
  • Rename Py_GetConstantRef() to Py_GetConstant().
  • Rename Py_GetConstant() to Py_GetConstantBorrowed() -- that name is quite long, but I suppose that the goal is also to discourage users to use borrowed references :-)
  • Rename constant identifiers, add _IDX suffix. Example: Py_NONE becomes Py_NONE_IDX.

At the end, we get: PyObject *zero = Py_GetConstant(Py_ZERO_IDX).

I'm not sure that zero index is the best name. Maybe PyObject *zero = Py_GetConstant(Py_ZERO_CST) would be better? Use _CST suffix: get the "zero constant".

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

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

Please don't force-push, it makes reviewing hard.

@vstinner
Copy link
Member Author

Please don't force-push, it makes reviewing hard.

Oh. I'm used to use git rebase to rewrite the commit message, since the API changed.

@gvanrossum
Copy link
Member

That's more productive for you, but more work for a reviewer. :-(

@zooba
Copy link
Member

zooba commented Mar 18, 2024

Maybe PyObject *zero = Py_GetConstant(Py_ZERO_CST) would be better? Use _CST suffix: get the "zero constant".

Or Py_CONSTANT_ZERO? Or Py_GETCONSTANT_ZERO to fully match the API name? Just CST makes me think of US central time, but having it before the specific constant might better suggest that it's not the constant, but an identifier for the constant.

@vstinner
Copy link
Member Author

I'm fine with Py_GetConstant(Py_CONSTANT_ZERO).

@vstinner
Copy link
Member Author

I updated the PR:

  • Complete the documentation to specify where borrowed references come from (I applied Petr's suggestion). I also mentioned that constants are immortal.
  • Rename Py_NONE_IDX to Py_CONSTANT_NONE
  • Remove the What's New entry about Py_None becoming a function call at the ABI level, but keep it in the Changelog.

Conflicting files
Modules/Setup.stdlib.in
Modules/_testlimitedcapi.c
Modules/_testlimitedcapi/parts.h
PCbuild/_testlimitedcapi.vcxproj
PCbuild/_testlimitedcapi.vcxproj.filters

I rebased the PR to fix merge conflicts.

Copy link
Member

@zooba zooba left a comment

Choose a reason for hiding this comment

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

No changes needed, but I did suggest a couple things to maybe think about either now or in the future.

_PyUnicode_InitState(interp);

if (_Py_IsMainInterpreter(interp)) {
_Py_GetConstant_Init();
Copy link
Member

Choose a reason for hiding this comment

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

I'm thinking ahead a little bit here, and we could change this later, but I think I'd pass interp into the init function (and it can do the IsMain check now, or later change to some other interpreter-specific behaviour if it becomes interesting/necessary).

@ericsnowcurrently FYI.

Copy link
Member Author

Choose a reason for hiding this comment

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

The 10 constants are immortal, right now I don't see the advantage to pass an interpreter state (or check for _Py_IsMainInterpreter () inside the funciton). If later it changes, we can easily pass interp, since it's an internal function.

@encukou
Copy link
Member

encukou commented Mar 20, 2024

Thanks for mentioning immortality as an implementation detail. If it was a guarantee of the API, there'd be no difference between Py_GetConstant and Py_GetConstantBorrowed :)

@vstinner
Copy link
Member Author

I updated the PR to address @zooba's review.

@vstinner
Copy link
Member Author

About the doc, I chose to use a table to give constant values:

Screenshot 2024-03-20 at 17-44-17 Object Protocol

@vstinner
Copy link
Member Author

@zooba @encukou @gvanrossum: Would you mind to review the updated PR?

encukou
encukou previously approved these changes Mar 21, 2024
Copy link
Member

@encukou encukou left a comment

Choose a reason for hiding this comment

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

Looks good overall, thank you for the addition!
I do have some comments:

@vstinner
Copy link
Member Author

@encukou: I applied your suggestions, please review the updated PR.

@encukou
Copy link
Member

encukou commented Mar 21, 2024

The documentation has strong requirement on the function argument:

This is stable ABI; future versions of Python might add new values.

But yes, PyErr_BadInternalCall is appropriate.

Copy link
Member

@encukou encukou left a comment

Choose a reason for hiding this comment

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

LGTM, thanks!

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

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

Yeah, looks good!

Add Py_GetConstant() and Py_GetConstantBorrowed() functions.

In the limited C API version 3.13, getting Py_None, Py_False,
Py_True, Py_Ellipsis and Py_NotImplemented singletons is now
implemented as function calls at the stable ABI level to hide
implementation details. Getting these constants still return borrowed
references.

Add _testlimitedcapi/object.c and test_capi/test_object.py to test
Py_GetConstant() and Py_GetConstantBorrowed() functions.
@vstinner vstinner enabled auto-merge (squash) March 21, 2024 15:20
@vstinner
Copy link
Member Author

Ok, thanks for reviews. Now that the PR is approved, I rebased it to solve the merge conflicts, and I enabled auto-merged.

@vstinner vstinner merged commit 8bea6c4 into python:main Mar 21, 2024
@vstinner vstinner deleted the get_constant branch March 21, 2024 16:07
@vstinner
Copy link
Member Author

I added the two functions to pythoncapi-commit: python/pythoncapi-compat@b16ff9a

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants