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
base: main
Are you sure you want to change the base?
Add weakref Python types #3835
Conversation
As a result of the checks made about the objectpointers in PyO3, all errors in PyWeakref_GetObject are already caught. Therefor there is no need to check for these errors in the non-ffi layer.
There are some issues with the new |
CodSpeed Performance ReportMerging #3835 will not alter performanceComparing Summary
|
d24fc7c
to
dff0914
Compare
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 The type-objects for the reference types are not exported (not bound to in |
There was a problem hiding this 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.
Regarding PyPy, for now I suggest just not offering these types on PyPy. |
@davidhewitt, I'm running into an issue where code compiled with It does work when compiling against Python 3.7 without Do you have any idea what is going on? |
https://github.com/PyO3/pyo3/blob/main/src%2Fpyclass%2Fcreate_type_object.rs#L311-L370 When compiling Could this be the origin of the problem? |
IIRC this is a known limitation, we should probably make the |
There was a problem hiding this 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
assert!( | ||
object.is_callable(), | ||
"An object to be referenced by a PyWeakCallableProxy should be callable. Use PyWeakProxy instead." | ||
); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
toPyWeakNonCallableProxy
- Create a new
PyWeakProxy
which can hold an enum to abstract overPyWeakNonCallableProxy
andPyWeakCallableProxy
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)))
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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...
(MaybePyWeakAnyRef
?): Any Weakreference type, usingPyWeakref_Check
PyWeakRef
:weakref.ReferenceType
usingPyWeakref_CheckRef
for the specific normal reference typePyWeakProxy
: Any weak Proxy type (Callable or Not) usingPyWeakref_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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 mightPyWeakrefRef
be better. I like the longer name for the clarity, but it feels clunky due to the length.PyWeakrefReference
(or Pythonsweakref.ref
) is subclassable (The only subclassableweakref
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?
There was a problem hiding this comment.
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.
I think all the (current) feedback has been processed. |
@davidhewitt, Would you have time to review the PR? |
#3134 Add
PyWeakRef
,PyWeakProxy
andPyWeakCallableProxy
types, corresponding to Python'sweakref.ReferenceType
,weakref.ProxyType
andweakref.CallableProxyType
.