Merge pull request #911 from davidhewitt/remove-objectprotocol

Remove ObjectProtocol
This commit is contained in:
Yuji Kanagawa 2020-05-08 18:56:52 +09:00 committed by GitHub
commit d5eb8f0544
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 703 additions and 677 deletions

View File

@ -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<T>` 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<PyObject>` 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<Target = PyAny>`. [#911](https://github.com/PyO3/pyo3/pull/911)
* `PyCell<T>` now implements `Deref<Target = PyAny>`. [#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]

View File

@ -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

View File

@ -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

View File

@ -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<PyAny> using .into() or Py::from
let _: Py<PyAny> = obj.into();
// Convert to Py<ConcreteType> using PyAny::extract
let _: Py<PyList> = 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<Target = PyAny>`, 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<T> use .into() or Py::from()
let _: Py<PyList> = 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<ConcreteType>`: `obj.as_ref(py).extract::<Py<ConcreteType>>`
- 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<ConcreteType> using PyObject::extract
let _: Py<PyList> = obj.extract(py).unwrap();
```
### `Py<SomeType>`
@ -75,45 +160,25 @@ implemented in Rust.
**Conversions:**
- To `PyObject`: `obj.to_object(py)`
- To `&SomeType` or `&PyCell<SomeType>`: `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.
```rust
# use pyo3::prelude::*;
# use pyo3::types::PyList;
# let gil = Python::acquire_gil();
# let py = gil.python();
let list: Py<PyList> = PyList::empty(py).into();
// Access the native type using AsPyRef::as_ref(py)
// (For #[pyclass] types, as_ref() will return &PyCell<T>)
let _: &PyList = list.as_ref(py);
// Convert to PyObject with .into()
let _: PyObject = list.into();
```
**Note:** `PyObject` is semantically equivalent to `Py<PyAny>` 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)`
### `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.
**Conversions:**
- To `PyAny`: `obj.as_ref()`
- To `Py<T>`: `Py::from(obj)`
### `PyCell<SomeType>`
**Represents:** a reference to a Rust object (instance of `PyClass`) which is
@ -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<T>` implements `Deref<Target = PyAny>`,
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<MyClass> = PyCell::new(py, MyClass { }).unwrap();
// Obtain PyRef<T> with .try_borrow()
let pr: PyRef<MyClass> = cell.try_borrow().unwrap();
# drop(pr);
// Obtain PyRefMut<T> with .try_borrow_mut()
let prm: PyRefMut<MyClass> = cell.try_borrow_mut().unwrap();
# drop(prm);
// Can use methods from PyAny on PyCell<T> due to Deref implementation
let _ = cell.repr();
// Rust will convert &PyCell<T> 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<T> from PyAny, use PyAny::downcast
let _: &PyCell<MyClass> = any.downcast().unwrap();
```
### `PyRef<SomeType>` and `PyRefMut<SomeType>`

View File

@ -430,7 +430,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),*
];

View File

@ -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()

View File

@ -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;

View File

@ -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<T>>(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<T>>(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<T>>(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<T>>(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<T>>(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<T>>(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<U>>(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);

View File

@ -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;

View File

@ -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;

View File

@ -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};

View File

@ -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<T> Py<T> {
/// `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<T> Py<T> {
/// };
/// 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<T>::as_ref` returns `&PyDict`, `&PyList` or so for native types, and `&PyCell<T>`
/// 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<T> Py<T> {
/// };
/// 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;

View File

@ -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);
}

View File

@ -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<N>(&self, attr_name: N) -> PyResult<bool>
where
N: ToPyObject;
/// Retrieves an attribute value.
///
/// This is equivalent to the Python expression `self.attr_name`.
fn getattr<N>(&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<N, V>(&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<N>(&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<O>(&self, other: O) -> PyResult<Ordering>
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<O>(&self, other: O, compare_op: CompareOp) -> PyResult<PyObject>
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<Py<PyTuple>>, 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<Py<PyTuple>>) -> 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::<Vec<i32>>(py).unwrap(), vec![7, 6, 5, 4, 3]);
/// ```
fn call_method(
&self,
name: &str,
args: impl IntoPy<Py<PyTuple>>,
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<Py<PyTuple>>) -> 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<isize>;
/// Returns whether the object is considered to be true.
///
/// This is equivalent to the Python expression `bool(self)`.
fn is_true(&self) -> PyResult<bool>;
/// 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<usize>;
/// 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<bool>;
/// Gets an item from the collections.
///
/// This is equivalent to the Python expression `self[key]`.
fn get_item<K>(&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<K, V>(&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<K>(&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<PyIterator>;
/// 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<D>
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<T> ObjectProtocol for T
where
T: PyNativeType + AsPyPointer,
{
fn hasattr<N>(&self, attr_name: N) -> PyResult<bool>
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<N>(&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<N, V>(&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<N>(&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<O>(&self, other: O) -> PyResult<Ordering>
where
O: ToPyObject,
{
unsafe fn do_compare(
py: Python,
a: *mut ffi::PyObject,
b: *mut ffi::PyObject,
) -> PyResult<Ordering> {
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<O>(&self, other: O, compare_op: CompareOp) -> PyResult<PyObject>
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<Py<PyTuple>>, 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<Py<PyTuple>>) -> PyResult<&PyAny> {
self.call(args, None)
}
fn call_method(
&self,
name: &str,
args: impl IntoPy<Py<PyTuple>>,
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<Py<PyTuple>>) -> PyResult<&PyAny> {
self.call_method(name, args, None)
}
fn hash(&self) -> PyResult<isize> {
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<bool> {
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<usize> {
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<bool> {
self.len().map(|l| l == 0)
}
fn get_item<K>(&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<K, V>(&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<K>(&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<PyIterator> {
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<D>
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 = <PyString as PyTryFrom>::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 = <PyString as PyTryFrom>::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::<Vec<i32>>(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::<String>().unwrap());
let b = dir.into_iter().map(|x| x.extract::<String>().unwrap());
assert!(a.eq(b));
}
}

View File

@ -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;

View File

@ -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<T: PyClass> PyCellInner<T> {
/// [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<Target = PyAny>`, so you can also call methods from `PyAny`
/// when you have a `PyCell<T>`.
///
/// # Examples
///
/// In most cases, `PyCell` is hidden behind `#[pymethods]`.
@ -374,6 +378,20 @@ impl<T: PyClass> ToPyObject for &PyCell<T> {
}
}
impl<T: PyClass> AsRef<PyAny> for PyCell<T> {
fn as_ref(&self) -> &PyAny {
unsafe { self.py().from_borrowed_ptr(self.as_ptr()) }
}
}
impl<T: PyClass> Deref for PyCell<T> {
type Target = PyAny;
fn deref(&self) -> &PyAny {
unsafe { self.py().from_borrowed_ptr(self.as_ptr()) }
}
}
impl<T: PyClass + fmt::Debug> fmt::Debug for PyCell<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.try_borrow() {

View File

@ -206,7 +206,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());
})
}
@ -414,7 +414,6 @@ impl<'p> Python<'p> {
#[cfg(test)]
mod test {
use crate::objectprotocol::ObjectProtocol;
use crate::types::{IntoPyDict, PyAny, PyBool, PyInt, PyList};
use crate::Python;

View File

@ -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<T>(&self) -> Result<&T, PyDowncastError>
where
for<'py> T: PyTryFrom<'py>,
{
<T as PyTryFrom>::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<N>(&self, attr_name: N) -> PyResult<bool>
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<N>(&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<N, V>(&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<N>(&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<O>(&self, other: O) -> PyResult<Ordering>
where
O: ToPyObject,
{
unsafe fn do_compare(
py: Python,
a: *mut ffi::PyObject,
b: *mut ffi::PyObject,
) -> PyResult<Ordering> {
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<O>(&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<Py<PyTuple>>,
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<Py<PyTuple>>) -> 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::<Vec<i32>>(py).unwrap(), vec![7, 6, 5, 4, 3]);
/// ```
pub fn call_method(
&self,
name: &str,
args: impl IntoPy<Py<PyTuple>>,
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<Py<PyTuple>>) -> 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<bool> {
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<bool> {
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<K>(&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<K, V>(&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<K>(&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<PyIterator> {
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<D>
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<isize> {
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<usize> {
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::<Vec<i32>>(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::<PyList>()
.unwrap();
let a = obj
.dir()
.into_iter()
.map(|x| x.extract::<String>().unwrap());
let b = dir.into_iter().map(|x| x.extract::<String>().unwrap());
assert!(a.eq(b));
}
}

View File

@ -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;

View File

@ -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<K>(&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};

View File

@ -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;

View File

@ -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<Vec<usize>> = list.iter()?.map(|i| i.and_then(ObjectProtocol::extract::<usize>)).collect();
/// let numbers: PyResult<Vec<usize>> = list.iter()?.map(|i| i.and_then(PyAny::extract::<usize>)).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;

View File

@ -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};

View File

@ -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())
}

View File

@ -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;

View File

@ -38,7 +38,7 @@ macro_rules! int_fits_larger_int {
impl<'source> FromPyObject<'source> for $rust_type {
fn extract(obj: &'source PyAny) -> PyResult<Self> {
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())
}
}

View File

@ -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;

View File

@ -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;

View File

@ -255,4 +255,22 @@ mod test {
let py_string = <PyString as PyTryFrom>::try_from(obj.as_ref(py)).unwrap();
assert_eq!(py_string.to_string_lossy(), "🐈 Hello <20><><EFBFBD>World");
}
#[test]
fn test_debug_string() {
let gil = Python::acquire_gil();
let py = gil.python();
let v = "Hello\n".to_object(py);
let s = <PyString as PyTryFrom>::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 = <PyString as PyTryFrom>::try_from(v.as_ref(py)).unwrap();
assert_eq!(format!("{}", s), "Hello\n");
}
}

View File

@ -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]

View File

@ -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<SubClass> = cell.borrow_mut();
let mut base: PyRefMut<BaseClass> = sub.into_super();
assert_eq!(120, base.value);
base.value = 999;
assert_eq!(999, base.value);
drop(base);
// Repeat for PyRef
let sub: PyRef<SubClass> = cell.borrow();
let base: PyRef<BaseClass> = 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"
);
}

View File

@ -240,7 +240,8 @@ fn inheritance_with_new_methods_with_drop() {
let obj: &PyCell<SubClassWithDrop> = 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));