diff --git a/CHANGELOG.md b/CHANGELOG.md index 0776c04a21e..3d29e1ed7a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,11 +15,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. * When the GIL is not held, the refcount is now decreased when the GIL is next acquired. (Previously would wait until next time the GIL was released.) * `FromPyObject` for `Py` now works for a wider range of `T`, in particular for `T: PyClass`. [#880](https://github.com/PyO3/pyo3/pull/880) * Some functions such as `PyList::get_item` which return borrowed objects at the C ffi layer now return owned objects at the PyO3 layer, for safety reasons. [#890](https://github.com/PyO3/pyo3/pull/890) +* The trait `ObjectProtocol` has been removed, and all the methods from the trait have been moved to `PyAny`. [#911](https://github.com/PyO3/pyo3/pull/911) + * The exception to this is `ObjectProtocol::None`, which has simply been removed. Use `Python::None` instead. ### Added * `_PyDict_NewPresized`. [#849](https://github.com/PyO3/pyo3/pull/849) * `IntoPy` for `HashSet` and `BTreeSet`. [#864](https://github.com/PyO3/pyo3/pull/864) -* `ObjectProtocol::dir`. [#886](https://github.com/PyO3/pyo3/pull/886) +* `PyAny::dir`. [#886](https://github.com/PyO3/pyo3/pull/886) +* All builtin types (`PyList`, `PyTuple`, `PyDict`) etc. now implement `Deref`. [#911](https://github.com/PyO3/pyo3/pull/911) +* `PyCell` now implements `Deref`. [#911](https://github.com/PyO3/pyo3/pull/911) ### Fixed * `__radd__` and other `__r*__` methods now correctly work with operators. [#839](https://github.com/PyO3/pyo3/pull/839) @@ -31,6 +35,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Removed * `PyMethodsProtocol` is now renamed to `PyMethodsImpl` and hidden. [#889](https://github.com/PyO3/pyo3/pull/889) * `num-traits` is no longer a dependency. [#895](https://github.com/PyO3/pyo3/pull/895) +* `ObjectProtocol`. [#911](https://github.com/PyO3/pyo3/pull/911) ## [0.9.2] diff --git a/guide/src/conversions.md b/guide/src/conversions.md index 80560052f83..2d49f780482 100644 --- a/guide/src/conversions.md +++ b/guide/src/conversions.md @@ -34,7 +34,7 @@ same purpose, except that it consumes `self`. ## `*args` and `**kwargs` for Python object calls There are several ways how to pass positional and keyword arguments to a Python object call. -The [`ObjectProtocol`] trait provides two methods: +[`PyAny`] provides two methods: * `call` - call any callable Python object. * `call_method` - call a specific method on the object, shorthand for `get_attr` then `call`. @@ -137,7 +137,7 @@ Eventually, traits such as [`ToPyObject`] will be replaced by this trait and a [ [`ToPyObject`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.ToPyObject.html [`PyObject`]: https://docs.rs/pyo3/latest/pyo3/struct.PyObject.html [`PyTuple`]: https://docs.rs/pyo3/latest/pyo3/types/struct.PyTuple.html -[`ObjectProtocol`]: https://docs.rs/pyo3/latest/pyo3/trait.ObjectProtocol.html +[`PyAny`]: https://docs.rs/pyo3/latest/pyo3/struct.PyAny.html [`IntoPyDict`]: https://docs.rs/pyo3/latest/pyo3/types/trait.IntoPyDict.html [`PyRef`]: https://pyo3.rs/master/doc/pyo3/pycell/struct.PyRef.html diff --git a/guide/src/function.md b/guide/src/function.md index 1ae7bf0cb0f..110504b0a46 100644 --- a/guide/src/function.md +++ b/guide/src/function.md @@ -177,15 +177,15 @@ Currently, there are no conversions between `Fn`s in Rust and callables in Pytho ### Calling Python functions in Rust -You can use [`ObjectProtocol::is_callable`] to check if you have a callable object. `is_callable` will return `true` for functions (including lambdas), methods and objects with a `__call__` method. You can call the object with [`ObjectProtocol::call`] with the args as first parameter and the kwargs (or `None`) as second parameter. There are also [`ObjectProtocol::call0`] with no args and [`ObjectProtocol::call1`] with only positional args. +You can use [`PyAny::is_callable`] to check if you have a callable object. `is_callable` will return `true` for functions (including lambdas), methods and objects with a `__call__` method. You can call the object with [`PyAny::call`] with the args as first parameter and the kwargs (or `None`) as second parameter. There are also [`PyAny::call0`] with no args and [`PyAny::call1`] with only positional args. ### Calling Rust functions in Python If you have a static function, you can expose it with `#[pyfunction]` and use [`wrap_pyfunction!`] to get the corresponding [`PyObject`]. For dynamic functions, e.g. lambdas and functions that were passed as arguments, you must put them in some kind of owned container, e.g. a `Box`. (A long-term solution will be a special container similar to wasm-bindgen's `Closure`). You can then use a `#[pyclass]` struct with that container as a field as a way to pass the function over the FFI barrier. You can even make that class callable with `__call__` so it looks like a function in Python code. -[`ObjectProtocol::is_callable`]: https://docs.rs/pyo3/latest/pyo3/trait.ObjectProtocol.html#tymethod.is_callable -[`ObjectProtocol::call`]: https://docs.rs/pyo3/latest/pyo3/trait.ObjectProtocol.html#tymethod.call -[`ObjectProtocol::call0`]: https://docs.rs/pyo3/latest/pyo3/trait.ObjectProtocol.html#tymethod.call0 -[`ObjectProtocol::call1`]: https://docs.rs/pyo3/latest/pyo3/trait.ObjectProtocol.html#tymethod.call1 +[`PyAny::is_callable`]: https://docs.rs/pyo3/latest/pyo3/struct.PyAny.html#tymethod.is_callable +[`PyAny::call`]: https://docs.rs/pyo3/latest/pyo3/struct.PyAny.html#tymethod.call +[`PyAny::call0`]: https://docs.rs/pyo3/latest/pyo3/struct.PyAny.html#tymethod.call0 +[`PyAny::call1`]: https://docs.rs/pyo3/latest/pyo3/struct.PyAny.html#tymethod.call1 [`PyObject`]: https://docs.rs/pyo3/latest/pyo3/struct.PyObject.html [`wrap_pyfunction!`]: https://docs.rs/pyo3/latest/pyo3/macro.wrap_pyfunction.html diff --git a/guide/src/types.md b/guide/src/types.md index 97a48e442d6..d510a3bfb22 100644 --- a/guide/src/types.md +++ b/guide/src/types.md @@ -46,6 +46,79 @@ references is done at runtime using `PyCell`, a scheme very similar to ## Object types +### [`PyAny`] + +**Represents:** a Python object of unspecified type, restricted to a GIL +lifetime. Currently, `PyAny` can only ever occur as a reference, `&PyAny`. + +**Used:** Whenever you want to refer to some Python object and will have the +GIL for the whole duration you need to access that object. For example, +intermediate values and arguments to `pyfunction`s or `pymethod`s implemented +in Rust where any type is allowed. + +Many general methods for interacting with Python objects are on the `PyAny` struct, +such as `getattr`, `setattr`, and `.call`. + +**Conversions:** + +```rust +# use pyo3::prelude::*; +# use pyo3::types::PyList; +# let gil = Python::acquire_gil(); +# let py = gil.python(); +let obj: &PyAny = PyList::empty(py); + +// Convert to &ConcreteType using PyAny::downcast +let _: &PyList = obj.downcast().unwrap(); + +// Convert to PyObject using .into() or .to_object(py) +let _: PyObject = obj.into(); + +// Convert to Py using .into() or Py::from +let _: Py = obj.into(); + +// Convert to Py using PyAny::extract +let _: Py = obj.extract().unwrap(); +``` + + +### `PyTuple`, `PyDict`, and many more + +**Represents:** a native Python object of known type, restricted to a GIL +lifetime just like `PyAny`. + +**Used:** Whenever you want to operate with native Python types while holding +the GIL. Like `PyAny`, this is the most convenient form to use for function +arguments and intermediate values. + +These types all implement `Deref`, so they all expose the same +methods which can be found on `PyAny`. + +**Conversions:** + +```rust +# use pyo3::prelude::*; +# use pyo3::types::PyList; +# let gil = Python::acquire_gil(); +# let py = gil.python(); +let list = PyList::empty(py); + +// Can use methods from PyAny on all Python types due to Deref implementation +let _ = list.repr(); + +// Rust will convert &PyList etc. to &PyAny automatically due to Deref implementation +let _: &PyAny = list; + +// For more explicit &PyAny conversion, use .as_ref() +let _: &PyAny = list.as_ref(); + +// To convert to PyObject use .into() or .to_object(py) +let _: PyObject = list.into(); + +// To convert to Py use .into() or Py::from() +let _: Py = list.into(); +``` + ### `PyObject` **Represents:** a GIL independent reference to a Python object of unspecified @@ -60,10 +133,22 @@ Can be cloned using Python reference counts with `.clone_ref()`. **Conversions:** -- To `&PyAny`: `obj.as_ref(py)` -- To `Py`: `obj.as_ref(py).extract::>` -- To `&ConcreteType` (which must be a Python native type): `obj.cast_as(py)` +```rust +# use pyo3::prelude::*; +# use pyo3::types::PyList; +# let gil = Python::acquire_gil(); +# let py = gil.python(); +let obj: PyObject = PyList::empty(py).into(); +// Convert to &PyAny using AsPyRef::as_ref +let _: &PyAny = obj.as_ref(py); + +// Convert to &ConcreteType using PyObject::cast_as +let _: &PyList = obj.cast_as(py).unwrap(); + +// Convert to Py using PyObject::extract +let _: Py = obj.extract(py).unwrap(); +``` ### `Py` @@ -75,43 +160,23 @@ implemented in Rust. **Conversions:** -- To `PyObject`: `obj.to_object(py)` -- To `&SomeType` or `&PyCell`: `obj.as_ref(py)`. For `pyclass` types - implemented in Rust, you get a `PyCell` (see below). For Python native types, - mutating operations through PyO3's API don't require `&mut` access. - -**Note:** `PyObject` is semantically equivalent to `Py` and might be -merged with it in the future. - - -### `PyAny` - -**Represents:** a Python object of unspecified type, restricted to a GIL -lifetime. Currently, `PyAny` can only ever occur as a reference, usually -`&PyAny`. - -**Used:** Whenever you want to refer to some Python object only as long as -holding the GIL. For example, intermediate values and arguments to -`pyfunction`s or `pymethod`s implemented in Rust where any type is allowed. - -**Conversions:** - -- To `PyObject`: `obj.to_object(py)` +```rust +# use pyo3::prelude::*; +# use pyo3::types::PyList; +# let gil = Python::acquire_gil(); +# let py = gil.python(); +let list: Py = PyList::empty(py).into(); +// Access the native type using AsPyRef::as_ref(py) +// (For #[pyclass] types, as_ref() will return &PyCell) +let _: &PyList = list.as_ref(py); -### `PyTuple`, `PyDict`, and many more +// Convert to PyObject with .into() +let _: PyObject = list.into(); +``` -**Represents:** a native Python object of known type, restricted to a GIL -lifetime just like `PyAny`. - -**Used:** Whenever you want to operate with native Python types while holding -the GIL. Like `PyAny`, this is the most convenient form to use for function -arguments and intermediate values. - -**Conversions:** - -- To `PyAny`: `obj.as_ref()` -- To `Py`: `Py::from(obj)` +**Note:** `PyObject` is semantically equivalent to `Py` and might be +merged with it in the future. ### `PyCell` @@ -124,10 +189,39 @@ wrapped in a Python object. The cell part is an analog to stdlib's taking `&SomeType` or `&mut SomeType`) while maintaining the aliasing rules of Rust references. +Like pyo3's Python native types, `PyCell` implements `Deref`, +so it also exposes all of the methods on `PyAny`. + **Conversions:** -- From `PyAny`: `.downcast()` +```rust +# use pyo3::prelude::*; +# use pyo3::types::PyList; +# #[pyclass] struct MyClass { } +# let gil = Python::acquire_gil(); +# let py = gil.python(); +let cell: &PyCell = PyCell::new(py, MyClass { }).unwrap(); + +// Obtain PyRef with .try_borrow() +let pr: PyRef = cell.try_borrow().unwrap(); +# drop(pr); + +// Obtain PyRefMut with .try_borrow_mut() +let prm: PyRefMut = cell.try_borrow_mut().unwrap(); +# drop(prm); + +// Can use methods from PyAny on PyCell due to Deref implementation +let _ = cell.repr(); + +// Rust will convert &PyCell to &PyAny automatically due to Deref implementation +let _: &PyAny = cell; + +// For more explicit &PyAny conversion, use .as_ref() +let any: &PyAny = cell.as_ref(); +// To obtain a PyCell from PyAny, use PyAny::downcast +let _: &PyCell = any.downcast().unwrap(); +``` ### `PyRef` and `PyRefMut` diff --git a/pyo3-derive-backend/src/pymethod.rs b/pyo3-derive-backend/src/pymethod.rs index 64bf7b9a02a..ac0551bc190 100644 --- a/pyo3-derive-backend/src/pymethod.rs +++ b/pyo3-derive-backend/src/pymethod.rs @@ -414,7 +414,6 @@ pub fn impl_arg_params(spec: &FnSpec<'_>, body: TokenStream) -> TokenStream { let num_normal_params = params.len(); // create array of arguments, and then parse quote! {{ - use pyo3::ObjectProtocol; const PARAMS: &'static [pyo3::derive_utils::ParamDescription] = &[ #(#params),* ]; diff --git a/src/buffer.rs b/src/buffer.rs index cf9b5c1c135..84737e0ccb1 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -661,9 +661,6 @@ mod test { use crate::ffi; use crate::Python; - #[allow(unused_imports)] - use crate::objectprotocol::ObjectProtocol; - #[test] fn test_compatible_size() { // for the cast in PyBuffer::shape() diff --git a/src/class/basic.rs b/src/class/basic.rs index d9996e6e4d9..8d13ab494df 100644 --- a/src/class/basic.rs +++ b/src/class/basic.rs @@ -11,8 +11,7 @@ use crate::callback::HashCallbackOutput; use crate::class::methods::PyMethodDef; use crate::{ - exceptions, ffi, FromPyObject, IntoPy, ObjectProtocol, PyAny, PyCell, PyClass, PyErr, PyObject, - PyResult, + exceptions, ffi, FromPyObject, IntoPy, PyAny, PyCell, PyClass, PyErr, PyObject, PyResult, }; use std::os::raw::c_int; diff --git a/src/class/macros.rs b/src/class/macros.rs index 7d5b053994b..54d82779d98 100644 --- a/src/class/macros.rs +++ b/src/class/macros.rs @@ -66,7 +66,6 @@ macro_rules! py_binary_func { where T: for<'p> $trait<'p>, { - use $crate::ObjectProtocol; $crate::callback_body!(py, { let slf = py.from_borrowed_ptr::<$crate::PyCell>(slf); let arg = py.from_borrowed_ptr::<$crate::PyAny>(arg); @@ -94,7 +93,6 @@ macro_rules! py_binary_num_func { where T: for<'p> $trait<'p>, { - use $crate::ObjectProtocol; $crate::callback_body!(py, { let lhs = py.from_borrowed_ptr::<$crate::PyAny>(lhs); let rhs = py.from_borrowed_ptr::<$crate::PyAny>(rhs); @@ -117,7 +115,6 @@ macro_rules! py_binary_reverse_num_func { where T: for<'p> $trait<'p>, { - use $crate::ObjectProtocol; $crate::callback_body!(py, { // Swap lhs <-> rhs let slf = py.from_borrowed_ptr::<$crate::PyCell>(rhs); @@ -142,7 +139,6 @@ macro_rules! py_binary_self_func { where T: for<'p> $trait<'p>, { - use $crate::ObjectProtocol; $crate::callback_body!(py, { let slf_ = py.from_borrowed_ptr::<$crate::PyCell>(slf); let arg = py.from_borrowed_ptr::<$crate::PyAny>(arg); @@ -191,7 +187,6 @@ macro_rules! py_ternary_func { where T: for<'p> $trait<'p>, { - use $crate::ObjectProtocol; $crate::callback_body!(py, { let slf = py.from_borrowed_ptr::<$crate::PyCell>(slf); let arg1 = py @@ -224,7 +219,6 @@ macro_rules! py_ternary_num_func { where T: for<'p> $trait<'p>, { - use $crate::ObjectProtocol; $crate::callback_body!(py, { let arg1 = py .from_borrowed_ptr::<$crate::types::PyAny>(arg1) @@ -256,7 +250,6 @@ macro_rules! py_ternary_reverse_num_func { where T: for<'p> $trait<'p>, { - use $crate::ObjectProtocol; $crate::callback_body!(py, { // Swap lhs <-> rhs let slf = py.from_borrowed_ptr::<$crate::PyCell>(arg2); @@ -284,7 +277,6 @@ macro_rules! py_dummy_ternary_self_func { where T: for<'p> $trait<'p>, { - use $crate::ObjectProtocol; $crate::callback_body!(py, { let slf_cell = py.from_borrowed_ptr::<$crate::PyCell>(slf); let arg1 = py.from_borrowed_ptr::<$crate::PyAny>(arg1); @@ -307,7 +299,6 @@ macro_rules! py_func_set { where T: for<'p> $trait_name<'p>, { - use $crate::ObjectProtocol; $crate::callback_body!(py, { let slf = py.from_borrowed_ptr::<$crate::PyCell<$generic>>(slf); @@ -340,7 +331,6 @@ macro_rules! py_func_del { where U: for<'p> $trait_name<'p>, { - use $crate::ObjectProtocol; $crate::callback_body!(py, { if value.is_null() { let slf = py.from_borrowed_ptr::<$crate::PyCell>(slf); @@ -370,7 +360,6 @@ macro_rules! py_func_set_del { where T: for<'p> $trait1<'p> + for<'p> $trait2<'p>, { - use $crate::ObjectProtocol; $crate::callback_body!(py, { let slf = py.from_borrowed_ptr::<$crate::PyCell<$generic>>(slf); let name = py.from_borrowed_ptr::<$crate::PyAny>(name); diff --git a/src/class/sequence.rs b/src/class/sequence.rs index ba8f0d2b114..ad88032c968 100644 --- a/src/class/sequence.rs +++ b/src/class/sequence.rs @@ -5,7 +5,6 @@ use crate::conversion::{FromPyObject, IntoPy}; use crate::err::{PyErr, PyResult}; -use crate::objectprotocol::ObjectProtocol; use crate::{exceptions, ffi, PyAny, PyCell, PyClass, PyObject}; use std::os::raw::c_int; diff --git a/src/err.rs b/src/err.rs index b93ff8ab55b..433396281a6 100644 --- a/src/err.rs +++ b/src/err.rs @@ -5,8 +5,8 @@ use crate::type_object::PyTypeObject; use crate::types::PyType; use crate::{exceptions, ffi}; use crate::{ - AsPyPointer, FromPy, FromPyPointer, IntoPy, IntoPyPointer, ObjectProtocol, Py, PyAny, PyObject, - Python, ToBorrowedObject, ToPyObject, + AsPyPointer, FromPy, FromPyPointer, IntoPy, IntoPyPointer, Py, PyAny, PyObject, Python, + ToBorrowedObject, ToPyObject, }; use libc::c_int; use std::ffi::CString; diff --git a/src/exceptions.rs b/src/exceptions.rs index 4d7d476840d..854696144bc 100644 --- a/src/exceptions.rs +++ b/src/exceptions.rs @@ -354,7 +354,6 @@ pub mod socket { #[cfg(test)] mod test { use crate::exceptions::Exception; - use crate::objectprotocol::ObjectProtocol; use crate::types::{IntoPyDict, PyDict}; use crate::{PyErr, Python}; diff --git a/src/instance.rs b/src/instance.rs index 37080feeda7..f0925eb0b41 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -2,7 +2,6 @@ use crate::err::{PyErr, PyResult}; use crate::gil; use crate::object::PyObject; -use crate::objectprotocol::ObjectProtocol; use crate::type_object::PyBorrowFlagLayout; use crate::{ ffi, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyAny, PyCell, PyClass, @@ -148,7 +147,6 @@ impl Py { /// `PyObject::as_ref` returns `&PyAny`. /// ``` /// # use pyo3::prelude::*; -/// use pyo3::ObjectProtocol; /// let obj: PyObject = { /// let gil = Python::acquire_gil(); /// let py = gil.python(); @@ -156,14 +154,13 @@ impl Py { /// }; /// let gil = Python::acquire_gil(); /// let py = gil.python(); -/// assert_eq!(obj.as_ref(py).len().unwrap(), 0); // PyAny implements ObjectProtocol +/// assert_eq!(obj.as_ref(py).len().unwrap(), 0); /// ``` /// /// `Py::as_ref` returns `&PyDict`, `&PyList` or so for native types, and `&PyCell` /// for `#[pyclass]`. /// ``` /// # use pyo3::prelude::*; -/// use pyo3::ObjectProtocol; /// let obj: PyObject = { /// let gil = Python::acquire_gil(); /// let py = gil.python(); @@ -171,7 +168,7 @@ impl Py { /// }; /// let gil = Python::acquire_gil(); /// let py = gil.python(); -/// assert_eq!(obj.as_ref(py).len().unwrap(), 0); // PyAny implements ObjectProtocol +/// assert_eq!(obj.as_ref(py).len().unwrap(), 0); /// ``` pub trait AsPyRef: Sized { type Target; diff --git a/src/lib.rs b/src/lib.rs index bd6b32285ca..89e728e29cf 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,7 +142,6 @@ pub use crate::err::{PyDowncastError, PyErr, PyErrArguments, PyErrValue, PyResul pub use crate::gil::{GILGuard, GILPool}; pub use crate::instance::{AsPyRef, ManagedPyRef, Py, PyNativeType}; pub use crate::object::PyObject; -pub use crate::objectprotocol::ObjectProtocol; pub use crate::pycell::{PyCell, PyRef, PyRefMut}; pub use crate::pyclass::PyClass; pub use crate::pyclass_init::PyClassInitializer; @@ -187,7 +186,6 @@ mod instance; mod internal_tricks; pub mod marshal; mod object; -mod objectprotocol; pub mod panic; pub mod prelude; pub mod pycell; @@ -338,6 +336,7 @@ pub mod doc_test { doctest!("../guide/src/exception.md", guide_exception_md); doctest!("../guide/src/function.md", guide_function_md); doctest!("../guide/src/get_started.md", guide_get_started_md); + doctest!("../guide/src/migration.md", guide_migration_md); doctest!("../guide/src/module.md", guide_module_md); doctest!( "../guide/src/python_from_rust.md", @@ -346,5 +345,5 @@ pub mod doc_test { doctest!("../guide/src/parallelism.md", guide_parallelism_md); doctest!("../guide/src/pypy.md", guide_pypy_md); doctest!("../guide/src/rust_cpython.md", guide_rust_cpython_md); - doctest!("../guide/src/migration.md", guide_migration_md); + doctest!("../guide/src/types.md", guide_types_md); } diff --git a/src/objectprotocol.rs b/src/objectprotocol.rs deleted file mode 100644 index 2385f91b68d..00000000000 --- a/src/objectprotocol.rs +++ /dev/null @@ -1,573 +0,0 @@ -// Copyright (c) 2017-present PyO3 Project and Contributors - -use crate::class::basic::CompareOp; -use crate::err::{self, PyDowncastError, PyErr, PyResult}; -use crate::exceptions::TypeError; -use crate::types::{PyAny, PyDict, PyIterator, PyList, PyString, PyTuple, PyType}; -use crate::{ - ffi, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, Py, PyNativeType, PyObject, PyTryFrom, - Python, ToBorrowedObject, ToPyObject, -}; -use std::cmp::Ordering; -use std::os::raw::c_int; - -/// Python object model helper methods -pub trait ObjectProtocol { - /// Determines whether this object has the given attribute. - /// - /// This is equivalent to the Python expression `hasattr(self, attr_name)`. - fn hasattr(&self, attr_name: N) -> PyResult - where - N: ToPyObject; - - /// Retrieves an attribute value. - /// - /// This is equivalent to the Python expression `self.attr_name`. - fn getattr(&self, attr_name: N) -> PyResult<&PyAny> - where - N: ToPyObject; - - /// Sets an attribute value. - /// - /// This is equivalent to the Python expression `self.attr_name = value`. - fn setattr(&self, attr_name: N, value: V) -> PyResult<()> - where - N: ToBorrowedObject, - V: ToBorrowedObject; - - /// Deletes an attribute. - /// - /// This is equivalent to the Python expression `del self.attr_name`. - fn delattr(&self, attr_name: N) -> PyResult<()> - where - N: ToPyObject; - - /// Compares two Python objects. - /// - /// This is equivalent to: - /// ```python - /// if self == other: - /// return Equal - /// elif a < b: - /// return Less - /// elif a > b: - /// return Greater - /// else: - /// raise TypeError("ObjectProtocol::compare(): All comparisons returned false") - /// ``` - fn compare(&self, other: O) -> PyResult - where - O: ToPyObject; - - /// Compares two Python objects. - /// - /// Depending on the value of `compare_op`, this is equivalent to one of the - /// following Python expressions: - /// * CompareOp::Eq: `self == other` - /// * CompareOp::Ne: `self != other` - /// * CompareOp::Lt: `self < other` - /// * CompareOp::Le: `self <= other` - /// * CompareOp::Gt: `self > other` - /// * CompareOp::Ge: `self >= other` - fn rich_compare(&self, other: O, compare_op: CompareOp) -> PyResult - where - O: ToPyObject; - - /// Computes the "repr" representation of self. - /// - /// This is equivalent to the Python expression `repr(self)`. - fn repr(&self) -> PyResult<&PyString>; - - /// Computes the "str" representation of self. - /// - /// This is equivalent to the Python expression `str(self)`. - fn str(&self) -> PyResult<&PyString>; - - /// Determines whether this object is callable. - fn is_callable(&self) -> bool; - - /// Calls the object. - /// - /// This is equivalent to the Python expression `self(*args, **kwargs)`. - fn call(&self, args: impl IntoPy>, kwargs: Option<&PyDict>) -> PyResult<&PyAny>; - - /// Calls the object with only positional arguments. - /// - /// This is equivalent to the Python expression `self(*args)`. - fn call1(&self, args: impl IntoPy>) -> PyResult<&PyAny>; - - /// Calls the object without arguments. - /// - /// This is equivalent to the Python expression `self()`. - fn call0(&self) -> PyResult<&PyAny>; - - /// Calls a method on the object. - /// - /// This is equivalent to the Python expression `self.name(*args, **kwargs)`. - /// - /// # Example - /// ```rust - /// # use pyo3::prelude::*; - /// use pyo3::types::IntoPyDict; - /// - /// let gil = Python::acquire_gil(); - /// let py = gil.python(); - /// let list = vec![3, 6, 5, 4, 7].to_object(py); - /// let dict = vec![("reverse", true)].into_py_dict(py); - /// list.call_method(py, "sort", (), Some(dict)).unwrap(); - /// assert_eq!(list.extract::>(py).unwrap(), vec![7, 6, 5, 4, 3]); - /// ``` - fn call_method( - &self, - name: &str, - args: impl IntoPy>, - kwargs: Option<&PyDict>, - ) -> PyResult<&PyAny>; - - /// Calls a method on the object with only positional arguments. - /// - /// This is equivalent to the Python expression `self.name(*args)`. - fn call_method1(&self, name: &str, args: impl IntoPy>) -> PyResult<&PyAny>; - - /// Calls a method on the object without arguments. - /// - /// This is equivalent to the Python expression `self.name()`. - fn call_method0(&self, name: &str) -> PyResult<&PyAny>; - - /// Retrieves the hash code of the object. - /// - /// This is equivalent to the Python expression `hash(self)`. - fn hash(&self) -> PyResult; - - /// Returns whether the object is considered to be true. - /// - /// This is equivalent to the Python expression `bool(self)`. - fn is_true(&self) -> PyResult; - - /// Returns whether the object is considered to be None. - /// - /// This is equivalent to the Python expression `self is None`. - fn is_none(&self) -> bool; - - /// Returns the length of the sequence or mapping. - /// - /// This is equivalent to the Python expression `len(self)`. - fn len(&self) -> PyResult; - - /// Returns true if the sequence or mapping has a length of 0. - /// - /// This is equivalent to the Python expression `len(self) == 0`. - fn is_empty(&self) -> PyResult; - - /// Gets an item from the collections. - /// - /// This is equivalent to the Python expression `self[key]`. - fn get_item(&self, key: K) -> PyResult<&PyAny> - where - K: ToBorrowedObject; - - /// Sets a collection item value. - /// - /// This is equivalent to the Python expression `self[key] = value`. - fn set_item(&self, key: K, value: V) -> PyResult<()> - where - K: ToBorrowedObject, - V: ToBorrowedObject; - - /// Deletes an item from the collection. - /// - /// This is equivalent to the Python expression `del self[key]`. - fn del_item(&self, key: K) -> PyResult<()> - where - K: ToBorrowedObject; - - /// Takes an object and returns an iterator for it. - /// - /// This is typically a new iterator but if the argument is an iterator, - /// this returns itself. - fn iter(&self) -> PyResult; - - /// Returns the Python type object for this object's type. - fn get_type(&self) -> &PyType; - - /// Returns the Python type pointer for this object. - fn get_type_ptr(&self) -> *mut ffi::PyTypeObject; - - /// Casts the PyObject to a concrete Python object type. - /// - /// This can cast only to native Python types, not types implemented in Rust. - fn cast_as<'a, D>(&'a self) -> Result<&'a D, PyDowncastError> - where - D: PyTryFrom<'a>, - &'a PyAny: std::convert::From<&'a Self>; - - /// Extracts some type from the Python object. - /// - /// This is a wrapper function around `FromPyObject::extract()`. - fn extract<'a, D>(&'a self) -> PyResult - where - D: FromPyObject<'a>, - &'a PyAny: std::convert::From<&'a Self>; - - /// Returns the reference count for the Python object. - fn get_refcnt(&self) -> isize; - - /// Returns the list of attributes of this object. - fn dir(&self) -> &PyList; - - /// Gets the Python builtin value `None`. - #[allow(non_snake_case)] // the Python keyword starts with uppercase - fn None(&self) -> PyObject; -} - -impl ObjectProtocol for T -where - T: PyNativeType + AsPyPointer, -{ - fn hasattr(&self, attr_name: N) -> PyResult - where - N: ToPyObject, - { - attr_name.with_borrowed_ptr(self.py(), |attr_name| unsafe { - Ok(ffi::PyObject_HasAttr(self.as_ptr(), attr_name) != 0) - }) - } - - fn getattr(&self, attr_name: N) -> PyResult<&PyAny> - where - N: ToPyObject, - { - attr_name.with_borrowed_ptr(self.py(), |attr_name| unsafe { - self.py() - .from_owned_ptr_or_err(ffi::PyObject_GetAttr(self.as_ptr(), attr_name)) - }) - } - - fn setattr(&self, attr_name: N, value: V) -> PyResult<()> - where - N: ToBorrowedObject, - V: ToBorrowedObject, - { - attr_name.with_borrowed_ptr(self.py(), move |attr_name| { - value.with_borrowed_ptr(self.py(), |value| unsafe { - err::error_on_minusone( - self.py(), - ffi::PyObject_SetAttr(self.as_ptr(), attr_name, value), - ) - }) - }) - } - - fn delattr(&self, attr_name: N) -> PyResult<()> - where - N: ToPyObject, - { - attr_name.with_borrowed_ptr(self.py(), |attr_name| unsafe { - err::error_on_minusone(self.py(), ffi::PyObject_DelAttr(self.as_ptr(), attr_name)) - }) - } - - fn compare(&self, other: O) -> PyResult - where - O: ToPyObject, - { - unsafe fn do_compare( - py: Python, - a: *mut ffi::PyObject, - b: *mut ffi::PyObject, - ) -> PyResult { - let result = ffi::PyObject_RichCompareBool(a, b, ffi::Py_EQ); - if result == 1 { - return Ok(Ordering::Equal); - } else if result < 0 { - return Err(PyErr::fetch(py)); - } - let result = ffi::PyObject_RichCompareBool(a, b, ffi::Py_LT); - if result == 1 { - return Ok(Ordering::Less); - } else if result < 0 { - return Err(PyErr::fetch(py)); - } - let result = ffi::PyObject_RichCompareBool(a, b, ffi::Py_GT); - if result == 1 { - return Ok(Ordering::Greater); - } else if result < 0 { - return Err(PyErr::fetch(py)); - } - Err(TypeError::py_err( - "ObjectProtocol::compare(): All comparisons returned false", - )) - } - - other.with_borrowed_ptr(self.py(), |other| unsafe { - do_compare(self.py(), self.as_ptr(), other) - }) - } - - fn rich_compare(&self, other: O, compare_op: CompareOp) -> PyResult - where - O: ToPyObject, - { - unsafe { - other.with_borrowed_ptr(self.py(), |other| { - PyObject::from_owned_ptr_or_err( - self.py(), - ffi::PyObject_RichCompare(self.as_ptr(), other, compare_op as c_int), - ) - }) - } - } - - fn repr(&self) -> PyResult<&PyString> { - unsafe { - self.py() - .from_owned_ptr_or_err(ffi::PyObject_Repr(self.as_ptr())) - } - } - - fn str(&self) -> PyResult<&PyString> { - unsafe { - self.py() - .from_owned_ptr_or_err(ffi::PyObject_Str(self.as_ptr())) - } - } - - fn is_callable(&self) -> bool { - unsafe { ffi::PyCallable_Check(self.as_ptr()) != 0 } - } - - fn call(&self, args: impl IntoPy>, kwargs: Option<&PyDict>) -> PyResult<&PyAny> { - let args = args.into_py(self.py()).into_ptr(); - let kwargs = kwargs.into_ptr(); - let result = unsafe { - let return_value = ffi::PyObject_Call(self.as_ptr(), args, kwargs); - self.py().from_owned_ptr_or_err(return_value) - }; - unsafe { - ffi::Py_XDECREF(args); - ffi::Py_XDECREF(kwargs); - } - result - } - - fn call0(&self) -> PyResult<&PyAny> { - self.call((), None) - } - - fn call1(&self, args: impl IntoPy>) -> PyResult<&PyAny> { - self.call(args, None) - } - - fn call_method( - &self, - name: &str, - args: impl IntoPy>, - kwargs: Option<&PyDict>, - ) -> PyResult<&PyAny> { - name.with_borrowed_ptr(self.py(), |name| unsafe { - let py = self.py(); - let ptr = ffi::PyObject_GetAttr(self.as_ptr(), name); - if ptr.is_null() { - return Err(PyErr::fetch(py)); - } - let args = args.into_py(py).into_ptr(); - let kwargs = kwargs.into_ptr(); - let result_ptr = ffi::PyObject_Call(ptr, args, kwargs); - let result = py.from_owned_ptr_or_err(result_ptr); - ffi::Py_DECREF(ptr); - ffi::Py_XDECREF(args); - ffi::Py_XDECREF(kwargs); - result - }) - } - - fn call_method0(&self, name: &str) -> PyResult<&PyAny> { - self.call_method(name, (), None) - } - - fn call_method1(&self, name: &str, args: impl IntoPy>) -> PyResult<&PyAny> { - self.call_method(name, args, None) - } - - fn hash(&self) -> PyResult { - let v = unsafe { ffi::PyObject_Hash(self.as_ptr()) }; - if v == -1 { - Err(PyErr::fetch(self.py())) - } else { - Ok(v) - } - } - - fn is_true(&self) -> PyResult { - let v = unsafe { ffi::PyObject_IsTrue(self.as_ptr()) }; - if v == -1 { - Err(PyErr::fetch(self.py())) - } else { - Ok(v != 0) - } - } - - fn is_none(&self) -> bool { - unsafe { ffi::Py_None() == self.as_ptr() } - } - - fn len(&self) -> PyResult { - let v = unsafe { ffi::PyObject_Size(self.as_ptr()) }; - if v == -1 { - Err(PyErr::fetch(self.py())) - } else { - Ok(v as usize) - } - } - - fn is_empty(&self) -> PyResult { - self.len().map(|l| l == 0) - } - - fn get_item(&self, key: K) -> PyResult<&PyAny> - where - K: ToBorrowedObject, - { - key.with_borrowed_ptr(self.py(), |key| unsafe { - self.py() - .from_owned_ptr_or_err(ffi::PyObject_GetItem(self.as_ptr(), key)) - }) - } - - fn set_item(&self, key: K, value: V) -> PyResult<()> - where - K: ToBorrowedObject, - V: ToBorrowedObject, - { - key.with_borrowed_ptr(self.py(), move |key| { - value.with_borrowed_ptr(self.py(), |value| unsafe { - err::error_on_minusone(self.py(), ffi::PyObject_SetItem(self.as_ptr(), key, value)) - }) - }) - } - - fn del_item(&self, key: K) -> PyResult<()> - where - K: ToBorrowedObject, - { - key.with_borrowed_ptr(self.py(), |key| unsafe { - err::error_on_minusone(self.py(), ffi::PyObject_DelItem(self.as_ptr(), key)) - }) - } - - fn iter(&self) -> PyResult { - Ok(PyIterator::from_object(self.py(), self)?) - } - - fn get_type(&self) -> &PyType { - unsafe { PyType::from_type_ptr(self.py(), (*self.as_ptr()).ob_type) } - } - - #[inline] - fn get_type_ptr(&self) -> *mut ffi::PyTypeObject { - unsafe { (*self.as_ptr()).ob_type } - } - - fn cast_as<'a, D>(&'a self) -> Result<&'a D, PyDowncastError> - where - D: PyTryFrom<'a>, - &'a PyAny: std::convert::From<&'a Self>, - { - D::try_from(self) - } - - fn extract<'a, D>(&'a self) -> PyResult - where - D: FromPyObject<'a>, - &'a PyAny: std::convert::From<&'a T>, - { - FromPyObject::extract(self.into()) - } - - fn get_refcnt(&self) -> isize { - unsafe { ffi::Py_REFCNT(self.as_ptr()) } - } - - fn dir(&self) -> &PyList { - unsafe { self.py().from_owned_ptr(ffi::PyObject_Dir(self.as_ptr())) } - } - - #[allow(non_snake_case)] // the Python keyword starts with uppercase - fn None(&self) -> PyObject { - unsafe { PyObject::from_borrowed_ptr(self.py(), ffi::Py_None()) } - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::instance::AsPyRef; - use crate::types::{IntoPyDict, PyString}; - use crate::Python; - use crate::{PyTryFrom, ToPyObject}; - - #[test] - fn test_debug_string() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let v = "Hello\n".to_object(py); - let s = ::try_from(v.as_ref(py)).unwrap(); - assert_eq!(format!("{:?}", s), "'Hello\\n'"); - } - - #[test] - fn test_display_string() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let v = "Hello\n".to_object(py); - let s = ::try_from(v.as_ref(py)).unwrap(); - assert_eq!(format!("{}", s), "Hello\n"); - } - - #[test] - fn test_call_for_non_existing_method() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let a = py.eval("42", None, None).unwrap(); - a.call_method0("__str__").unwrap(); // ok - assert!(a.call_method("nonexistent_method", (1,), None).is_err()); - assert!(a.call_method0("nonexistent_method").is_err()); - assert!(a.call_method1("nonexistent_method", (1,)).is_err()); - } - - #[test] - fn test_call_with_kwargs() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let list = vec![3, 6, 5, 4, 7].to_object(py); - let dict = vec![("reverse", true)].into_py_dict(py); - list.call_method(py, "sort", (), Some(dict)).unwrap(); - assert_eq!(list.extract::>(py).unwrap(), vec![7, 6, 5, 4, 3]); - } - - #[test] - fn test_type() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let obj = py.eval("42", None, None).unwrap(); - assert_eq!(unsafe { obj.get_type().as_type_ptr() }, obj.get_type_ptr()) - } - - #[test] - fn test_dir() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let obj = py.eval("42", None, None).unwrap(); - let dir = py - .eval("dir(42)", None, None) - .unwrap() - .extract::<&PyList>() - .unwrap(); - let a = obj - .dir() - .into_iter() - .map(|x| x.extract::().unwrap()); - let b = dir.into_iter().map(|x| x.extract::().unwrap()); - assert!(a.eq(b)); - } -} diff --git a/src/prelude.rs b/src/prelude.rs index 09ce54773a0..60b7deab2d5 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -14,7 +14,6 @@ pub use crate::err::{PyErr, PyResult}; pub use crate::gil::GILGuard; pub use crate::instance::{AsPyRef, Py}; pub use crate::object::PyObject; -pub use crate::objectprotocol::ObjectProtocol; pub use crate::pycell::{PyCell, PyRef, PyRefMut}; pub use crate::pyclass_init::PyClassInitializer; pub use crate::python::Python; diff --git a/src/pycell.rs b/src/pycell.rs index 9cb31ae86f5..e6249824693 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -3,6 +3,7 @@ use crate::conversion::{AsPyPointer, FromPyPointer, ToPyObject}; use crate::pyclass_init::PyClassInitializer; use crate::pyclass_slots::{PyClassDict, PyClassWeakRef}; use crate::type_object::{PyBorrowFlagLayout, PyLayout, PySizedLayout, PyTypeInfo}; +use crate::types::PyAny; use crate::{ffi, FromPy, PyClass, PyErr, PyNativeType, PyObject, PyResult, Python}; use std::cell::{Cell, UnsafeCell}; use std::fmt; @@ -101,6 +102,9 @@ impl PyCellInner { /// [Interior Mutability Pattern](https://doc.rust-lang.org/book/ch15-05-interior-mutability.html) /// like [std::cell::RefCell](https://doc.rust-lang.org/std/cell/struct.RefCell.html). /// +/// `PyCell` implements `Deref`, so you can also call methods from `PyAny` +/// when you have a `PyCell`. +/// /// # Examples /// /// In most cases, `PyCell` is hidden behind `#[pymethods]`. @@ -374,6 +378,20 @@ impl ToPyObject for &PyCell { } } +impl AsRef for PyCell { + fn as_ref(&self) -> &PyAny { + unsafe { self.py().from_borrowed_ptr(self.as_ptr()) } + } +} + +impl Deref for PyCell { + type Target = PyAny; + + fn deref(&self) -> &PyAny { + unsafe { self.py().from_borrowed_ptr(self.as_ptr()) } + } +} + impl fmt::Debug for PyCell { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.try_borrow() { diff --git a/src/python.rs b/src/python.rs index fbb682c36a6..e9d2bbb8643 100644 --- a/src/python.rs +++ b/src/python.rs @@ -204,7 +204,7 @@ impl<'p> Python<'p> { ) -> PyResult<()> { let res = self.run_code(code, ffi::Py_file_input, globals, locals); res.map(|obj| { - debug_assert!(crate::ObjectProtocol::is_none(obj)); + debug_assert!(obj.is_none()); }) } @@ -412,7 +412,6 @@ impl<'p> Python<'p> { #[cfg(test)] mod test { - use crate::objectprotocol::ObjectProtocol; use crate::types::{IntoPyDict, PyAny, PyBool, PyInt, PyList}; use crate::Python; diff --git a/src/types/any.rs b/src/types/any.rs index 32ccee4004d..7c9e4117209 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -1,7 +1,14 @@ -use crate::conversion::{AsPyPointer, PyTryFrom}; -use crate::err::PyDowncastError; -use crate::ffi; +use crate::class::basic::CompareOp; +use crate::conversion::{ + AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyTryFrom, ToBorrowedObject, ToPyObject, +}; +use crate::err::{PyDowncastError, PyErr, PyResult}; +use crate::exceptions::TypeError; +use crate::types::{PyDict, PyIterator, PyList, PyString, PyTuple, PyType}; +use crate::{err, ffi, Py, PyNativeType, Python}; +use libc::c_int; use std::cell::UnsafeCell; +use std::cmp::Ordering; /// A Python object with GIL lifetime /// @@ -59,10 +66,449 @@ pyobject_native_type_convert!( pyobject_native_type_extract!(PyAny); impl PyAny { + /// Convert this PyAny to a concrete Python type. pub fn downcast(&self) -> Result<&T, PyDowncastError> where for<'py> T: PyTryFrom<'py>, { ::try_from(self) } + + /// Determines whether this object has the given attribute. + /// + /// This is equivalent to the Python expression `hasattr(self, attr_name)`. + pub fn hasattr(&self, attr_name: N) -> PyResult + where + N: ToPyObject, + { + attr_name.with_borrowed_ptr(self.py(), |attr_name| unsafe { + Ok(ffi::PyObject_HasAttr(self.as_ptr(), attr_name) != 0) + }) + } + + /// Retrieves an attribute value. + /// + /// This is equivalent to the Python expression `self.attr_name`. + pub fn getattr(&self, attr_name: N) -> PyResult<&PyAny> + where + N: ToPyObject, + { + attr_name.with_borrowed_ptr(self.py(), |attr_name| unsafe { + self.py() + .from_owned_ptr_or_err(ffi::PyObject_GetAttr(self.as_ptr(), attr_name)) + }) + } + + /// Sets an attribute value. + /// + /// This is equivalent to the Python expression `self.attr_name = value`. + pub fn setattr(&self, attr_name: N, value: V) -> PyResult<()> + where + N: ToBorrowedObject, + V: ToBorrowedObject, + { + attr_name.with_borrowed_ptr(self.py(), move |attr_name| { + value.with_borrowed_ptr(self.py(), |value| unsafe { + err::error_on_minusone( + self.py(), + ffi::PyObject_SetAttr(self.as_ptr(), attr_name, value), + ) + }) + }) + } + + /// Deletes an attribute. + /// + /// This is equivalent to the Python expression `del self.attr_name`. + pub fn delattr(&self, attr_name: N) -> PyResult<()> + where + N: ToPyObject, + { + attr_name.with_borrowed_ptr(self.py(), |attr_name| unsafe { + err::error_on_minusone(self.py(), ffi::PyObject_DelAttr(self.as_ptr(), attr_name)) + }) + } + + /// Compares two Python objects. + /// + /// This is equivalent to: + /// ```python + /// if self == other: + /// return Equal + /// elif a < b: + /// return Less + /// elif a > b: + /// return Greater + /// else: + /// raise TypeError("PyAny::compare(): All comparisons returned false") + /// ``` + pub fn compare(&self, other: O) -> PyResult + where + O: ToPyObject, + { + unsafe fn do_compare( + py: Python, + a: *mut ffi::PyObject, + b: *mut ffi::PyObject, + ) -> PyResult { + let result = ffi::PyObject_RichCompareBool(a, b, ffi::Py_EQ); + if result == 1 { + return Ok(Ordering::Equal); + } else if result < 0 { + return Err(PyErr::fetch(py)); + } + let result = ffi::PyObject_RichCompareBool(a, b, ffi::Py_LT); + if result == 1 { + return Ok(Ordering::Less); + } else if result < 0 { + return Err(PyErr::fetch(py)); + } + let result = ffi::PyObject_RichCompareBool(a, b, ffi::Py_GT); + if result == 1 { + return Ok(Ordering::Greater); + } else if result < 0 { + return Err(PyErr::fetch(py)); + } + Err(TypeError::py_err( + "PyAny::compare(): All comparisons returned false", + )) + } + + other.with_borrowed_ptr(self.py(), |other| unsafe { + do_compare(self.py(), self.as_ptr(), other) + }) + } + + /// Compares two Python objects. + /// + /// Depending on the value of `compare_op`, this is equivalent to one of the + /// following Python expressions: + /// * CompareOp::Eq: `self == other` + /// * CompareOp::Ne: `self != other` + /// * CompareOp::Lt: `self < other` + /// * CompareOp::Le: `self <= other` + /// * CompareOp::Gt: `self > other` + /// * CompareOp::Ge: `self >= other` + pub fn rich_compare(&self, other: O, compare_op: CompareOp) -> PyResult<&PyAny> + where + O: ToPyObject, + { + unsafe { + other.with_borrowed_ptr(self.py(), |other| { + self.py().from_owned_ptr_or_err(ffi::PyObject_RichCompare( + self.as_ptr(), + other, + compare_op as c_int, + )) + }) + } + } + + /// Determines whether this object is callable. + pub fn is_callable(&self) -> bool { + unsafe { ffi::PyCallable_Check(self.as_ptr()) != 0 } + } + + /// Calls the object. + /// + /// This is equivalent to the Python expression `self(*args, **kwargs)`. + pub fn call( + &self, + args: impl IntoPy>, + kwargs: Option<&PyDict>, + ) -> PyResult<&PyAny> { + let args = args.into_py(self.py()).into_ptr(); + let kwargs = kwargs.into_ptr(); + let result = unsafe { + let return_value = ffi::PyObject_Call(self.as_ptr(), args, kwargs); + self.py().from_owned_ptr_or_err(return_value) + }; + unsafe { + ffi::Py_XDECREF(args); + ffi::Py_XDECREF(kwargs); + } + result + } + + /// Calls the object with only positional arguments. + /// + /// This is equivalent to the Python expression `self(*args)`. + pub fn call0(&self) -> PyResult<&PyAny> { + self.call((), None) + } + + /// Calls the object without arguments. + /// + /// This is equivalent to the Python expression `self()`. + pub fn call1(&self, args: impl IntoPy>) -> PyResult<&PyAny> { + self.call(args, None) + } + + /// Calls a method on the object. + /// + /// This is equivalent to the Python expression `self.name(*args, **kwargs)`. + /// + /// # Example + /// ```rust + /// # use pyo3::prelude::*; + /// use pyo3::types::IntoPyDict; + /// + /// let gil = Python::acquire_gil(); + /// let py = gil.python(); + /// let list = vec![3, 6, 5, 4, 7].to_object(py); + /// let dict = vec![("reverse", true)].into_py_dict(py); + /// list.call_method(py, "sort", (), Some(dict)).unwrap(); + /// assert_eq!(list.extract::>(py).unwrap(), vec![7, 6, 5, 4, 3]); + /// ``` + pub fn call_method( + &self, + name: &str, + args: impl IntoPy>, + kwargs: Option<&PyDict>, + ) -> PyResult<&PyAny> { + name.with_borrowed_ptr(self.py(), |name| unsafe { + let py = self.py(); + let ptr = ffi::PyObject_GetAttr(self.as_ptr(), name); + if ptr.is_null() { + return Err(PyErr::fetch(py)); + } + let args = args.into_py(py).into_ptr(); + let kwargs = kwargs.into_ptr(); + let result_ptr = ffi::PyObject_Call(ptr, args, kwargs); + let result = py.from_owned_ptr_or_err(result_ptr); + ffi::Py_DECREF(ptr); + ffi::Py_XDECREF(args); + ffi::Py_XDECREF(kwargs); + result + }) + } + + /// Calls a method on the object without arguments. + /// + /// This is equivalent to the Python expression `self.name()`. + pub fn call_method0(&self, name: &str) -> PyResult<&PyAny> { + self.call_method(name, (), None) + } + + /// Calls a method on the object with only positional arguments. + /// + /// This is equivalent to the Python expression `self.name(*args)`. + pub fn call_method1(&self, name: &str, args: impl IntoPy>) -> PyResult<&PyAny> { + self.call_method(name, args, None) + } + + /// Returns whether the object is considered to be true. + /// + /// This is equivalent to the Python expression `bool(self)`. + pub fn is_true(&self) -> PyResult { + let v = unsafe { ffi::PyObject_IsTrue(self.as_ptr()) }; + if v == -1 { + Err(PyErr::fetch(self.py())) + } else { + Ok(v != 0) + } + } + + /// Returns whether the object is considered to be None. + /// + /// This is equivalent to the Python expression `self is None`. + pub fn is_none(&self) -> bool { + unsafe { ffi::Py_None() == self.as_ptr() } + } + + /// Returns true if the sequence or mapping has a length of 0. + /// + /// This is equivalent to the Python expression `len(self) == 0`. + pub fn is_empty(&self) -> PyResult { + self.len().map(|l| l == 0) + } + + /// Gets an item from the collection. + /// + /// This is equivalent to the Python expression `self[key]`. + pub fn get_item(&self, key: K) -> PyResult<&PyAny> + where + K: ToBorrowedObject, + { + key.with_borrowed_ptr(self.py(), |key| unsafe { + self.py() + .from_owned_ptr_or_err(ffi::PyObject_GetItem(self.as_ptr(), key)) + }) + } + + /// Sets a collection item value. + /// + /// This is equivalent to the Python expression `self[key] = value`. + pub fn set_item(&self, key: K, value: V) -> PyResult<()> + where + K: ToBorrowedObject, + V: ToBorrowedObject, + { + key.with_borrowed_ptr(self.py(), move |key| { + value.with_borrowed_ptr(self.py(), |value| unsafe { + err::error_on_minusone(self.py(), ffi::PyObject_SetItem(self.as_ptr(), key, value)) + }) + }) + } + + /// Deletes an item from the collection. + /// + /// This is equivalent to the Python expression `del self[key]`. + pub fn del_item(&self, key: K) -> PyResult<()> + where + K: ToBorrowedObject, + { + key.with_borrowed_ptr(self.py(), |key| unsafe { + err::error_on_minusone(self.py(), ffi::PyObject_DelItem(self.as_ptr(), key)) + }) + } + + /// Takes an object and returns an iterator for it. + /// + /// This is typically a new iterator but if the argument is an iterator, + /// this returns itself. + pub fn iter(&self) -> PyResult { + Ok(PyIterator::from_object(self.py(), self)?) + } + + /// Returns the Python type object for this object's type. + pub fn get_type(&self) -> &PyType { + unsafe { PyType::from_type_ptr(self.py(), (*self.as_ptr()).ob_type) } + } + + /// Returns the Python type pointer for this object. + #[inline] + pub fn get_type_ptr(&self) -> *mut ffi::PyTypeObject { + unsafe { (*self.as_ptr()).ob_type } + } + + /// Casts the PyObject to a concrete Python object type. + /// + /// This can cast only to native Python types, not types implemented in Rust. + pub fn cast_as<'a, D>(&'a self) -> Result<&'a D, PyDowncastError> + where + D: PyTryFrom<'a>, + { + D::try_from(self) + } + + /// Extracts some type from the Python object. + /// + /// This is a wrapper function around `FromPyObject::extract()`. + pub fn extract<'a, D>(&'a self) -> PyResult + where + D: FromPyObject<'a>, + { + FromPyObject::extract(self) + } + + /// Returns the reference count for the Python object. + pub fn get_refcnt(&self) -> isize { + unsafe { ffi::Py_REFCNT(self.as_ptr()) } + } + + /// Computes the "repr" representation of self. + /// + /// This is equivalent to the Python expression `repr(self)`. + pub fn repr(&self) -> PyResult<&PyString> { + unsafe { + self.py() + .from_owned_ptr_or_err(ffi::PyObject_Repr(self.as_ptr())) + } + } + + /// Computes the "str" representation of self. + /// + /// This is equivalent to the Python expression `str(self)`. + pub fn str(&self) -> PyResult<&PyString> { + unsafe { + self.py() + .from_owned_ptr_or_err(ffi::PyObject_Str(self.as_ptr())) + } + } + + /// Retrieves the hash code of self. + /// + /// This is equivalent to the Python expression `hash(obi)`. + pub fn hash(&self) -> PyResult { + let v = unsafe { ffi::PyObject_Hash(self.as_ptr()) }; + if v == -1 { + Err(PyErr::fetch(self.py())) + } else { + Ok(v) + } + } + + /// Returns the length of the sequence or mapping. + /// + /// This is equivalent to the Python expression `len(self)`. + pub fn len(&self) -> PyResult { + let v = unsafe { ffi::PyObject_Size(self.as_ptr()) }; + if v == -1 { + Err(PyErr::fetch(self.py())) + } else { + Ok(v as usize) + } + } + + /// Returns the list of attributes of this object. + /// + /// This is equivalent to the Python expression `dir(self)`. + pub fn dir(&self) -> &PyList { + unsafe { self.py().from_owned_ptr(ffi::PyObject_Dir(self.as_ptr())) } + } +} + +#[cfg(test)] +mod test { + use crate::types::{IntoPyDict, PyList}; + use crate::Python; + use crate::ToPyObject; + + #[test] + fn test_call_for_non_existing_method() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let a = py.eval("42", None, None).unwrap(); + a.call_method0("__str__").unwrap(); // ok + assert!(a.call_method("nonexistent_method", (1,), None).is_err()); + assert!(a.call_method0("nonexistent_method").is_err()); + assert!(a.call_method1("nonexistent_method", (1,)).is_err()); + } + + #[test] + fn test_call_with_kwargs() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let list = vec![3, 6, 5, 4, 7].to_object(py); + let dict = vec![("reverse", true)].into_py_dict(py); + list.call_method(py, "sort", (), Some(dict)).unwrap(); + assert_eq!(list.extract::>(py).unwrap(), vec![7, 6, 5, 4, 3]); + } + + #[test] + fn test_type() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let obj = py.eval("42", None, None).unwrap(); + assert_eq!(unsafe { obj.get_type().as_type_ptr() }, obj.get_type_ptr()) + } + + #[test] + fn test_dir() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let obj = py.eval("42", None, None).unwrap(); + let dir = py + .eval("dir(42)", None, None) + .unwrap() + .downcast::() + .unwrap(); + let a = obj + .dir() + .into_iter() + .map(|x| x.extract::().unwrap()); + let b = dir.into_iter().map(|x| x.extract::().unwrap()); + assert!(a.eq(b)); + } } diff --git a/src/types/boolobject.rs b/src/types/boolobject.rs index 686947f823a..44078fb25d5 100644 --- a/src/types/boolobject.rs +++ b/src/types/boolobject.rs @@ -59,7 +59,6 @@ impl<'source> FromPyObject<'source> for bool { #[cfg(test)] mod test { - use crate::objectprotocol::ObjectProtocol; use crate::types::{PyAny, PyBool}; use crate::Python; use crate::ToPyObject; diff --git a/src/types/dict.rs b/src/types/dict.rs index fe75ccf79bc..4e4cca7256b 100644 --- a/src/types/dict.rs +++ b/src/types/dict.rs @@ -96,7 +96,7 @@ impl PyDict { /// /// Returns `None` if the item is not present, or if an error occurs. /// - /// To get a `KeyError` for non-existing keys, use `ObjectProtocol::get_item`. + /// To get a `KeyError` for non-existing keys, use `PyAny::get_item`. pub fn get_item(&self, key: K) -> Option<&PyAny> where K: ToBorrowedObject, @@ -361,8 +361,8 @@ mod test { use crate::instance::AsPyRef; use crate::types::dict::IntoPyDict; use crate::types::{PyDict, PyList, PyTuple}; + use crate::PyObject; use crate::Python; - use crate::{ObjectProtocol, PyObject}; use crate::{PyTryFrom, ToPyObject}; use std::collections::{BTreeMap, HashMap}; diff --git a/src/types/floatob.rs b/src/types/floatob.rs index 8c6096889f4..2512ebfdaf0 100644 --- a/src/types/floatob.rs +++ b/src/types/floatob.rs @@ -2,8 +2,8 @@ // // based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython use crate::{ - ffi, AsPyPointer, FromPy, FromPyObject, ObjectProtocol, PyAny, PyErr, PyNativeType, PyObject, - PyResult, Python, ToPyObject, + ffi, AsPyPointer, FromPy, FromPyObject, PyAny, PyErr, PyNativeType, PyObject, PyResult, Python, + ToPyObject, }; use std::os::raw::c_double; diff --git a/src/types/iterator.rs b/src/types/iterator.rs index 88f5fc78814..3a5a929b313 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -19,7 +19,7 @@ use crate::{ffi, AsPyPointer, PyAny, PyDowncastError, PyErr, PyNativeType, PyRes /// let gil = Python::acquire_gil(); /// let py = gil.python(); /// let list = py.eval("iter([1, 2, 3, 4])", None, None)?; -/// let numbers: PyResult> = list.iter()?.map(|i| i.and_then(ObjectProtocol::extract::)).collect(); +/// let numbers: PyResult> = list.iter()?.map(|i| i.and_then(PyAny::extract::)).collect(); /// let sum: usize = numbers?.iter().sum(); /// assert_eq!(sum, 10); /// # Ok(()) @@ -87,7 +87,6 @@ impl<'p> Drop for PyIterator<'p> { mod tests { use crate::gil::GILPool; use crate::instance::AsPyRef; - use crate::objectprotocol::ObjectProtocol; use crate::types::{PyDict, PyList}; use crate::GILGuard; use crate::Python; diff --git a/src/types/list.rs b/src/types/list.rs index 6fcd65221f9..50129e8924e 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -226,7 +226,6 @@ where #[cfg(test)] mod test { use crate::instance::AsPyRef; - use crate::objectprotocol::ObjectProtocol; use crate::types::PyList; use crate::Python; use crate::{IntoPy, PyObject, PyTryFrom, ToPyObject}; diff --git a/src/types/mod.rs b/src/types/mod.rs index a5479453d69..66e53e5d2d0 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -35,6 +35,15 @@ macro_rules! pyobject_native_type_named ( } } + impl<$($type_param,)*> ::std::ops::Deref for $name { + type Target = $crate::PyAny; + + #[inline] + fn deref(&self) -> &$crate::PyAny { + unsafe { &*(self.as_ptr() as *const $crate::PyAny) } + } + } + unsafe impl<$($type_param,)*> $crate::PyNativeType for $name {} impl<$($type_param,)*> $crate::AsPyPointer for $name { @@ -158,7 +167,6 @@ macro_rules! pyobject_native_type_convert( fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { - use $crate::ObjectProtocol; let s = self.repr().map_err(|_| ::std::fmt::Error)?; f.write_str(&s.to_string_lossy()) } @@ -168,7 +176,6 @@ macro_rules! pyobject_native_type_convert( fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { - use $crate::ObjectProtocol; let s = self.str().map_err(|_| ::std::fmt::Error)?; f.write_str(&s.to_string_lossy()) } diff --git a/src/types/module.rs b/src/types/module.rs index b7d614416f5..db15bdd99ee 100644 --- a/src/types/module.rs +++ b/src/types/module.rs @@ -7,7 +7,6 @@ use crate::exceptions; use crate::ffi; use crate::instance::PyNativeType; use crate::object::PyObject; -use crate::objectprotocol::ObjectProtocol; use crate::pyclass::PyClass; use crate::type_object::PyTypeObject; use crate::types::PyTuple; diff --git a/src/types/num.rs b/src/types/num.rs index e3b8e05361d..965aa1ae4f4 100644 --- a/src/types/num.rs +++ b/src/types/num.rs @@ -38,7 +38,7 @@ macro_rules! int_fits_larger_int { impl<'source> FromPyObject<'source> for $rust_type { fn extract(obj: &'source PyAny) -> PyResult { - let val = $crate::objectprotocol::ObjectProtocol::extract::<$larger_type>(obj)?; + let val: $larger_type = obj.extract()?; <$rust_type>::try_from(val).map_err(|_| exceptions::OverflowError.into()) } } diff --git a/src/types/sequence.rs b/src/types/sequence.rs index cb2fe3fbe64..4adaf5a8b53 100644 --- a/src/types/sequence.rs +++ b/src/types/sequence.rs @@ -5,7 +5,6 @@ use crate::err::{self, PyDowncastError, PyErr, PyResult}; use crate::exceptions; use crate::ffi::{self, Py_ssize_t}; use crate::instance::PyNativeType; -use crate::objectprotocol::ObjectProtocol; use crate::types::{PyAny, PyList, PyTuple}; use crate::AsPyPointer; use crate::{FromPyObject, PyTryFrom, ToBorrowedObject}; @@ -385,7 +384,6 @@ impl<'v> PyTryFrom<'v> for PySequence { mod test { use crate::instance::AsPyRef; use crate::object::PyObject; - use crate::objectprotocol::ObjectProtocol; use crate::types::PySequence; use crate::AsPyPointer; use crate::Python; diff --git a/src/types/set.rs b/src/types/set.rs index 75cc11b4c73..a815f81afd8 100644 --- a/src/types/set.rs +++ b/src/types/set.rs @@ -303,7 +303,7 @@ impl<'a> std::iter::IntoIterator for &'a PyFrozenSet { #[cfg(test)] mod test { use super::{PyFrozenSet, PySet}; - use crate::{AsPyRef, IntoPy, ObjectProtocol, PyObject, PyTryFrom, Python, ToPyObject}; + use crate::{AsPyRef, IntoPy, PyObject, PyTryFrom, Python, ToPyObject}; use std::collections::{BTreeSet, HashSet}; use std::iter::FromIterator; diff --git a/src/types/string.rs b/src/types/string.rs index 434ccbca060..625ae55d0a7 100644 --- a/src/types/string.rs +++ b/src/types/string.rs @@ -255,4 +255,22 @@ mod test { let py_string = ::try_from(obj.as_ref(py)).unwrap(); assert_eq!(py_string.to_string_lossy(), "🐈 Hello ���World"); } + + #[test] + fn test_debug_string() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = "Hello\n".to_object(py); + let s = ::try_from(v.as_ref(py)).unwrap(); + assert_eq!(format!("{:?}", s), "'Hello\\n'"); + } + + #[test] + fn test_display_string() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = "Hello\n".to_object(py); + let s = ::try_from(v.as_ref(py)).unwrap(); + assert_eq!(format!("{}", s), "Hello\n"); + } } diff --git a/src/types/tuple.rs b/src/types/tuple.rs index 40e1ef3f0b5..d8c9a0be2e2 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -247,7 +247,7 @@ tuple_conversion!( #[cfg(test)] mod test { use crate::types::{PyAny, PyTuple}; - use crate::{AsPyRef, ObjectProtocol, PyTryFrom, Python, ToPyObject}; + use crate::{AsPyRef, PyTryFrom, Python, ToPyObject}; use std::collections::HashSet; #[test] diff --git a/tests/test_class_conversion.rs b/tests/test_class_conversion.rs index 6da710c0748..2c2d3030828 100644 --- a/tests/test_class_conversion.rs +++ b/tests/test_class_conversion.rs @@ -1,5 +1,5 @@ use pyo3::prelude::*; -use pyo3::{ObjectProtocol, ToPyObject}; +use pyo3::ToPyObject; #[macro_use] mod common; @@ -31,7 +31,10 @@ fn test_cloneable_pyclass() { } #[pyclass] -struct BaseClass {} +#[derive(Default)] +struct BaseClass { + value: i32, +} #[pymethods] impl BaseClass { @@ -64,7 +67,7 @@ fn test_polymorphic_container_stores_base_class() { let p = PyCell::new( py, PolymorphicContainer { - inner: Py::new(py, BaseClass {}).unwrap(), + inner: Py::new(py, BaseClass::default()).unwrap(), }, ) .unwrap() @@ -81,7 +84,7 @@ fn test_polymorphic_container_stores_sub_class() { let p = PyCell::new( py, PolymorphicContainer { - inner: Py::new(py, BaseClass {}).unwrap(), + inner: Py::new(py, BaseClass::default()).unwrap(), }, ) .unwrap() @@ -92,7 +95,7 @@ fn test_polymorphic_container_stores_sub_class() { "inner", PyCell::new( py, - PyClassInitializer::from(BaseClass {}).add_subclass(SubClass {}), + PyClassInitializer::from(BaseClass::default()).add_subclass(SubClass {}), ) .unwrap(), ) @@ -109,7 +112,7 @@ fn test_polymorphic_container_does_not_accept_other_types() { let p = PyCell::new( py, PolymorphicContainer { - inner: Py::new(py, BaseClass {}).unwrap(), + inner: Py::new(py, BaseClass::default()).unwrap(), }, ) .unwrap() @@ -121,3 +124,40 @@ fn test_polymorphic_container_does_not_accept_other_types() { assert!(setattr(py.None()).is_err()); assert!(setattr((1i32, 2i32).into_py(py)).is_err()); } + +#[test] +fn test_pyref_as_base() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let cell = PyCell::new(py, (SubClass {}, BaseClass { value: 120 })).unwrap(); + + // First try PyRefMut + let sub: PyRefMut = cell.borrow_mut(); + let mut base: PyRefMut = sub.into_super(); + assert_eq!(120, base.value); + base.value = 999; + assert_eq!(999, base.value); + drop(base); + + // Repeat for PyRef + let sub: PyRef = cell.borrow(); + let base: PyRef = sub.into_super(); + assert_eq!(999, base.value); +} + +#[test] +fn test_pycell_deref() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let cell = PyCell::new(py, (SubClass {}, BaseClass { value: 120 })).unwrap(); + + // Should be able to deref as PyAny + assert_eq!( + cell.call_method0("foo") + .and_then(PyAny::extract::<&str>) + .unwrap(), + "SubClass" + ); +} diff --git a/tests/test_gc.rs b/tests/test_gc.rs index 88e440fdf02..0b01de83211 100644 --- a/tests/test_gc.rs +++ b/tests/test_gc.rs @@ -240,7 +240,8 @@ fn inheritance_with_new_methods_with_drop() { let obj: &PyCell = inst.try_into().unwrap(); let mut obj_ref_mut = obj.borrow_mut(); obj_ref_mut.data = Some(Arc::clone(&drop_called1)); - obj_ref_mut.as_mut().data = Some(Arc::clone(&drop_called2)); + let base: &mut BaseClassWithDrop = obj_ref_mut.as_mut(); + base.data = Some(Arc::clone(&drop_called2)); } assert!(drop_called1.load(Ordering::Relaxed));