Skip to content

Commit

Permalink
add call_bound and call_method_bound
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Dec 30, 2023
1 parent 0ca97b5 commit 9ce82d0
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 29 deletions.
4 changes: 2 additions & 2 deletions examples/decorator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl PyCounter {
&self,
py: Python<'_>,
args: &PyTuple,
kwargs: Option<&PyDict>,
kwargs: Option<Bound<'_, PyDict>>,
) -> PyResult<Py<PyAny>> {
let old_count = self.count.get();
let new_count = old_count + 1;
Expand All @@ -51,7 +51,7 @@ impl PyCounter {
println!("{} has been called {} time(s).", name, new_count);

// After doing something, we finally forward the call to the wrapped function
let ret = self.wraps.call(py, args, kwargs)?;
let ret = self.wraps.call_bound(py, args, kwargs.as_ref())?;

// We could do something with the return value of
// the function before returning it
Expand Down
20 changes: 13 additions & 7 deletions guide/src/python_from_rust.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ Any Python-native object reference (such as `&PyAny`, `&PyList`, or `&PyCell<MyC

PyO3 offers two APIs to make function calls:

* [`call`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyAny.html#method.call) - call any callable Python object.
* [`call_method`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyAny.html#method.call_method) - call a method on the Python object.
* [`call`]({{#PYO3_DOCS_URL}}/pyo3/prelude/trait.PyAnyMethods#tymethod.call) - call any callable Python object.
* [`call_method`]({{#PYO3_DOCS_URL}}/pyo3/prelude/trait.PyAnyMethods#tymethod.call_method) - call a method on the Python object.

Both of these APIs take `args` and `kwargs` arguments (for positional and keyword arguments respectively). There are variants for less complex calls:

* [`call1`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyAny.html#method.call1) and [`call_method1`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyAny.html#method.call_method1) to call only with positional `args`.
* [`call0`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyAny.html#method.call0) and [`call_method0`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyAny.html#method.call_method0) to call with no arguments.
* [`call1`]({{#PYO3_DOCS_URL}}/pyo3/prelude/trait.PyAnyMethods#tymethod.call1) and [`call_method1`]({{#PYO3_DOCS_URL}}/pyo3/prelude/trait.PyAnyMethods#tymethod.call_method1) to call only with positional `args`.
* [`call0`]({{#PYO3_DOCS_URL}}/pyo3/prelude/trait.PyAnyMethods#tymethod.call0) and [`call_method0`]({{#PYO3_DOCS_URL}}/pyo3/prelude/trait.PyAnyMethods#tymethod.call_method0) to call with no arguments.

For convenience the [`Py<T>`](types.html#pyt-and-pyobject) smart pointer also exposes these same six API methods, but needs a `Python` token as an additional first argument to prove the GIL is held.

Expand Down Expand Up @@ -95,22 +95,28 @@ fn main() -> PyResult<()> {

// call object with PyDict
let kwargs = [(key1, val1)].into_py_dict(py);
fun.call(py, (), Some(kwargs))?;
fun.call_bound(py, (), Some(&kwargs.as_borrowed()))?;

// pass arguments as Vec
let kwargs = vec![(key1, val1), (key2, val2)];
fun.call(py, (), Some(kwargs.into_py_dict(py)))?;
fun.call_bound(py, (), Some(&kwargs.into_py_dict(py).as_borrowed()))?;

// pass arguments as HashMap
let mut kwargs = HashMap::<&str, i32>::new();
kwargs.insert(key1, 1);
fun.call(py, (), Some(kwargs.into_py_dict(py)))?;
fun.call_bound(py, (), Some(&kwargs.into_py_dict(py).as_borrowed()))?;

Ok(())
})
}
```

<div class="warning">

During PyO3's [migration from "GIL Refs" to the `Bound<T>` smart pointer](./migration.md#migrating-from-the-gil-refs-api-to-boundt), `call` is temporarily named `call_bound`.

</div>

## Executing existing Python code

If you already have some existing Python code that you need to execute from Rust, the following FAQs can help you select the right PyO3 functionality for your situation:
Expand Down
76 changes: 63 additions & 13 deletions src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1016,14 +1016,30 @@ impl<T> Py<T> {
.setattr(attr_name, value.into_py(py).into_bound(py))
}

/// Deprecated form of [`call_bound`][Py::call_bound].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`call` will be replaced by `call_bound` in a future PyO3 version"
)
)]
#[inline]
pub fn call<A>(&self, py: Python<'_>, args: A, kwargs: Option<&PyDict>) -> PyResult<PyObject>
where
A: IntoPy<Py<PyTuple>>,
{
self.call_bound(py, args, kwargs.map(PyDict::as_borrowed).as_deref())
}

/// Calls the object.
///
/// This is equivalent to the Python expression `self(*args, **kwargs)`.
pub fn call(
pub fn call_bound(
&self,
py: Python<'_>,
args: impl IntoPy<Py<PyTuple>>,
kwargs: Option<&PyDict>,
kwargs: Option<&Bound<'_, PyDict>>,
) -> PyResult<PyObject> {
self.bind(py).as_any().call(args, kwargs).map(Bound::unbind)
}
Expand All @@ -1042,18 +1058,41 @@ impl<T> Py<T> {
self.bind(py).as_any().call0().map(Bound::unbind)
}

/// Deprecated form of [`call_method_bound`][Py::call_method_bound].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`call_method` will be replaced by `call_method_bound` in a future PyO3 version"
)
)]
#[inline]
pub fn call_method<N, A>(
&self,
py: Python<'_>,
name: N,
args: A,
kwargs: Option<&PyDict>,
) -> PyResult<PyObject>
where
N: IntoPy<Py<PyString>>,
A: IntoPy<Py<PyTuple>>,
{
self.call_method_bound(py, name, args, kwargs.map(PyDict::as_borrowed).as_deref())
}

/// Calls a method on the object.
///
/// This is equivalent to the Python expression `self.name(*args, **kwargs)`.
///
/// To avoid repeated temporary allocations of Python strings, the [`intern!`](crate::intern)
/// macro can be used to intern `name`.
pub fn call_method<N, A>(
pub fn call_method_bound<N, A>(
&self,
py: Python<'_>,
name: N,
args: A,
kwargs: Option<&PyDict>,
kwargs: Option<&Bound<'_, PyDict>>,
) -> PyResult<PyObject>
where
N: IntoPy<Py<PyString>>,
Expand Down Expand Up @@ -1490,23 +1529,34 @@ impl PyObject {
}

#[cfg(test)]
#[cfg_attr(not(feature = "gil-refs"), allow(deprecated))]
mod tests {
use super::{Bound, Py, PyObject};
use crate::types::{PyDict, PyString};
use crate::types::{dict::IntoPyDict, PyDict, PyString};
use crate::{PyAny, PyNativeType, PyResult, Python, ToPyObject};

#[test]
fn test_call0() {
fn test_call() {
Python::with_gil(|py| {
let obj = py.get_type::<PyDict>().to_object(py);
assert_eq!(
obj.call0(py)
.unwrap()
.as_ref(py)
.repr()

let assert_repr = |obj: &PyAny, expected: &str| {
assert_eq!(obj.repr().unwrap().to_str().unwrap(), expected);
};

assert_repr(obj.call0(py).unwrap().as_ref(py), "{}");
assert_repr(obj.call1(py, ()).unwrap().as_ref(py), "{}");
assert_repr(obj.call(py, (), None).unwrap().as_ref(py), "{}");

assert_repr(
obj.call1(py, ((('x', 1),),)).unwrap().as_ref(py),
"{'x': 1}",
);
assert_repr(
obj.call(py, (), Some([('x', 1)].into_py_dict(py)))
.unwrap()
.to_string_lossy(),
"{}"
.as_ref(py),
"{'x': 1}",
);
})
}
Expand Down
15 changes: 8 additions & 7 deletions src/types/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ impl PyAny {
kwargs: Option<&PyDict>,
) -> PyResult<&PyAny> {
self.as_borrowed()
.call(args, kwargs)
.call(args, kwargs.map(PyDict::as_borrowed).as_deref())
.map(Bound::into_gil_ref)
}

Expand Down Expand Up @@ -547,7 +547,7 @@ impl PyAny {
A: IntoPy<Py<PyTuple>>,
{
self.as_borrowed()
.call_method(name, args, kwargs)
.call_method(name, args, kwargs.map(PyDict::as_borrowed).as_deref())
.map(Bound::into_gil_ref)
}

Expand Down Expand Up @@ -1322,7 +1322,7 @@ pub trait PyAnyMethods<'py> {
fn call(
&self,
args: impl IntoPy<Py<PyTuple>>,
kwargs: Option<&PyDict>,
kwargs: Option<&Bound<'_, PyDict>>,
) -> PyResult<Bound<'py, PyAny>>;

/// Calls the object without arguments.
Expand Down Expand Up @@ -1415,7 +1415,7 @@ pub trait PyAnyMethods<'py> {
&self,
name: N,
args: A,
kwargs: Option<&PyDict>,
kwargs: Option<&Bound<'_, PyDict>>,
) -> PyResult<Bound<'py, PyAny>>
where
N: IntoPy<Py<PyString>>,
Expand Down Expand Up @@ -1970,12 +1970,12 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> {
fn call(
&self,
args: impl IntoPy<Py<PyTuple>>,
kwargs: Option<&PyDict>,
kwargs: Option<&Bound<'_, PyDict>>,
) -> PyResult<Bound<'py, PyAny>> {
fn inner<'py>(
any: &Bound<'py, PyAny>,
args: Bound<'_, PyTuple>,
kwargs: Option<&PyDict>,
kwargs: Option<&Bound<'_, PyDict>>,
) -> PyResult<Bound<'py, PyAny>> {
unsafe {
ffi::PyObject_Call(
Expand Down Expand Up @@ -2015,7 +2015,7 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> {
&self,
name: N,
args: A,
kwargs: Option<&PyDict>,
kwargs: Option<&Bound<'_, PyDict>>,
) -> PyResult<Bound<'py, PyAny>>
where
N: IntoPy<Py<PyString>>,
Expand Down Expand Up @@ -2292,6 +2292,7 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> {
}

#[cfg(test)]
#[cfg_attr(not(feature = "gil-refs"), allow(deprecated))]
mod tests {
use crate::{
basic::CompareOp,
Expand Down

0 comments on commit 9ce82d0

Please sign in to comment.