From a7307dbaac2c6fb410caac6b9d9e8ecd40868c7f Mon Sep 17 00:00:00 2001 From: Ivan Krivosheev Date: Tue, 28 Jun 2022 22:53:10 +0300 Subject: [PATCH 1/9] Add super object --- src/types/any.rs | 9 +++++++- src/types/mod.rs | 2 ++ src/types/pysuper.rs | 36 +++++++++++++++++++++++++++++++ tests/test_super.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 src/types/pysuper.rs create mode 100644 tests/test_super.rs diff --git a/src/types/any.rs b/src/types/any.rs index bdb7a9320e7..3ce9116a788 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -3,7 +3,7 @@ use crate::conversion::{AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyTryF use crate::err::{PyDowncastError, PyErr, PyResult}; use crate::exceptions::PyTypeError; use crate::type_object::PyTypeInfo; -use crate::types::{PyDict, PyIterator, PyList, PyString, PyTuple, PyType}; +use crate::types::{PyDict, PyIterator, PyList, PyString, PySuper, PyTuple, PyType}; use crate::{err, ffi, Py, PyNativeType, PyObject, Python}; use std::cell::UnsafeCell; use std::cmp::Ordering; @@ -883,6 +883,13 @@ impl PyAny { pub fn py(&self) -> Python<'_> { PyNativeType::py(self) } + + /// Return a proxy object that delegates method calls to a parent or sibling class of type. + /// + /// This is equivalent to the Python expression `super()` + pub fn py_super(&self) -> PyResult<&PySuper> { + PySuper::new(self.py(), self.get_type(), self) + } } #[cfg(test)] diff --git a/src/types/mod.rs b/src/types/mod.rs index e4f72577db6..1b9d3e79cec 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -27,6 +27,7 @@ pub use self::mapping::PyMapping; pub use self::module::PyModule; pub use self::num::PyLong; pub use self::num::PyLong as PyInt; +pub use self::pysuper::PySuper; pub use self::sequence::PySequence; pub use self::set::PySet; pub use self::slice::{PySlice, PySliceIndices}; @@ -277,6 +278,7 @@ mod list; mod mapping; mod module; mod num; +mod pysuper; mod sequence; mod set; mod slice; diff --git a/src/types/pysuper.rs b/src/types/pysuper.rs new file mode 100644 index 00000000000..d30de72f6af --- /dev/null +++ b/src/types/pysuper.rs @@ -0,0 +1,36 @@ +use crate::exceptions::PyRuntimeError; +use crate::ffi; +use crate::type_object::PyTypeInfo; +use crate::types::{PyTuple, PyType}; +use crate::{AsPyPointer, Py, PyAny, PyErr, PyResult, Python}; +use std::ptr; + +/// Represents a Python `super` object. +/// +/// This type is immutable. +#[repr(transparent)] +pub struct PySuper(PyAny); + +pyobject_native_type_core!(PySuper, ffi::PySuper_Type, #checkfunction=ffi::PyType_Check); + +impl PySuper { + pub fn new<'py>(py: Python<'py>, ty: &'py PyType, obj: &'py PyAny) -> PyResult<&'py PySuper> { + let args = PyTuple::new(py, &[ty, obj]); + let type_ = PySuper::type_object_raw(py); + let super_ = unsafe { ffi::PyType_GenericNew(type_, args.as_ptr(), ptr::null_mut()) }; + if super_.is_null() { + return Err(PyRuntimeError::new_err("Could not create super().")); + }; + unsafe { + (*(*super_).ob_type) + .tp_init + .map(|f| f(super_, args.as_ptr(), ptr::null_mut())) + }; + if let Some(exc) = PyErr::take(py) { + return Err(exc); + } + + let super_: PyResult> = unsafe { Py::from_borrowed_ptr_or_err(py, super_) }; + super_.map(|o| o.into_ref(py)) + } +} diff --git a/tests/test_super.rs b/tests/test_super.rs new file mode 100644 index 00000000000..12c97fbc032 --- /dev/null +++ b/tests/test_super.rs @@ -0,0 +1,50 @@ +use pyo3::prelude::*; + +#[pyclass(subclass)] +struct BaseClass { + val1: usize, +} + +#[pymethods] +impl BaseClass { + #[new] + fn new() -> Self { + BaseClass { val1: 10 } + } + + pub fn method1(&self) -> PyResult { + Ok(self.val1) + } +} + +#[pyclass(extends=BaseClass)] +struct SubClass {} + +#[pymethods] +impl SubClass { + #[new] + fn new() -> (Self, BaseClass) { + (SubClass {}, BaseClass::new()) + } + + fn method2<'a>(self_: PyRef, py: Python<'a>) -> PyResult<&'a PyAny> { + let any: Py = self_.into_py(py); + let super_ = any.into_ref(py).py_super()?; + super_.call_method("method1", (), None) + } +} + +#[test] +fn test_call_super_method() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let cls = py.get_type::(); + pyo3::py_run!( + py, + cls, + r#" + obj = cls() + assert obj.method2() == 10 + "# + ) +} From 81fbfadb2f08f3c2518f8083a7d744590fc37233 Mon Sep 17 00:00:00 2001 From: Ivan Krivosheev Date: Tue, 28 Jun 2022 22:54:42 +0300 Subject: [PATCH 2/9] Edit CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d219465fe82..7809147df61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `CompareOp::matches` to easily implement `__richcmp__` as the result of a Rust `std::cmp::Ordering` comparison. [#2460](https://github.com/PyO3/pyo3/pull/2460) - Supprt `#[pyo3(name)]` on enum variants [#2457](https://github.com/PyO3/pyo3/pull/2457) +- Add `PySuper` object [#2049](https://github.com/PyO3/pyo3/issues/2049) ### Changed From 2178b154cdbe3d36a8d8f590da8a181747f73e5e Mon Sep 17 00:00:00 2001 From: Ivan Krivosheev Date: Tue, 28 Jun 2022 23:12:27 +0300 Subject: [PATCH 3/9] Fix clippy warnings --- tests/test_super.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_super.rs b/tests/test_super.rs index 12c97fbc032..fc6d391514b 100644 --- a/tests/test_super.rs +++ b/tests/test_super.rs @@ -12,8 +12,8 @@ impl BaseClass { BaseClass { val1: 10 } } - pub fn method1(&self) -> PyResult { - Ok(self.val1) + pub fn method1(&self) -> usize { + self.val1 } } @@ -27,7 +27,7 @@ impl SubClass { (SubClass {}, BaseClass::new()) } - fn method2<'a>(self_: PyRef, py: Python<'a>) -> PyResult<&'a PyAny> { + fn method2<'a>(self_: PyRef<'_, Self>, py: Python<'a>) -> PyResult<&'a PyAny> { let any: Py = self_.into_py(py); let super_ = any.into_ref(py).py_super()?; super_.call_method("method1", (), None) From 68b02049cc687ecef19180940d7191eb74b8d3d7 Mon Sep 17 00:00:00 2001 From: Ivan Krivosheev Date: Tue, 28 Jun 2022 23:26:43 +0300 Subject: [PATCH 4/9] Rewrite create PySuper using stable API --- src/types/pysuper.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/types/pysuper.rs b/src/types/pysuper.rs index d30de72f6af..15ef7044919 100644 --- a/src/types/pysuper.rs +++ b/src/types/pysuper.rs @@ -1,9 +1,7 @@ -use crate::exceptions::PyRuntimeError; use crate::ffi; use crate::type_object::PyTypeInfo; use crate::types::{PyTuple, PyType}; use crate::{AsPyPointer, Py, PyAny, PyErr, PyResult, Python}; -use std::ptr; /// Represents a Python `super` object. /// @@ -17,15 +15,7 @@ impl PySuper { pub fn new<'py>(py: Python<'py>, ty: &'py PyType, obj: &'py PyAny) -> PyResult<&'py PySuper> { let args = PyTuple::new(py, &[ty, obj]); let type_ = PySuper::type_object_raw(py); - let super_ = unsafe { ffi::PyType_GenericNew(type_, args.as_ptr(), ptr::null_mut()) }; - if super_.is_null() { - return Err(PyRuntimeError::new_err("Could not create super().")); - }; - unsafe { - (*(*super_).ob_type) - .tp_init - .map(|f| f(super_, args.as_ptr(), ptr::null_mut())) - }; + let super_ = unsafe { ffi::PyObject_CallObject(type_ as *mut _, args.as_ptr()) }; if let Some(exc) = PyErr::take(py) { return Err(exc); } From 4255b87c969cae94ac9349abc958f3dff1a5f1e5 Mon Sep 17 00:00:00 2001 From: Ivan Krivosheev Date: Tue, 28 Jun 2022 23:33:53 +0300 Subject: [PATCH 5/9] Fix tests --- tests/test_super.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_super.rs b/tests/test_super.rs index fc6d391514b..f1f9aea3968 100644 --- a/tests/test_super.rs +++ b/tests/test_super.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "macros")] + use pyo3::prelude::*; #[pyclass(subclass)] From edab2a64a379b57562fd7ebecbaee74c0859072b Mon Sep 17 00:00:00 2001 From: Ivan Krivosheev Date: Wed, 29 Jun 2022 14:01:36 +0300 Subject: [PATCH 6/9] Fix after review --- src/types/pysuper.rs | 60 +++++++++++++++++++++++++++++++++++--------- tests/test_super.rs | 24 +++++++++--------- 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/types/pysuper.rs b/src/types/pysuper.rs index 15ef7044919..4f34d87afee 100644 --- a/src/types/pysuper.rs +++ b/src/types/pysuper.rs @@ -1,7 +1,7 @@ use crate::ffi; use crate::type_object::PyTypeInfo; -use crate::types::{PyTuple, PyType}; -use crate::{AsPyPointer, Py, PyAny, PyErr, PyResult, Python}; +use crate::types::PyType; +use crate::{PyAny, PyResult, Python}; /// Represents a Python `super` object. /// @@ -9,18 +9,54 @@ use crate::{AsPyPointer, Py, PyAny, PyErr, PyResult, Python}; #[repr(transparent)] pub struct PySuper(PyAny); -pyobject_native_type_core!(PySuper, ffi::PySuper_Type, #checkfunction=ffi::PyType_Check); +pyobject_native_type_core!(PySuper, ffi::PySuper_Type); impl PySuper { + /// Constructs a new super object. More read about super object: [docs](https://docs.python.org/3/library/functions.html#super) + /// + /// # Examples + /// + /// ```rust + /// use pyo3::prelude::*; + /// use pyo3::types::PySuper; + /// + ///#[pyclass(subclass)] + /// struct BaseClass { + /// val1: usize, + /// } + /// + /// #[pymethods] + /// impl BaseClass { + /// #[new] + /// fn new() -> Self { + /// BaseClass { val1: 10 } + /// } + /// + /// pub fn method(&self) -> usize { + /// self.val1 + /// } + /// } + /// + /// #[pyclass(extends=BaseClass)] + /// struct SubClass {} + /// + /// #[pymethods] + /// impl SubClass { + /// #[new] + /// fn new() -> (Self, BaseClass) { + /// (SubClass {}, BaseClass::new()) + /// } + /// + /// fn method<'a>(self_: PyRef<'_, Self>, py: Python<'a>) -> PyResult<&'a PyAny> { + /// let any: Py = self_.into_py(py); + /// let super_ = any.into_ref(py).py_super()?; + /// super_.call_method("method", (), None) + /// } + /// } + /// ``` pub fn new<'py>(py: Python<'py>, ty: &'py PyType, obj: &'py PyAny) -> PyResult<&'py PySuper> { - let args = PyTuple::new(py, &[ty, obj]); - let type_ = PySuper::type_object_raw(py); - let super_ = unsafe { ffi::PyObject_CallObject(type_ as *mut _, args.as_ptr()) }; - if let Some(exc) = PyErr::take(py) { - return Err(exc); - } - - let super_: PyResult> = unsafe { Py::from_borrowed_ptr_or_err(py, super_) }; - super_.map(|o| o.into_ref(py)) + let super_ = PySuper::type_object(py).call1((ty, obj))?; + let super_ = super_.downcast::()?; + Ok(super_) } } diff --git a/tests/test_super.rs b/tests/test_super.rs index f1f9aea3968..dcc66f0be3e 100644 --- a/tests/test_super.rs +++ b/tests/test_super.rs @@ -14,7 +14,7 @@ impl BaseClass { BaseClass { val1: 10 } } - pub fn method1(&self) -> usize { + pub fn method(&self) -> usize { self.val1 } } @@ -29,24 +29,24 @@ impl SubClass { (SubClass {}, BaseClass::new()) } - fn method2<'a>(self_: PyRef<'_, Self>, py: Python<'a>) -> PyResult<&'a PyAny> { + fn method<'a>(self_: PyRef<'_, Self>, py: Python<'a>) -> PyResult<&'a PyAny> { let any: Py = self_.into_py(py); let super_ = any.into_ref(py).py_super()?; - super_.call_method("method1", (), None) + super_.call_method("method", (), None) } } #[test] fn test_call_super_method() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let cls = py.get_type::(); - pyo3::py_run!( - py, - cls, - r#" + Python::with_gil(|py| { + let cls = py.get_type::(); + pyo3::py_run!( + py, + cls, + r#" obj = cls() - assert obj.method2() == 10 + assert obj.method() == 10 "# - ) + ) + }); } From f497b60b990647268ae4bd90d76b4f8d4d638369 Mon Sep 17 00:00:00 2001 From: Ivan Krivosheev Date: Wed, 29 Jun 2022 14:14:18 +0300 Subject: [PATCH 7/9] Remove unused import --- src/types/pysuper.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types/pysuper.rs b/src/types/pysuper.rs index 4f34d87afee..9dd6e0418a8 100644 --- a/src/types/pysuper.rs +++ b/src/types/pysuper.rs @@ -18,7 +18,6 @@ impl PySuper { /// /// ```rust /// use pyo3::prelude::*; - /// use pyo3::types::PySuper; /// ///#[pyclass(subclass)] /// struct BaseClass { From 73d819ad64d2bd04c4dc7661cc31116c8e2b258a Mon Sep 17 00:00:00 2001 From: Ivan Krivosheev Date: Sun, 3 Jul 2022 19:56:39 +0300 Subject: [PATCH 8/9] Fix PyPy build, fix test --- src/types/any.rs | 2 +- src/types/mod.rs | 2 ++ src/types/pysuper.rs | 10 +++++----- tests/test_super.rs | 7 +++---- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/types/any.rs b/src/types/any.rs index 3ce9116a788..70398977ac6 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -888,7 +888,7 @@ impl PyAny { /// /// This is equivalent to the Python expression `super()` pub fn py_super(&self) -> PyResult<&PySuper> { - PySuper::new(self.py(), self.get_type(), self) + PySuper::new(self.get_type(), self) } } diff --git a/src/types/mod.rs b/src/types/mod.rs index 1b9d3e79cec..36530ef2642 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -27,6 +27,7 @@ pub use self::mapping::PyMapping; pub use self::module::PyModule; pub use self::num::PyLong; pub use self::num::PyLong as PyInt; +#[cfg(not(PyPy))] pub use self::pysuper::PySuper; pub use self::sequence::PySequence; pub use self::set::PySet; @@ -278,6 +279,7 @@ mod list; mod mapping; mod module; mod num; +#[cfg(not(PyPy))] mod pysuper; mod sequence; mod set; diff --git a/src/types/pysuper.rs b/src/types/pysuper.rs index 9dd6e0418a8..c84e64cde30 100644 --- a/src/types/pysuper.rs +++ b/src/types/pysuper.rs @@ -1,7 +1,7 @@ use crate::ffi; use crate::type_object::PyTypeInfo; use crate::types::PyType; -use crate::{PyAny, PyResult, Python}; +use crate::{PyAny, PyResult}; /// Represents a Python `super` object. /// @@ -46,14 +46,14 @@ impl PySuper { /// (SubClass {}, BaseClass::new()) /// } /// - /// fn method<'a>(self_: PyRef<'_, Self>, py: Python<'a>) -> PyResult<&'a PyAny> { - /// let any: Py = self_.into_py(py); - /// let super_ = any.into_ref(py).py_super()?; + /// fn method(self_: &PyCell) -> PyResult<&PyAny> { + /// let super_ = self_.py_super()?; /// super_.call_method("method", (), None) /// } /// } /// ``` - pub fn new<'py>(py: Python<'py>, ty: &'py PyType, obj: &'py PyAny) -> PyResult<&'py PySuper> { + pub fn new<'py>(ty: &'py PyType, obj: &'py PyAny) -> PyResult<&'py PySuper> { + let py = ty.py(); let super_ = PySuper::type_object(py).call1((ty, obj))?; let super_ = super_.downcast::()?; Ok(super_) diff --git a/tests/test_super.rs b/tests/test_super.rs index dcc66f0be3e..62cb5c1b6f0 100644 --- a/tests/test_super.rs +++ b/tests/test_super.rs @@ -1,4 +1,4 @@ -#![cfg(feature = "macros")] +#![cfg(all(feature = "macros", not(PyPy)))] use pyo3::prelude::*; @@ -29,9 +29,8 @@ impl SubClass { (SubClass {}, BaseClass::new()) } - fn method<'a>(self_: PyRef<'_, Self>, py: Python<'a>) -> PyResult<&'a PyAny> { - let any: Py = self_.into_py(py); - let super_ = any.into_ref(py).py_super()?; + fn method(self_: &PyCell) -> PyResult<&PyAny> { + let super_ = self_.py_super()?; super_.call_method("method", (), None) } } From 7cb6a498ddbcafa9f846d3b596369cc7daeed458 Mon Sep 17 00:00:00 2001 From: Ivan Krivosheev Date: Sun, 3 Jul 2022 20:22:34 +0300 Subject: [PATCH 9/9] Fix PyAny build for PyPy --- src/types/any.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/types/any.rs b/src/types/any.rs index 70398977ac6..b4cb99857bf 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -3,7 +3,9 @@ use crate::conversion::{AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyTryF use crate::err::{PyDowncastError, PyErr, PyResult}; use crate::exceptions::PyTypeError; use crate::type_object::PyTypeInfo; -use crate::types::{PyDict, PyIterator, PyList, PyString, PySuper, PyTuple, PyType}; +#[cfg(not(PyPy))] +use crate::types::PySuper; +use crate::types::{PyDict, PyIterator, PyList, PyString, PyTuple, PyType}; use crate::{err, ffi, Py, PyNativeType, PyObject, Python}; use std::cell::UnsafeCell; use std::cmp::Ordering; @@ -887,6 +889,7 @@ impl PyAny { /// Return a proxy object that delegates method calls to a parent or sibling class of type. /// /// This is equivalent to the Python expression `super()` + #[cfg(not(PyPy))] pub fn py_super(&self) -> PyResult<&PySuper> { PySuper::new(self.get_type(), self) }