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

feat: Adds the Bound<'_, PyMappingProxy> type #4644

Merged
merged 14 commits into from
Oct 29, 2024

Conversation

KLMatlock
Copy link
Contributor

Continuation of #2435 and #3521. Implement the PyMappingProxy type (called PyDictProxy in CPython).

Resolves #2108.

@davidhewitt
Copy link
Member

Thanks for the PR! This is in draft status; is there more you want to add before review?

@KLMatlock
Copy link
Contributor Author

KLMatlock commented Oct 25, 2024

Thanks for the PR! This is in draft status; is there more you want to add before review?

Nope! Just got busy yesterday and wanted to do a final look through. Please review when ready.

@KLMatlock KLMatlock marked this pull request as ready for review October 25, 2024 15:35
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 picking this up; overall this looks close to merge. While I have added a bunch of comments, they're primarily small tweaks where I think the design is relatively clear 😄

Comment on lines 71 to 74
#[inline]
pub unsafe fn PyDictProxy_Check(op: *mut PyObject) -> c_int {
PyObject_TypeCheck(op, std::ptr::addr_of_mut!(PyDictProxy_Type))
}
Copy link
Member

Choose a reason for hiding this comment

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

This function doesn't exist in https://github.com/python/cpython/blob/3.13/Include/descrobject.h (indeed I couldn't find it anywhere). So we shouldn't add this.

Suggested change
#[inline]
pub unsafe fn PyDictProxy_Check(op: *mut PyObject) -> c_int {
PyObject_TypeCheck(op, std::ptr::addr_of_mut!(PyDictProxy_Type))
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed!

@@ -780,7 +780,7 @@ where
}

/// Represents a tuple which can be used as a PyDict item.
trait PyDictItem<'py> {
pub trait PyDictItem<'py> {
Copy link
Member

Choose a reason for hiding this comment

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

This looks a possibly unintentional change

Suggested change
pub trait PyDictItem<'py> {
trait PyDictItem<'py> {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed! For some reason I thought I needed this for one of the tests.

Comment on lines 18 to 19
#checkfunction=ffi::PyDictProxy_Check
);
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 rather than the ffi function (as discussed, it doesn't seem to exist in CPython), we should just have fn PyDictProxy_Check defined in this file.

Suggested change
#checkfunction=ffi::PyDictProxy_Check
);
#checkfunction=PyDictProxy_Check
);
#[inline]
unsafe fn PyDictProxy_Check(op: *mut PyObject) -> c_int {
ffi::PyObject_TypeCheck(op, std::ptr::addr_of_mut!(PyDictProxy_Type))
}

Copy link
Member

Choose a reason for hiding this comment

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

Also, it looks like subclassing mappingproxy is not allowed so we can probably just do Py_IS_TYPE here.

>>> class F(types.MappingProxyType):
...     pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: type 'mappingproxy' is not an acceptable base type

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, I changed the check function to the following:

#[inline]
unsafe fn dict_proxy_check(op: *mut ffi::PyObject) -> c_int {
    ffi::Py_IS_TYPE(op, std::ptr::addr_of_mut!(ffi::PyDictProxy_Type))
}

Comment on lines 68 to 71
fn copy(&self) -> PyResult<Bound<'_, PyMappingProxy>> {
let res = self.call_method0("copy")?;
unsafe { Ok(res.downcast_into_unchecked::<PyMappingProxy>()) }
}
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 the type here is incorrect, it looks like .copy() returns a copy of the underlying mapping, perhaps?

>>> type(types.MappingProxyType({}).copy())
<class 'dict'>

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 changed this to return PyMapping instead.

Comment on lines 77 to 102
#[inline]
fn keys(&self) -> PyResult<Bound<'py, PySequence>> {
unsafe {
Ok(ffi::PyMapping_Keys(self.as_ptr())
.assume_owned_or_err(self.py())?
.downcast_into_unchecked())
}
}

#[inline]
fn values(&self) -> PyResult<Bound<'py, PySequence>> {
unsafe {
Ok(ffi::PyMapping_Values(self.as_ptr())
.assume_owned_or_err(self.py())?
.downcast_into_unchecked())
}
}

#[inline]
fn items(&self) -> PyResult<Bound<'py, PySequence>> {
unsafe {
Ok(ffi::PyMapping_Items(self.as_ptr())
.assume_owned_or_err(self.py())?
.downcast_into_unchecked())
}
}
Copy link
Member

Choose a reason for hiding this comment

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

It looks like since 3.7 (our minimum supported version) these FFI functions are now guaranteed to return lists: https://docs.python.org/3/c-api/mapping.html#c.PyMapping_Keys

So I think we can use PyList as the return type. It would allow for performance gains versus going through the sequence abstraction.

Similarly, would you be willing to update PyMappingMethods in the same way (maybe as a separate PR)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done! Made the changes and opened #4661

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 open to considering the conversion changes in this file, but these changes might break existing users' code silently. I'm unsure both if it's worth the churn and also what the performance cost might be.

If we move ahead with these we should also include the corresponding changes to the index map and hash brown conversions.

At a minimum I would at like to see these changes split off into a separate PR, although I will be honest that there is a chance we might decide not to have these. Was it critical for your use case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Went ahead and removed the conversion changes. It isn't necessary for me, I just had it in there because of the previous PR.

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.

This looks great to me, thanks very much for helping get this one finally over the line!

@davidhewitt davidhewitt added this pull request to the merge queue Oct 29, 2024
Merged via the queue into PyO3:main with commit 0aa13c8 Oct 29, 2024
44 checks passed
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.

Add support for mappingproxy? (standard library read-only dict)
2 participants