Merge pull request #3711 from davidhewitt/call-bound
add `call_bound` and `call_method_bound`
This commit is contained in:
commit
58746bb4f9
|
@ -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;
|
||||
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
@ -95,22 +95,30 @@ 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), [`Py<T>::call`]({{#PYO3_DOCS_URL}}/pyo3/struct.py#method.call) is temporarily named `call_bound` (and `call_method` is temporarily `call_method_bound`).
|
||||
|
||||
(This temporary naming is only the case for the `Py<T>` smart pointer. The methods on the `&PyAny` GIL Ref such as `call` have not been given replacements, and the methods on the `Bound<PyAny>` smart pointer such as [`Bound<PyAny>::call`]({#PYO3_DOCS_URL}}/pyo3/prelude/trait.pyanymethods#tymethod.call) already use follow the newest API conventions.)
|
||||
|
||||
</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:
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
@ -1042,12 +1058,15 @@ impl<T> Py<T> {
|
|||
self.bind(py).as_any().call0().map(Bound::unbind)
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
/// 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<'_>,
|
||||
|
@ -1055,6 +1074,26 @@ impl<T> Py<T> {
|
|||
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_bound<N, A>(
|
||||
&self,
|
||||
py: Python<'_>,
|
||||
name: N,
|
||||
args: A,
|
||||
kwargs: Option<&Bound<'_, PyDict>>,
|
||||
) -> PyResult<PyObject>
|
||||
where
|
||||
N: IntoPy<Py<PyString>>,
|
||||
A: IntoPy<Py<PyTuple>>,
|
||||
|
@ -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)
|
||||
|
||||
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()
|
||||
.as_ref(py)
|
||||
.repr()
|
||||
.unwrap()
|
||||
.to_string_lossy(),
|
||||
"{}"
|
||||
.as_ref(py),
|
||||
"{'x': 1}",
|
||||
);
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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>>,
|
||||
|
@ -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(
|
||||
|
@ -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>>,
|
||||
|
@ -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,
|
||||
|
|
|
@ -6,8 +6,8 @@ error[E0277]: the trait bound `PyErr: From<MyError>` is not satisfied
|
|||
|
|
||||
= help: the following other types implement trait `From<T>`:
|
||||
<PyErr as From<PyBorrowError>>
|
||||
<PyErr as From<PyBorrowMutError>>
|
||||
<PyErr as From<std::io::Error>>
|
||||
<PyErr as From<PyBorrowMutError>>
|
||||
<PyErr as From<PyDowncastError<'a>>>
|
||||
<PyErr as From<DowncastError<'_, '_>>>
|
||||
<PyErr as From<DowncastIntoError<'_>>>
|
||||
|
|
Loading…
Reference in a new issue