Skip to content

Commit

Permalink
Remove ObjectProtocol; Add methods to PyAny and use Deref
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed May 8, 2020
1 parent 8d28291 commit a5ebef4
Show file tree
Hide file tree
Showing 32 changed files with 703 additions and 677 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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]
Expand Down
4 changes: 2 additions & 2 deletions guide/src/conversions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions guide/src/function.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
170 changes: 132 additions & 38 deletions guide/src/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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>`

Expand All @@ -75,43 +160,23 @@ 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.

**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)`
```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);

### `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<T>`: `Py::from(obj)`
**Note:** `PyObject` is semantically equivalent to `Py<PyAny>` and might be
merged with it in the future.


### `PyCell<SomeType>`
Expand All @@ -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>`

Expand Down
1 change: 0 additions & 1 deletion pyo3-derive-backend/src/pymethod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),*
];
Expand Down
3 changes: 0 additions & 3 deletions src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
3 changes: 1 addition & 2 deletions src/class/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
11 changes: 0 additions & 11 deletions src/class/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
1 change: 0 additions & 1 deletion src/class/sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading

0 comments on commit a5ebef4

Please sign in to comment.