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 diff --git a/src/types/any.rs b/src/types/any.rs index bdb7a9320e7..b4cb99857bf 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -3,6 +3,8 @@ use crate::conversion::{AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyTryF use crate::err::{PyDowncastError, PyErr, PyResult}; use crate::exceptions::PyTypeError; use crate::type_object::PyTypeInfo; +#[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; @@ -883,6 +885,14 @@ 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()` + #[cfg(not(PyPy))] + pub fn py_super(&self) -> PyResult<&PySuper> { + PySuper::new(self.get_type(), self) + } } #[cfg(test)] diff --git a/src/types/mod.rs b/src/types/mod.rs index e4f72577db6..36530ef2642 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -27,6 +27,8 @@ 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; pub use self::slice::{PySlice, PySliceIndices}; @@ -277,6 +279,8 @@ mod list; mod mapping; mod module; mod num; +#[cfg(not(PyPy))] +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..c84e64cde30 --- /dev/null +++ b/src/types/pysuper.rs @@ -0,0 +1,61 @@ +use crate::ffi; +use crate::type_object::PyTypeInfo; +use crate::types::PyType; +use crate::{PyAny, PyResult}; + +/// Represents a Python `super` object. +/// +/// This type is immutable. +#[repr(transparent)] +pub struct PySuper(PyAny); + +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::*; + /// + ///#[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(self_: &PyCell) -> PyResult<&PyAny> { + /// let super_ = self_.py_super()?; + /// super_.call_method("method", (), None) + /// } + /// } + /// ``` + 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 new file mode 100644 index 00000000000..62cb5c1b6f0 --- /dev/null +++ b/tests/test_super.rs @@ -0,0 +1,51 @@ +#![cfg(all(feature = "macros", not(PyPy)))] + +use pyo3::prelude::*; + +#[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(self_: &PyCell) -> PyResult<&PyAny> { + let super_ = self_.py_super()?; + super_.call_method("method", (), None) + } +} + +#[test] +fn test_call_super_method() { + Python::with_gil(|py| { + let cls = py.get_type::(); + pyo3::py_run!( + py, + cls, + r#" + obj = cls() + assert obj.method() == 10 + "# + ) + }); +}