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

Add weakref Python types #3835

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

Conversation

SuperJappie08
Copy link

#3134 Add PyWeakRef, PyWeakProxy and PyWeakCallableProxy types, corresponding to Python's weakref.ReferenceType, weakref.ProxyType and weakref.CallableProxyType.

@SuperJappie08
Copy link
Author

There are some issues with the new run_bound.
I will resolve these issues now.

Copy link

codspeed-hq bot commented Feb 14, 2024

CodSpeed Performance Report

Merging #3835 will not alter performance

Comparing SuperJappie08:weakref (c7ab375) with main (b11174e)

Summary

✅ 69 untouched benchmarks

@davidhewitt
Copy link
Member

Thank you and sorry for the slow review, I will try my best to finally look at this tomorrow...

@SuperJappie08
Copy link
Author

Thank you and sorry for the slow review, I will try my best to finally look at this tomorrow...

No problem, completely understandable.

I also ran into one last issue, PyPy does not include all the required bindings for the weakref module in their C-api.
At least the bindings required in the way it is done now.

The type-objects for the reference types are not exported (not bound to in pyo3-ffi and I can't find them in the PyPy header files). Also, the memory layout is missing, since in PyPy weakref is not actually a binary module.
That last one should at least be solvable, but I cannot think of a solution for the missing type objects

Copy link
Member

@davidhewitt davidhewitt left a comment

Choose a reason for hiding this comment

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

Thank you very much for this!

It looked like a big diff but that's because you've included all docs and tests, thank you 🙏

I've given this a full read; I have a few initial things which need discussing and changing. I think once those are decided upon & done I might need to take another final read through just due to the size of the diff.

.gitignore Outdated Show resolved Hide resolved
src/types/weakref/reference.rs Outdated Show resolved Hide resolved
src/types/weakref/reference.rs Outdated Show resolved Hide resolved
src/types/weakref/reference.rs Outdated Show resolved Hide resolved
src/types/weakref/reference.rs Outdated Show resolved Hide resolved
src/types/weakref/reference.rs Outdated Show resolved Hide resolved
src/types/weakref/reference.rs Outdated Show resolved Hide resolved
src/types/weakref/reference.rs Outdated Show resolved Hide resolved
src/types/weakref/proxy.rs Outdated Show resolved Hide resolved
@davidhewitt
Copy link
Member

Regarding PyPy, for now I suggest just not offering these types on PyPy.

@SuperJappie08
Copy link
Author

SuperJappie08 commented Mar 8, 2024

@davidhewitt, I'm running into an issue where code compiled with abi-py37 does not enable PyClasses to be weakreferencable even if marked as so.

It does work when compiling against Python 3.7 without abi3-py37

Do you have any idea what is going on?

@SuperJappie08
Copy link
Author

https://github.com/PyO3/pyo3/blob/main/src%2Fpyclass%2Fcreate_type_object.rs#L311-L370

When compiling abi3 on Python versions lower than 3.9 it does not write any offsets to the type object.

Could this be the origin of the problem?

@davidhewitt
Copy link
Member

IIRC this is a known limitation, we should probably make the pyclass macro fail in this case. For this PR maybe it's good enough to skip the tests in the configuration.

Copy link
Member

@davidhewitt davidhewitt left a comment

Choose a reason for hiding this comment

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

Thanks for pushing this forward, it seems like it's nearly there now. I added some further thoughts. I was a little slow to review as the format checks are failing so I got a bit lots on the status of this.

src/types/weakref/callableproxy.rs Outdated Show resolved Hide resolved
src/types/weakref/reference.rs Outdated Show resolved Hide resolved
src/types/weakref/reference.rs Outdated Show resolved Hide resolved
Comment on lines 222 to 225
assert!(
object.is_callable(),
"An object to be referenced by a PyWeakCallableProxy should be callable. Use PyWeakProxy instead."
);
Copy link
Member

Choose a reason for hiding this comment

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

I wonder, given this footgun here and in PyWeakProxy constructors, should we consider merging these types into just PyWeakProxy? I guess the downside is that we don't match the C API so well, but maybe it works out as more ergonomic? I'm open to hearing your thoughts on this one, there might be very good reasons to keep them separate.

Copy link
Author

Choose a reason for hiding this comment

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

I agree that it is a real footgun. Merging these types wouldn't be that weird, since the Python-side API doesn't really differentiate between them. And the C API provides a check function which globs them together.

My suggestion may be the following:

  • Rename PyWeakProxy to PyWeakNonCallableProxy
  • Create a new PyWeakProxy which can hold an enum to abstract over PyWeakNonCallableProxy and PyWeakCallableProxy

This would (hopefully) allow for specific use of one specific type (if desired), while preventing the major footgun (as a result of the Python API design) for most users which would use the PyWeakProxy. (I referred to this as PyWeakProxyGeneric or PyWeakGenericProxy in #3134 (#3134 (comment)))

Copy link
Member

Choose a reason for hiding this comment

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

Looking at the type definitions, it seems that the check for both of these is PyWeakref_CheckProxy. I think this means that it gets hard to distinguish these two types from Rust.

What use cases do you envision for the specific types from the enum? If we can't think of any right now, one option might be to slim this type down a bit and just implement PyTypeCheck instead of PyTypeInfo, i.e. more like PyNone.

Copy link
Member

Choose a reason for hiding this comment

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

(and have one single PyWeakProxy covering both callable and non-callable types.)

Copy link
Author

Choose a reason for hiding this comment

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

That would probably make, the most sense.
I also recently figured out that the weakref type objects are actually part of the internal/private API, so they cannot be expected to be consistent in ABI3 or between patch versions, but it has been quite stable as far as I can see.

So maybe it would be good to move PyWeakRef also to the non-typeobject implementation, at least for the ABI3 target. We could still use the Type object pointer if we are compiling for a specific version, since it appears fairly stable(, while it is not guaranteed).

This would also allow work for PyPy, since they do export the Check functions.

An option I could for see is that we would follow the check functions:

  • PyWeak... (Maybe PyWeakAnyRef?): Any Weakreference type, using PyWeakref_Check
  • PyWeakRef: weakref.ReferenceType using PyWeakref_CheckRef for the specific normal reference type
  • PyWeakProxy: Any weak Proxy type (Callable or Not) using PyWeakref_CheckProxy.

This would align with the C API, and probably with the way these type will be used, since I can imagine that a function could take a weakreference, without caring what the exact reference type would be, since it does not matter (that much) when using it from the C API.
And the bonus point that we wouldn't have to exclude PyPy.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry for the delay here by me yet again. I agree with the suggestion to move them all to the non-typeobject implementation. In general I'm trying hard to make PyO3 not need to depend on CPython private APIs.

We can always expand the implementation later if CPython makes the APIs public.

As for naming, I guess the one that will be used most commonly is "any weak rerference type", so let's call that one PyWeakref maybe? We could then have PyWeakrefReference and PyWeakrefProxy for the specific subtypes.

So:

  • PyWeakref
  • PyWeakrefReference
  • PyWeakrefProxy

How does that sound to you?

Copy link
Author

Choose a reason for hiding this comment

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

The naming makes sense.

However I'm having some trouble doing the type objectless implementation.
You mentioned PyNone and PyNotImplemented as an example implementation however those are more like 'singleton' objects.

There is probably some way to do it and I want to figure it out. But do you have a hint?

(I also will not have much free time in the coming 3 weeks, so it is going to take a bit longer.)

Edit: After reading your previous comments carefully I could give it another go.

Copy link
Author

Choose a reason for hiding this comment

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

I have done it for PyWeakref and PyWeakrefProxy and renamed PyWeakRef to PyWeakrefReference.

I still need to make the changes for PyWeakrefReference; however, I have 2 findings:

  • PyWeakrefReference is quite a long name, is this very practical to use or might PyWeakrefRef be better. I like the longer name for the clarity, but it feels clunky due to the length.
  • PyWeakrefReference (or Pythons weakref.ref) is subclassable (The only subclassable weakref type). However, this non-typepointer implementation would not allow for subclassing. The use case of subclassing is to add extra data to the weakreference. Python weakref documentation mentioning this

Changing the implementation of PyWeakrefReference would mean no subclassing from PyO3 (with the cost of using an internal API, which has been available since Python 2.2 (and probably will remain available since they are used in the check macros)), but making the change would allow for alternative interpreter support.

What do you think?

Copy link
Author

Choose a reason for hiding this comment

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

@davidhewitt, The solution I thought pushed now does the following:
Only PyWeakrefReference uses the unstable API, only if you are compiling against a specific CPython version.
This keeps it subclassable, while allowing support for the other interpreters and the Limited API.

src/types/weakref/reference.rs Outdated Show resolved Hide resolved
@SuperJappie08
Copy link
Author

I think all the (current) feedback has been processed.

@SuperJappie08
Copy link
Author

@davidhewitt, Would you have time to review the PR?
Because I think it is nearly finished.
I'm still happy to make changes if necessary, but I am just afraid this PR is being buried (probably by more important PR's, which is fine) .

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.

None yet

2 participants