diff --git a/guide/src/performance.md b/guide/src/performance.md index fb2288dd566..5a57585c4a0 100644 --- a/guide/src/performance.md +++ b/guide/src/performance.md @@ -97,6 +97,19 @@ impl PartialEq for FooBound<'_> { } ``` +## Calling Python callables (`__call__`) +CPython support multiple calling protocols: [`tp_call`] and [`vectorcall`]. [`vectorcall`] is a more efficient protocol unlocking faster calls. +PyO3 will try to dispatch Python `call`s using the [`vectorcall`] calling convention to archive maximum performance if possible and falling back to [`tp_call`] otherwise. +This is implemented using the (internal) `PyCallArgs` trait. It defines how Rust types can be used as Python `call` arguments. This trait is currently implemented for +- Rust tuples, where each member implements `IntoPyObject`, +- `Bound<'_, PyTuple>` +- `Py` +Rust tuples may make use of [`vectorcall`] where as `Bound<'_, PyTuple>` and `Py` can only use [`tp_call`]. For maximum performance prefer using Rust tuples as arguments. + + +[`tp_call`]: https://docs.python.org/3/c-api/call.html#the-tp-call-protocol +[`vectorcall`]: https://docs.python.org/3/c-api/call.html#the-vectorcall-protocol + ## Disable the global reference pool PyO3 uses global mutable state to keep track of deferred reference count updates implied by `impl Drop for Py` being called without the GIL being held. The necessary synchronization to obtain and apply these reference count updates when PyO3-based code next acquires the GIL is somewhat expensive and can become a significant part of the cost of crossing the Python-Rust boundary. diff --git a/newsfragments/4768.added.md b/newsfragments/4768.added.md new file mode 100644 index 00000000000..1ce9c6f5b92 --- /dev/null +++ b/newsfragments/4768.added.md @@ -0,0 +1 @@ +Added `PyCallArgs` trait for arguments into the Python calling protocol. This enabled using a faster calling convention for certain types, improving performance. \ No newline at end of file diff --git a/newsfragments/4768.changed.md b/newsfragments/4768.changed.md new file mode 100644 index 00000000000..6b09fd0e093 --- /dev/null +++ b/newsfragments/4768.changed.md @@ -0,0 +1 @@ +`PyAnyMethods::call` an friends now require `PyCallArgs` for their positional arguments. \ No newline at end of file diff --git a/src/call.rs b/src/call.rs new file mode 100644 index 00000000000..1da1b67530b --- /dev/null +++ b/src/call.rs @@ -0,0 +1,150 @@ +//! Defines how Python calls are dispatched, see [`PyCallArgs`].for more information. + +use crate::ffi_ptr_ext::FfiPtrExt as _; +use crate::types::{PyAnyMethods as _, PyDict, PyString, PyTuple}; +use crate::{ffi, Borrowed, Bound, IntoPyObjectExt as _, Py, PyAny, PyResult}; + +pub(crate) mod private { + use super::*; + + pub trait Sealed {} + + impl Sealed for () {} + impl Sealed for Bound<'_, PyTuple> {} + impl Sealed for Py {} + + pub struct Token; +} + +/// This trait marks types that can be used as arguments to Python function +/// calls. +/// +/// This trait is currently implemented for Rust tuple (up to a size of 12), +/// [`Bound<'py, PyTuple>`] and [`Py`]. Custom types that are +/// convertable to `PyTuple` via `IntoPyObject` need to do so before passing it +/// to `call`. +/// +/// This trait is not intended to used by downstream crates directly. As such it +/// has no publicly available methods and cannot be implemented ouside of +/// `pyo3`. The corresponding public API is available through [`call`] +/// ([`call0`], [`call1`] and friends) on [`PyAnyMethods`]. +/// +/// # What is `PyCallArgs` used for? +/// `PyCallArgs` is used internally in `pyo3` to dispatch the Python calls in +/// the most optimal way for the current build configuration. Certain types, +/// such as Rust tuples, do allow the usage of a faster calling convention of +/// the Python interpreter (if available). More types that may take advantage +/// from this may be added in the future. +/// +/// [`call0`]: crate::types::PyAnyMethods::call0 +/// [`call1`]: crate::types::PyAnyMethods::call1 +/// [`call`]: crate::types::PyAnyMethods::call +/// [`PyAnyMethods`]: crate::types::PyAnyMethods +#[cfg_attr( + diagnostic_namespace, + diagnostic::on_unimplemented( + message = "`{Self}` cannot used as a Python `call` argument", + note = "`PyCallArgs` is implemented for Rust tuples, `Bound<'py, PyTuple>` and `Py`", + note = "if your type is convertable to `PyTuple` via `IntoPyObject`, call `.into_pyobject(py)` manually", + note = "if you meant to pass the type as a single argument, wrap it in a 1-tuple, `(,)`" + ) +)] +pub trait PyCallArgs<'py>: Sized + private::Sealed { + #[doc(hidden)] + fn call( + self, + function: Borrowed<'_, 'py, PyAny>, + kwargs: Borrowed<'_, 'py, PyDict>, + token: private::Token, + ) -> PyResult>; + + #[doc(hidden)] + fn call_positional( + self, + function: Borrowed<'_, 'py, PyAny>, + token: private::Token, + ) -> PyResult>; + + #[doc(hidden)] + fn call_method_positional( + self, + object: Borrowed<'_, 'py, PyAny>, + method_name: Borrowed<'_, 'py, PyString>, + _: private::Token, + ) -> PyResult> { + object + .getattr(method_name) + .and_then(|method| method.call1(self)) + } +} + +impl<'py> PyCallArgs<'py> for () { + fn call( + self, + function: Borrowed<'_, 'py, PyAny>, + kwargs: Borrowed<'_, 'py, PyDict>, + token: private::Token, + ) -> PyResult> { + let args = self.into_pyobject_or_pyerr(function.py())?; + args.call(function, kwargs, token) + } + + fn call_positional( + self, + function: Borrowed<'_, 'py, PyAny>, + token: private::Token, + ) -> PyResult> { + let args = self.into_pyobject_or_pyerr(function.py())?; + args.call_positional(function, token) + } +} + +impl<'py> PyCallArgs<'py> for Bound<'py, PyTuple> { + fn call( + self, + function: Borrowed<'_, 'py, PyAny>, + kwargs: Borrowed<'_, '_, PyDict>, + _: private::Token, + ) -> PyResult> { + unsafe { + ffi::PyObject_Call(function.as_ptr(), self.as_ptr(), kwargs.as_ptr()) + .assume_owned_or_err(function.py()) + } + } + + fn call_positional( + self, + function: Borrowed<'_, 'py, PyAny>, + _: private::Token, + ) -> PyResult> { + unsafe { + ffi::PyObject_Call(function.as_ptr(), self.as_ptr(), std::ptr::null_mut()) + .assume_owned_or_err(function.py()) + } + } +} + +impl<'py> PyCallArgs<'py> for Py { + fn call( + self, + function: Borrowed<'_, 'py, PyAny>, + kwargs: Borrowed<'_, '_, PyDict>, + _: private::Token, + ) -> PyResult> { + unsafe { + ffi::PyObject_Call(function.as_ptr(), self.as_ptr(), kwargs.as_ptr()) + .assume_owned_or_err(function.py()) + } + } + + fn call_positional( + self, + function: Borrowed<'_, 'py, PyAny>, + _: private::Token, + ) -> PyResult> { + unsafe { + ffi::PyObject_Call(function.as_ptr(), self.as_ptr(), std::ptr::null_mut()) + .assume_owned_or_err(function.py()) + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 46a2fd53d32..e5146c81c00 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -428,6 +428,7 @@ mod internal_tricks; mod internal; pub mod buffer; +pub mod call; pub mod conversion; mod conversions; #[cfg(feature = "experimental-async")] diff --git a/src/types/any.rs b/src/types/any.rs index d060c187631..1ebc5d40a0b 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -1,3 +1,4 @@ +use crate::call::PyCallArgs; use crate::class::basic::CompareOp; use crate::conversion::{AsPyPointer, FromPyObjectBound, IntoPyObject}; use crate::err::{DowncastError, DowncastIntoError, PyErr, PyResult}; @@ -10,7 +11,7 @@ use crate::py_result_ext::PyResultExt; use crate::type_object::{PyTypeCheck, PyTypeInfo}; #[cfg(not(any(PyPy, GraalPy)))] use crate::types::PySuper; -use crate::types::{PyDict, PyIterator, PyList, PyString, PyTuple, PyType}; +use crate::types::{PyDict, PyIterator, PyList, PyString, PyType}; use crate::{err, ffi, Borrowed, BoundObject, IntoPyObjectExt, Python}; use std::cell::UnsafeCell; use std::cmp::Ordering; @@ -436,7 +437,7 @@ pub trait PyAnyMethods<'py>: crate::sealed::Sealed { /// ``` fn call(&self, args: A, kwargs: Option<&Bound<'py, PyDict>>) -> PyResult> where - A: IntoPyObject<'py, Target = PyTuple>; + A: PyCallArgs<'py>; /// Calls the object without arguments. /// @@ -491,7 +492,7 @@ pub trait PyAnyMethods<'py>: crate::sealed::Sealed { /// ``` fn call1(&self, args: A) -> PyResult> where - A: IntoPyObject<'py, Target = PyTuple>; + A: PyCallArgs<'py>; /// Calls a method on the object. /// @@ -538,7 +539,7 @@ pub trait PyAnyMethods<'py>: crate::sealed::Sealed { ) -> PyResult> where N: IntoPyObject<'py, Target = PyString>, - A: IntoPyObject<'py, Target = PyTuple>; + A: PyCallArgs<'py>; /// Calls a method on the object without arguments. /// @@ -614,7 +615,7 @@ pub trait PyAnyMethods<'py>: crate::sealed::Sealed { fn call_method1(&self, name: N, args: A) -> PyResult> where N: IntoPyObject<'py, Target = PyString>, - A: IntoPyObject<'py, Target = PyTuple>; + A: PyCallArgs<'py>; /// Returns whether the object is considered to be true. /// @@ -1209,25 +1210,17 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { fn call(&self, args: A, kwargs: Option<&Bound<'py, PyDict>>) -> PyResult> where - A: IntoPyObject<'py, Target = PyTuple>, + A: PyCallArgs<'py>, { - fn inner<'py>( - any: &Bound<'py, PyAny>, - args: Borrowed<'_, 'py, PyTuple>, - kwargs: Option<&Bound<'py, PyDict>>, - ) -> PyResult> { - unsafe { - ffi::PyObject_Call( - any.as_ptr(), - args.as_ptr(), - kwargs.map_or(std::ptr::null_mut(), |dict| dict.as_ptr()), - ) - .assume_owned_or_err(any.py()) - } + if let Some(kwargs) = kwargs { + args.call( + self.as_borrowed(), + kwargs.as_borrowed(), + crate::call::private::Token, + ) + } else { + args.call_positional(self.as_borrowed(), crate::call::private::Token) } - - let py = self.py(); - inner(self, args.into_pyobject_or_pyerr(py)?.as_borrowed(), kwargs) } #[inline] @@ -1237,9 +1230,9 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { fn call1(&self, args: A) -> PyResult> where - A: IntoPyObject<'py, Target = PyTuple>, + A: PyCallArgs<'py>, { - self.call(args, None) + args.call_positional(self.as_borrowed(), crate::call::private::Token) } #[inline] @@ -1251,10 +1244,14 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { ) -> PyResult> where N: IntoPyObject<'py, Target = PyString>, - A: IntoPyObject<'py, Target = PyTuple>, + A: PyCallArgs<'py>, { - self.getattr(name) - .and_then(|method| method.call(args, kwargs)) + if kwargs.is_none() { + self.call_method1(name, args) + } else { + self.getattr(name) + .and_then(|method| method.call(args, kwargs)) + } } #[inline] @@ -1273,9 +1270,14 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { fn call_method1(&self, name: N, args: A) -> PyResult> where N: IntoPyObject<'py, Target = PyString>, - A: IntoPyObject<'py, Target = PyTuple>, + A: PyCallArgs<'py>, { - self.call_method(name, args, None) + let name = name.into_pyobject_or_pyerr(self.py())?; + args.call_method_positional( + self.as_borrowed(), + name.as_borrowed(), + crate::call::private::Token, + ) } fn is_truthy(&self) -> PyResult { diff --git a/src/types/tuple.rs b/src/types/tuple.rs index 216a376d833..a5de938140a 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -566,6 +566,255 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+ } } + impl<'py, $($T),+> crate::call::private::Sealed for ($($T,)+) where $($T: IntoPyObject<'py>,)+ {} + impl<'py, $($T),+> crate::call::PyCallArgs<'py> for ($($T,)+) + where + $($T: IntoPyObject<'py>,)+ + { + #[cfg(all(Py_3_9, not(any(PyPy, GraalPy, Py_LIMITED_API))))] + fn call( + self, + function: Borrowed<'_, 'py, PyAny>, + kwargs: Borrowed<'_, '_, crate::types::PyDict>, + _: crate::call::private::Token, + ) -> PyResult> { + let py = function.py(); + // We need this to drop the arguments correctly. + let args_bound = [$(self.$n.into_bound_py_any(py)?,)*]; + // Prepend one null argument for `PY_VECTORCALL_ARGUMENTS_OFFSET`. + let mut args = [std::ptr::null_mut(), $(args_bound[$n].as_ptr()),*]; + unsafe { + ffi::PyObject_VectorcallDict( + function.as_ptr(), + args.as_mut_ptr().add(1), + $length + ffi::PY_VECTORCALL_ARGUMENTS_OFFSET, + kwargs.as_ptr(), + ) + .assume_owned_or_err(py) + } + } + + #[cfg(all(Py_3_9, not(any(PyPy, GraalPy, Py_LIMITED_API))))] + fn call_positional( + self, + function: Borrowed<'_, 'py, PyAny>, + _: crate::call::private::Token, + ) -> PyResult> { + let py = function.py(); + // We need this to drop the arguments correctly. + let args_bound = [$(self.$n.into_bound_py_any(py)?,)*]; + if $length == 1 { + unsafe { + ffi::PyObject_CallOneArg( + function.as_ptr(), + args_bound[0].as_ptr() + ) + .assume_owned_or_err(py) + } + } else { + // Prepend one null argument for `PY_VECTORCALL_ARGUMENTS_OFFSET`. + let mut args = [std::ptr::null_mut(), $(args_bound[$n].as_ptr()),*]; + unsafe { + ffi::PyObject_Vectorcall( + function.as_ptr(), + args.as_mut_ptr().add(1), + $length + ffi::PY_VECTORCALL_ARGUMENTS_OFFSET, + std::ptr::null_mut(), + ) + .assume_owned_or_err(py) + } + } + } + + #[cfg(all(Py_3_9, not(any(PyPy, GraalPy, Py_LIMITED_API))))] + fn call_method_positional( + self, + object: Borrowed<'_, 'py, PyAny>, + method_name: Borrowed<'_, 'py, crate::types::PyString>, + _: crate::call::private::Token, + ) -> PyResult> { + let py = object.py(); + // We need this to drop the arguments correctly. + let args_bound = [$(self.$n.into_bound_py_any(py)?,)*]; + if $length == 1 { + unsafe { + ffi::PyObject_CallMethodOneArg( + object.as_ptr(), + method_name.as_ptr(), + args_bound[0].as_ptr(), + ) + .assume_owned_or_err(py) + } + } else { + let mut args = [object.as_ptr(), $(args_bound[$n].as_ptr()),*]; + unsafe { + ffi::PyObject_VectorcallMethod( + method_name.as_ptr(), + args.as_mut_ptr(), + // +1 for the receiver. + 1 + $length + ffi::PY_VECTORCALL_ARGUMENTS_OFFSET, + std::ptr::null_mut(), + ) + .assume_owned_or_err(py) + } + } + } + + #[cfg(not(all(Py_3_9, not(any(PyPy, GraalPy, Py_LIMITED_API)))))] + fn call( + self, + function: Borrowed<'_, 'py, PyAny>, + kwargs: Borrowed<'_, 'py, crate::types::PyDict>, + token: crate::call::private::Token, + ) -> PyResult> { + self.into_pyobject_or_pyerr(function.py())?.call(function, kwargs, token) + } + + #[cfg(not(all(Py_3_9, not(any(PyPy, GraalPy, Py_LIMITED_API)))))] + fn call_positional( + self, + function: Borrowed<'_, 'py, PyAny>, + token: crate::call::private::Token, + ) -> PyResult> { + self.into_pyobject_or_pyerr(function.py())?.call_positional(function, token) + } + + #[cfg(not(all(Py_3_9, not(any(PyPy, GraalPy, Py_LIMITED_API)))))] + fn call_method_positional( + self, + object: Borrowed<'_, 'py, PyAny>, + method_name: Borrowed<'_, 'py, crate::types::PyString>, + token: crate::call::private::Token, + ) -> PyResult> { + self.into_pyobject_or_pyerr(object.py())?.call_method_positional(object, method_name, token) + } + } + + impl<'a, 'py, $($T),+> crate::call::private::Sealed for &'a ($($T,)+) where $(&'a $T: IntoPyObject<'py>,)+ $($T: 'a,)+ /*MSRV */ {} + impl<'a, 'py, $($T),+> crate::call::PyCallArgs<'py> for &'a ($($T,)+) + where + $(&'a $T: IntoPyObject<'py>,)+ + $($T: 'a,)+ // MSRV + { + #[cfg(all(Py_3_9, not(any(PyPy, GraalPy, Py_LIMITED_API))))] + fn call( + self, + function: Borrowed<'_, 'py, PyAny>, + kwargs: Borrowed<'_, '_, crate::types::PyDict>, + _: crate::call::private::Token, + ) -> PyResult> { + let py = function.py(); + // We need this to drop the arguments correctly. + let args_bound = [$(self.$n.into_bound_py_any(py)?,)*]; + // Prepend one null argument for `PY_VECTORCALL_ARGUMENTS_OFFSET`. + let mut args = [std::ptr::null_mut(), $(args_bound[$n].as_ptr()),*]; + unsafe { + ffi::PyObject_VectorcallDict( + function.as_ptr(), + args.as_mut_ptr().add(1), + $length + ffi::PY_VECTORCALL_ARGUMENTS_OFFSET, + kwargs.as_ptr(), + ) + .assume_owned_or_err(py) + } + } + + #[cfg(all(Py_3_9, not(any(PyPy, GraalPy, Py_LIMITED_API))))] + fn call_positional( + self, + function: Borrowed<'_, 'py, PyAny>, + _: crate::call::private::Token, + ) -> PyResult> { + let py = function.py(); + // We need this to drop the arguments correctly. + let args_bound = [$(self.$n.into_bound_py_any(py)?,)*]; + if $length == 1 { + unsafe { + ffi::PyObject_CallOneArg( + function.as_ptr(), + args_bound[0].as_ptr() + ) + .assume_owned_or_err(py) + } + } else { + // Prepend one null argument for `PY_VECTORCALL_ARGUMENTS_OFFSET`. + let mut args = [std::ptr::null_mut(), $(args_bound[$n].as_ptr()),*]; + unsafe { + ffi::PyObject_Vectorcall( + function.as_ptr(), + args.as_mut_ptr().add(1), + $length + ffi::PY_VECTORCALL_ARGUMENTS_OFFSET, + std::ptr::null_mut(), + ) + .assume_owned_or_err(py) + } + } + } + + #[cfg(all(Py_3_9, not(any(PyPy, GraalPy, Py_LIMITED_API))))] + fn call_method_positional( + self, + object: Borrowed<'_, 'py, PyAny>, + method_name: Borrowed<'_, 'py, crate::types::PyString>, + _: crate::call::private::Token, + ) -> PyResult> { + let py = object.py(); + // We need this to drop the arguments correctly. + let args_bound = [$(self.$n.into_bound_py_any(py)?,)*]; + if $length == 1 { + unsafe { + ffi::PyObject_CallMethodOneArg( + object.as_ptr(), + method_name.as_ptr(), + args_bound[0].as_ptr(), + ) + .assume_owned_or_err(py) + } + } else { + let mut args = [object.as_ptr(), $(args_bound[$n].as_ptr()),*]; + unsafe { + ffi::PyObject_VectorcallMethod( + method_name.as_ptr(), + args.as_mut_ptr(), + // +1 for the receiver. + 1 + $length + ffi::PY_VECTORCALL_ARGUMENTS_OFFSET, + std::ptr::null_mut(), + ) + .assume_owned_or_err(py) + } + } + } + + #[cfg(not(all(Py_3_9, not(any(PyPy, GraalPy, Py_LIMITED_API)))))] + fn call( + self, + function: Borrowed<'_, 'py, PyAny>, + kwargs: Borrowed<'_, 'py, crate::types::PyDict>, + token: crate::call::private::Token, + ) -> PyResult> { + self.into_pyobject_or_pyerr(function.py())?.call(function, kwargs, token) + } + + #[cfg(not(all(Py_3_9, not(any(PyPy, GraalPy, Py_LIMITED_API)))))] + fn call_positional( + self, + function: Borrowed<'_, 'py, PyAny>, + token: crate::call::private::Token, + ) -> PyResult> { + self.into_pyobject_or_pyerr(function.py())?.call_positional(function, token) + } + + #[cfg(not(all(Py_3_9, not(any(PyPy, GraalPy, Py_LIMITED_API)))))] + fn call_method_positional( + self, + object: Borrowed<'_, 'py, PyAny>, + method_name: Borrowed<'_, 'py, crate::types::PyString>, + token: crate::call::private::Token, + ) -> PyResult> { + self.into_pyobject_or_pyerr(object.py())?.call_method_positional(object, method_name, token) + } + } + #[allow(deprecated)] impl <$($T: IntoPy),+> IntoPy> for ($($T,)+) { fn into_py(self, py: Python<'_>) -> Py { diff --git a/tests/test_compile_error.rs b/tests/test_compile_error.rs index 05d9ccd6d2e..10b692a604c 100644 --- a/tests/test_compile_error.rs +++ b/tests/test_compile_error.rs @@ -20,6 +20,7 @@ fn test_compile_errors() { t.compile_fail("tests/ui/invalid_pymethod_enum.rs"); t.compile_fail("tests/ui/invalid_pymethod_names.rs"); t.compile_fail("tests/ui/invalid_pymodule_args.rs"); + t.compile_fail("tests/ui/invalid_pycallargs.rs"); t.compile_fail("tests/ui/reject_generics.rs"); t.compile_fail("tests/ui/invalid_closure.rs"); t.compile_fail("tests/ui/pyclass_send.rs"); diff --git a/tests/ui/invalid_pycallargs.rs b/tests/ui/invalid_pycallargs.rs new file mode 100644 index 00000000000..b77dbb20dcb --- /dev/null +++ b/tests/ui/invalid_pycallargs.rs @@ -0,0 +1,8 @@ +use pyo3::prelude::*; + +fn main() { + Python::with_gil(|py| { + let any = py.None().into_bound(py); + any.call1("foo"); + }) +} diff --git a/tests/ui/invalid_pycallargs.stderr b/tests/ui/invalid_pycallargs.stderr new file mode 100644 index 00000000000..93c0bc19b7f --- /dev/null +++ b/tests/ui/invalid_pycallargs.stderr @@ -0,0 +1,29 @@ +error[E0277]: `&str` cannot used as a Python `call` argument + --> tests/ui/invalid_pycallargs.rs:6:19 + | +6 | any.call1("foo"); + | ----- ^^^^^ the trait `PyCallArgs<'_>` is not implemented for `&str` + | | + | required by a bound introduced by this call + | + = note: `PyCallArgs` is implemented for Rust tuples, `Bound<'py, PyTuple>` and `Py` + = note: if your type is convertable to `PyTuple` via `IntoPyObject`, call `.into_pyobject(py)` manually + = note: if you meant to pass the type as a single argument, wrap it in a 1-tuple, `(,)` + = help: the following other types implement trait `PyCallArgs<'py>`: + &'a (T0, T1) + &'a (T0, T1, T2) + &'a (T0, T1, T2, T3) + &'a (T0, T1, T2, T3, T4) + &'a (T0, T1, T2, T3, T4, T5) + &'a (T0, T1, T2, T3, T4, T5, T6) + &'a (T0, T1, T2, T3, T4, T5, T6, T7) + &'a (T0, T1, T2, T3, T4, T5, T6, T7, T8) + and $N others +note: required by a bound in `call1` + --> src/types/any.rs + | + | fn call1(&self, args: A) -> PyResult> + | ----- required by a bound in this associated function + | where + | A: PyCallArgs<'py>; + | ^^^^^^^^^^^^^^^ required by this bound in `PyAnyMethods::call1`