Merge pull request #1619 from birkenfeld/fastcall

Implement METH_FASTCALL for pyfunctions.
This commit is contained in:
David Hewitt 2021-06-05 12:32:16 +01:00 committed by GitHub
commit a5810eaffa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 215 additions and 127 deletions

View File

@ -48,6 +48,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Reduce LLVM line counts to improve compilation times. [#1604](https://github.com/PyO3/pyo3/pull/1604) - Reduce LLVM line counts to improve compilation times. [#1604](https://github.com/PyO3/pyo3/pull/1604)
- Deprecate string-literal second argument to `#[pyfn(m, "name")]`. [#1610](https://github.com/PyO3/pyo3/pull/1610) - Deprecate string-literal second argument to `#[pyfn(m, "name")]`. [#1610](https://github.com/PyO3/pyo3/pull/1610)
- No longer call `PyEval_InitThreads()` in `#[pymodule]` init code. [#1630](https://github.com/PyO3/pyo3/pull/1630) - No longer call `PyEval_InitThreads()` in `#[pymodule]` init code. [#1630](https://github.com/PyO3/pyo3/pull/1630)
- Use `METH_FASTCALL` argument passing convention, when possible, to improve `#[pyfunction]` performance. [#1619](https://github.com/PyO3/pyo3/pull/1619)
### Removed ### Removed
- Remove deprecated exception names `BaseException` etc. [#1426](https://github.com/PyO3/pyo3/pull/1426) - Remove deprecated exception names `BaseException` etc. [#1426](https://github.com/PyO3/pyo3/pull/1426)
@ -63,6 +64,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- `PyModuleDef_INIT` [#1630](https://github.com/PyO3/pyo3/pull/1630) - `PyModuleDef_INIT` [#1630](https://github.com/PyO3/pyo3/pull/1630)
- Remove `__doc__` from module's `__all__`. [#1509](https://github.com/PyO3/pyo3/pull/1509) - Remove `__doc__` from module's `__all__`. [#1509](https://github.com/PyO3/pyo3/pull/1509)
- Remove `PYO3_CROSS_INCLUDE_DIR` environment variable and the associated C header parsing functionality. [#1521](https://github.com/PyO3/pyo3/pull/1521) - Remove `PYO3_CROSS_INCLUDE_DIR` environment variable and the associated C header parsing functionality. [#1521](https://github.com/PyO3/pyo3/pull/1521)
- Remove `raw_pycfunction!` macro. [#1619](https://github.com/PyO3/pyo3/pull/1619)
### Fixed ### Fixed
- Remove FFI definition `PyCFunction_ClearFreeList` for Python 3.9 and later. [#1425](https://github.com/PyO3/pyo3/pull/1425) - Remove FFI definition `PyCFunction_ClearFreeList` for Python 3.9 and later. [#1425](https://github.com/PyO3/pyo3/pull/1425)

View File

@ -279,12 +279,11 @@ in the function body.
## Accessing the FFI functions ## Accessing the FFI functions
In order to make Rust functions callable from Python, PyO3 generates a In order to make Rust functions callable from Python, PyO3 generates an `extern "C"`
`extern "C" Fn(slf: *mut PyObject, args: *mut PyObject, kwargs: *mut PyObject) -> *mut Pyobject` function whose exact signature depends on the Rust signature. (PyO3 chooses the optimal
function and embeds the call to the Rust function inside this FFI-wrapper function. This Python argument passing convention.) It then embeds the call to the Rust function inside this
wrapper handles extraction of the regular arguments and the keyword arguments from the input FFI-wrapper function. This wrapper handles extraction of the regular arguments and the keyword
`PyObjects`. Since this function is not user-defined but required to build a `PyCFunction`, PyO3 arguments from the input `PyObject`s.
offers the `raw_pycfunction!()` macro to get the identifier of this generated wrapper.
The `wrap_pyfunction` macro can be used to directly get a `PyCFunction` given a The `wrap_pyfunction` macro can be used to directly get a `PyCFunction` given a
`#[pyfunction]` and a `PyModule`: `wrap_pyfunction!(rust_fun, module)`. `#[pyfunction]` and a `PyModule`: `wrap_pyfunction!(rust_fun, module)`.

View File

@ -158,6 +158,28 @@ pub fn parse_method_receiver(arg: &syn::FnArg) -> syn::Result<SelfType> {
} }
impl<'a> FnSpec<'a> { impl<'a> FnSpec<'a> {
/// Determine if the function gets passed a *args tuple or **kwargs dict.
pub fn accept_args_kwargs(&self) -> (bool, bool) {
let (mut accept_args, mut accept_kwargs) = (false, false);
for s in &self.attrs {
match s {
Argument::VarArgs(_) => accept_args = true,
Argument::KeywordArgs(_) => accept_kwargs = true,
_ => continue,
}
}
(accept_args, accept_kwargs)
}
/// Return true if the function can use METH_FASTCALL.
///
/// This is true on Py3.7+, except with the stable ABI (abi3).
pub fn can_use_fastcall(&self) -> bool {
cfg!(all(Py_3_7, not(Py_LIMITED_API)))
}
/// Parser function signature and function attributes /// Parser function signature and function attributes
pub fn parse( pub fn parse(
sig: &'a mut syn::Signature, sig: &'a mut syn::Signature,

View File

@ -401,25 +401,29 @@ pub fn impl_wrap_pyfunction(
let name = &func.sig.ident; let name = &func.sig.ident;
let wrapper_ident = format_ident!("__pyo3_raw_{}", name); let wrapper_ident = format_ident!("__pyo3_raw_{}", name);
let wrapper = function_c_wrapper(name, &wrapper_ident, &spec, options.pass_module)?; let wrapper = function_c_wrapper(name, &wrapper_ident, &spec, options.pass_module)?;
let methoddef = if spec.args.is_empty() { let (methoddef_meth, cfunc_variant) = if spec.args.is_empty() {
quote!(noargs) (quote!(noargs), quote!(PyCFunction))
} else if spec.can_use_fastcall() {
(
quote!(fastcall_cfunction_with_keywords),
quote!(PyCFunctionFastWithKeywords),
)
} else { } else {
quote!(cfunction_with_keywords) (
}; quote!(cfunction_with_keywords),
let cfunc = if spec.args.is_empty() { quote!(PyCFunctionWithKeywords),
quote!(PyCFunction) )
} else {
quote!(PyCFunctionWithKeywords)
}; };
let wrapped_pyfunction = quote! { let wrapped_pyfunction = quote! {
#wrapper #wrapper
pub(crate) fn #function_wrapper_ident<'a>( pub(crate) fn #function_wrapper_ident<'a>(
args: impl Into<pyo3::derive_utils::PyFunctionArguments<'a>> args: impl Into<pyo3::derive_utils::PyFunctionArguments<'a>>
) -> pyo3::PyResult<&'a pyo3::types::PyCFunction> { ) -> pyo3::PyResult<&'a pyo3::types::PyCFunction> {
pyo3::types::PyCFunction::internal_new( pyo3::types::PyCFunction::internal_new(
pyo3::class::methods::PyMethodDef:: #methoddef ( pyo3::class::methods::PyMethodDef:: #methoddef_meth (
#python_name, #python_name,
pyo3::class::methods:: #cfunc (#wrapper_ident), pyo3::class::methods:: #cfunc_variant (#wrapper_ident),
#doc, #doc,
), ),
args.into(), args.into(),
@ -469,8 +473,36 @@ fn function_c_wrapper(
}) })
} }
}) })
} else if spec.can_use_fastcall() {
let body = impl_arg_params(spec, None, cb, &py, true)?;
Ok(quote! {
unsafe extern "C" fn #wrapper_ident(
_slf: *mut pyo3::ffi::PyObject,
_args: *const *mut pyo3::ffi::PyObject,
_nargs: pyo3::ffi::Py_ssize_t,
_kwnames: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{
pyo3::callback::handle_panic(|#py| {
#slf_module
// _nargs is the number of positional arguments in the _args array,
// the number of KW args is given by the length of _kwnames
let _kwnames: Option<&pyo3::types::PyTuple> = #py.from_borrowed_ptr_or_opt(_kwnames);
// Safety: &PyAny has the same memory layout as `*mut ffi::PyObject`
let _args = _args as *const &pyo3::PyAny;
let _kwargs = if let Some(kwnames) = _kwnames {
std::slice::from_raw_parts(_args.offset(_nargs), kwnames.len())
} else {
&[]
};
let _args = std::slice::from_raw_parts(_args, _nargs as usize);
#body
})
}
})
} else { } else {
let body = impl_arg_params(spec, None, cb, &py)?; let body = impl_arg_params(spec, None, cb, &py, false)?;
Ok(quote! { Ok(quote! {
unsafe extern "C" fn #wrapper_ident( unsafe extern "C" fn #wrapper_ident(
_slf: *mut pyo3::ffi::PyObject, _slf: *mut pyo3::ffi::PyObject,
@ -482,7 +514,6 @@ fn function_c_wrapper(
#slf_module #slf_module
let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args); let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs); let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
#body #body
}) })
} }

View File

@ -93,7 +93,7 @@ pub fn impl_wrap_cfunction_with_keywords(
let body = impl_call(cls, &spec); let body = impl_call(cls, &spec);
let slf = self_ty.receiver(cls); let slf = self_ty.receiver(cls);
let py = syn::Ident::new("_py", Span::call_site()); let py = syn::Ident::new("_py", Span::call_site());
let body = impl_arg_params(&spec, Some(cls), body, &py)?; let body = impl_arg_params(&spec, Some(cls), body, &py, false)?;
let deprecations = &spec.deprecations; let deprecations = &spec.deprecations;
Ok(quote! {{ Ok(quote! {{
unsafe extern "C" fn __wrap( unsafe extern "C" fn __wrap(
@ -114,6 +114,42 @@ pub fn impl_wrap_cfunction_with_keywords(
}}) }})
} }
/// Generate function wrapper for PyCFunctionFastWithKeywords
pub fn impl_wrap_fastcall_cfunction_with_keywords(
cls: &syn::Type,
spec: &FnSpec<'_>,
self_ty: &SelfType,
) -> Result<TokenStream> {
let body = impl_call(cls, &spec);
let slf = self_ty.receiver(cls);
let py = syn::Ident::new("_py", Span::call_site());
let body = impl_arg_params(&spec, Some(cls), body, &py, true)?;
Ok(quote! {{
unsafe extern "C" fn __wrap(
_slf: *mut pyo3::ffi::PyObject,
_args: *const *mut pyo3::ffi::PyObject,
_nargs: pyo3::ffi::Py_ssize_t,
_kwnames: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{
pyo3::callback::handle_panic(|#py| {
#slf
let _kwnames: Option<&pyo3::types::PyTuple> = #py.from_borrowed_ptr_or_opt(_kwnames);
// Safety: &PyAny has the same memory layout as `*mut ffi::PyObject`
let _args = _args as *const &pyo3::PyAny;
let _kwargs = if let Some(kwnames) = _kwnames {
std::slice::from_raw_parts(_args.offset(_nargs), kwnames.len())
} else {
&[]
};
let _args = std::slice::from_raw_parts(_args, _nargs as usize);
#body
})
}
__wrap
}})
}
/// Generate function wrapper PyCFunction /// Generate function wrapper PyCFunction
pub fn impl_wrap_noargs(cls: &syn::Type, spec: &FnSpec<'_>, self_ty: &SelfType) -> TokenStream { pub fn impl_wrap_noargs(cls: &syn::Type, spec: &FnSpec<'_>, self_ty: &SelfType) -> TokenStream {
let body = impl_call(cls, &spec); let body = impl_call(cls, &spec);
@ -142,7 +178,7 @@ pub fn impl_wrap_new(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<TokenStream>
let names: Vec<syn::Ident> = get_arg_names(&spec); let names: Vec<syn::Ident> = get_arg_names(&spec);
let cb = quote! { #cls::#name(#(#names),*) }; let cb = quote! { #cls::#name(#(#names),*) };
let py = syn::Ident::new("_py", Span::call_site()); let py = syn::Ident::new("_py", Span::call_site());
let body = impl_arg_params(spec, Some(cls), cb, &py)?; let body = impl_arg_params(spec, Some(cls), cb, &py, false)?;
let deprecations = &spec.deprecations; let deprecations = &spec.deprecations;
Ok(quote! {{ Ok(quote! {{
#[allow(unused_mut)] #[allow(unused_mut)]
@ -172,7 +208,7 @@ pub fn impl_wrap_class(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<TokenStream
let names: Vec<syn::Ident> = get_arg_names(&spec); let names: Vec<syn::Ident> = get_arg_names(&spec);
let cb = quote! { pyo3::callback::convert(_py, #cls::#name(&_cls, #(#names),*)) }; let cb = quote! { pyo3::callback::convert(_py, #cls::#name(&_cls, #(#names),*)) };
let py = syn::Ident::new("_py", Span::call_site()); let py = syn::Ident::new("_py", Span::call_site());
let body = impl_arg_params(spec, Some(cls), cb, &py)?; let body = impl_arg_params(spec, Some(cls), cb, &py, false)?;
let deprecations = &spec.deprecations; let deprecations = &spec.deprecations;
Ok(quote! {{ Ok(quote! {{
#[allow(unused_mut)] #[allow(unused_mut)]
@ -200,7 +236,7 @@ pub fn impl_wrap_static(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<TokenStrea
let names: Vec<syn::Ident> = get_arg_names(&spec); let names: Vec<syn::Ident> = get_arg_names(&spec);
let cb = quote! { pyo3::callback::convert(_py, #cls::#name(#(#names),*)) }; let cb = quote! { pyo3::callback::convert(_py, #cls::#name(#(#names),*)) };
let py = syn::Ident::new("_py", Span::call_site()); let py = syn::Ident::new("_py", Span::call_site());
let body = impl_arg_params(spec, Some(cls), cb, &py)?; let body = impl_arg_params(spec, Some(cls), cb, &py, false)?;
let deprecations = &spec.deprecations; let deprecations = &spec.deprecations;
Ok(quote! {{ Ok(quote! {{
#[allow(unused_mut)] #[allow(unused_mut)]
@ -379,6 +415,7 @@ pub fn impl_arg_params(
self_: Option<&syn::Type>, self_: Option<&syn::Type>,
body: TokenStream, body: TokenStream,
py: &syn::Ident, py: &syn::Ident,
fastcall: bool,
) -> Result<TokenStream> { ) -> Result<TokenStream> {
if spec.args.is_empty() { if spec.args.is_empty() {
return Ok(body); return Ok(body);
@ -428,16 +465,7 @@ pub fn impl_arg_params(
)?); )?);
} }
let (mut accept_args, mut accept_kwargs) = (false, false); let (accept_args, accept_kwargs) = spec.accept_args_kwargs();
for s in spec.attrs.iter() {
use crate::pyfunction::Argument;
match s {
Argument::VarArgs(_) => accept_args = true,
Argument::KeywordArgs(_) => accept_kwargs = true,
_ => continue,
}
}
let cls_name = if let Some(cls) = self_ { let cls_name = if let Some(cls) = self_ {
quote! { Some(<#cls as pyo3::type_object::PyTypeInfo>::NAME) } quote! { Some(<#cls as pyo3::type_object::PyTypeInfo>::NAME) }
@ -446,6 +474,24 @@ pub fn impl_arg_params(
}; };
let python_name = &spec.python_name; let python_name = &spec.python_name;
let (args_to_extract, kwargs_to_extract) = if fastcall {
// _args is a &[&PyAny], _kwnames is a Option<&PyTuple> containing the
// keyword names of the keyword args in _kwargs
(
// need copied() for &&PyAny -> &PyAny
quote! { _args.iter().copied() },
quote! { _kwnames.map(|kwnames| {
kwnames.as_slice().iter().copied().zip(_kwargs.iter().copied())
}) },
)
} else {
// _args is a &PyTuple, _kwargs is an Option<&PyDict>
(
quote! { _args.iter() },
quote! { _kwargs.map(|dict| dict.iter()) },
)
};
// create array of arguments, and then parse // create array of arguments, and then parse
Ok(quote! { Ok(quote! {
{ {
@ -462,7 +508,12 @@ pub fn impl_arg_params(
}; };
let mut #args_array = [None; #num_params]; let mut #args_array = [None; #num_params];
let (_args, _kwargs) = DESCRIPTION.extract_arguments(_args, _kwargs, &mut #args_array)?; let (_args, _kwargs) = DESCRIPTION.extract_arguments(
#py,
#args_to_extract,
#kwargs_to_extract,
&mut #args_array
)?;
#(#param_conversion)* #(#param_conversion)*
@ -616,32 +667,36 @@ pub fn impl_py_method_def(
let add_flags = flags.map(|flags| quote!(.flags(#flags))); let add_flags = flags.map(|flags| quote!(.flags(#flags)));
let python_name = spec.null_terminated_python_name(); let python_name = spec.null_terminated_python_name();
let doc = &spec.doc; let doc = &spec.doc;
if spec.args.is_empty() { let (methoddef_meth, cfunc_variant) = if spec.args.is_empty() {
let wrapper = impl_wrap_noargs(cls, spec, self_ty); (quote!(noargs), quote!(PyCFunction))
Ok(quote! { } else if spec.can_use_fastcall() {
pyo3::class::PyMethodDefType::Method({ (
pyo3::class::PyMethodDef::noargs( quote!(fastcall_cfunction_with_keywords),
#python_name, quote!(PyCFunctionFastWithKeywords),
pyo3::class::methods::PyCFunction(#wrapper), )
#doc
)
#add_flags
})
})
} else { } else {
let wrapper = impl_wrap_cfunction_with_keywords(cls, &spec, self_ty)?; (
Ok(quote! { quote!(cfunction_with_keywords),
pyo3::class::PyMethodDefType::Method({ quote!(PyCFunctionWithKeywords),
pyo3::class::PyMethodDef::cfunction_with_keywords( )
#python_name, };
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper), let wrapper = if spec.args.is_empty() {
#doc impl_wrap_noargs(cls, spec, self_ty)
) } else if spec.can_use_fastcall() {
#add_flags impl_wrap_fastcall_cfunction_with_keywords(cls, &spec, self_ty)?
}) } else {
impl_wrap_cfunction_with_keywords(cls, &spec, self_ty)?
};
Ok(quote! {
pyo3::class::PyMethodDefType::Method({
pyo3::class::PyMethodDef:: #methoddef_meth (
#python_name,
pyo3::class::methods:: #cfunc_variant (#wrapper),
#doc
)
#add_flags
}) })
} })
} }
pub fn impl_py_method_def_new(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> { pub fn impl_py_method_def_new(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> {

View File

@ -28,6 +28,8 @@ pub enum PyMethodDefType {
pub enum PyMethodType { pub enum PyMethodType {
PyCFunction(PyCFunction), PyCFunction(PyCFunction),
PyCFunctionWithKeywords(PyCFunctionWithKeywords), PyCFunctionWithKeywords(PyCFunctionWithKeywords),
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
PyCFunctionFastWithKeywords(PyCFunctionFastWithKeywords),
} }
// These newtype structs serve no purpose other than wrapping which are function pointers - because // These newtype structs serve no purpose other than wrapping which are function pointers - because
@ -36,6 +38,9 @@ pub enum PyMethodType {
pub struct PyCFunction(pub ffi::PyCFunction); pub struct PyCFunction(pub ffi::PyCFunction);
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct PyCFunctionWithKeywords(pub ffi::PyCFunctionWithKeywords); pub struct PyCFunctionWithKeywords(pub ffi::PyCFunctionWithKeywords);
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
#[derive(Clone, Copy, Debug)]
pub struct PyCFunctionFastWithKeywords(pub ffi::_PyCFunctionFastWithKeywords);
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct PyGetter(pub ffi::getter); pub struct PyGetter(pub ffi::getter);
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -105,6 +110,21 @@ impl PyMethodDef {
} }
} }
/// Define a function that can take `*args` and `**kwargs`.
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
pub const fn fastcall_cfunction_with_keywords(
name: &'static str,
cfunction: PyCFunctionFastWithKeywords,
doc: &'static str,
) -> Self {
Self {
ml_name: name,
ml_meth: PyMethodType::PyCFunctionFastWithKeywords(cfunction),
ml_flags: ffi::METH_FASTCALL | ffi::METH_KEYWORDS,
ml_doc: doc,
}
}
pub const fn flags(mut self, flags: c_int) -> Self { pub const fn flags(mut self, flags: c_int) -> Self {
self.ml_flags |= flags; self.ml_flags |= flags;
self self
@ -115,6 +135,10 @@ impl PyMethodDef {
let meth = match self.ml_meth { let meth = match self.ml_meth {
PyMethodType::PyCFunction(meth) => meth.0, PyMethodType::PyCFunction(meth) => meth.0,
PyMethodType::PyCFunctionWithKeywords(meth) => unsafe { std::mem::transmute(meth.0) }, PyMethodType::PyCFunctionWithKeywords(meth) => unsafe { std::mem::transmute(meth.0) },
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
PyMethodType::PyCFunctionFastWithKeywords(meth) => unsafe {
std::mem::transmute(meth.0)
},
}; };
Ok(ffi::PyMethodDef { Ok(ffi::PyMethodDef {

View File

@ -39,6 +39,7 @@ impl FunctionDescription {
format!("{}()", self.func_name) format!("{}()", self.func_name)
} }
} }
/// Extracts the `args` and `kwargs` provided into `output`, according to this function /// Extracts the `args` and `kwargs` provided into `output`, according to this function
/// definition. /// definition.
/// ///
@ -52,8 +53,9 @@ impl FunctionDescription {
/// Unexpected, duplicate or invalid arguments will cause this function to return `TypeError`. /// Unexpected, duplicate or invalid arguments will cause this function to return `TypeError`.
pub fn extract_arguments<'p>( pub fn extract_arguments<'p>(
&self, &self,
args: &'p PyTuple, py: Python<'p>,
kwargs: Option<&'p PyDict>, mut args: impl ExactSizeIterator<Item = &'p PyAny>,
kwargs: Option<impl Iterator<Item = (&'p PyAny, &'p PyAny)>>,
output: &mut [Option<&'p PyAny>], output: &mut [Option<&'p PyAny>],
) -> PyResult<(Option<&'p PyTuple>, Option<&'p PyDict>)> { ) -> PyResult<(Option<&'p PyTuple>, Option<&'p PyDict>)> {
let num_positional_parameters = self.positional_parameter_names.len(); let num_positional_parameters = self.positional_parameter_names.len();
@ -66,33 +68,36 @@ impl FunctionDescription {
); );
// Handle positional arguments // Handle positional arguments
let (args_provided, varargs) = { let args_provided = {
let args_provided = args.len(); let args_provided = args.len();
if self.accept_varargs { if self.accept_varargs {
( std::cmp::min(num_positional_parameters, args_provided)
std::cmp::min(num_positional_parameters, args_provided),
Some(args.slice(num_positional_parameters as isize, args_provided as isize)),
)
} else if args_provided > num_positional_parameters { } else if args_provided > num_positional_parameters {
return Err(self.too_many_positional_arguments(args_provided)); return Err(self.too_many_positional_arguments(args_provided));
} else { } else {
(args_provided, None) args_provided
} }
}; };
// Copy positional arguments into output // Copy positional arguments into output
for (out, arg) in output[..args_provided].iter_mut().zip(args) { for (out, arg) in output[..args_provided].iter_mut().zip(args.by_ref()) {
*out = Some(arg); *out = Some(arg);
} }
// Collect varargs into tuple
let varargs = if self.accept_varargs {
Some(PyTuple::new(py, args))
} else {
None
};
// Handle keyword arguments // Handle keyword arguments
let varkeywords = match (kwargs, self.accept_varkeywords) { let varkeywords = match (kwargs, self.accept_varkeywords) {
(Some(kwargs), true) => { (Some(kwargs), true) => {
let mut varkeywords = None; let mut varkeywords = None;
self.extract_keyword_arguments(kwargs, output, |name, value| { self.extract_keyword_arguments(kwargs, output, |name, value| {
varkeywords varkeywords
.get_or_insert_with(|| PyDict::new(kwargs.py())) .get_or_insert_with(|| PyDict::new(py))
.set_item(name, value) .set_item(name, value)
})?; })?;
varkeywords varkeywords
@ -146,7 +151,7 @@ impl FunctionDescription {
#[inline] #[inline]
fn extract_keyword_arguments<'p>( fn extract_keyword_arguments<'p>(
&self, &self,
kwargs: &'p PyDict, kwargs: impl Iterator<Item = (&'p PyAny, &'p PyAny)>,
output: &mut [Option<&'p PyAny>], output: &mut [Option<&'p PyAny>],
mut unexpected_keyword_handler: impl FnMut(&'p PyAny, &'p PyAny) -> PyResult<()>, mut unexpected_keyword_handler: impl FnMut(&'p PyAny, &'p PyAny) -> PyResult<()>,
) -> PyResult<()> { ) -> PyResult<()> {

View File

@ -45,7 +45,7 @@ pub type PyCFunctionWithKeywords = unsafe extern "C" fn(
kwds: *mut PyObject, kwds: *mut PyObject,
) -> *mut PyObject; ) -> *mut PyObject;
#[cfg(Py_3_7)] #[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
pub type _PyCFunctionFastWithKeywords = unsafe extern "C" fn( pub type _PyCFunctionFastWithKeywords = unsafe extern "C" fn(
slf: *mut PyObject, slf: *mut PyObject,
args: *const *mut PyObject, args: *const *mut PyObject,

View File

@ -335,35 +335,6 @@ macro_rules! wrap_pyfunction {
}; };
} }
/// Returns the function that is called in the C-FFI.
///
/// Use this together with `#[pyfunction]` and [types::PyCFunction].
/// ```
/// use pyo3::prelude::*;
/// use pyo3::types::PyCFunction;
/// use pyo3::raw_pycfunction;
///
/// #[pyfunction]
/// fn some_fun(arg: i32) -> PyResult<()> {
/// Ok(())
/// }
///
/// #[pymodule]
/// fn module(_py: Python, module: &PyModule) -> PyResult<()> {
/// let ffi_wrapper_fun = raw_pycfunction!(some_fun);
/// let docs = "Some documentation string with null-termination\0";
/// let py_cfunction =
/// PyCFunction::new_with_keywords(ffi_wrapper_fun, "function_name", docs, module.into())?;
/// module.add_function(py_cfunction)
/// }
/// ```
#[macro_export]
macro_rules! raw_pycfunction {
($function_name: ident) => {{
pyo3::paste::expr! { [<__pyo3_raw_ $function_name>] }
}};
}
/// Returns a function that takes a [Python] instance and returns a Python module. /// Returns a function that takes a [Python] instance and returns a Python module.
/// ///
/// Use this together with `#[pymodule]` and [types::PyModule::add_wrapped]. /// Use this together with `#[pymodule]` and [types::PyModule::add_wrapped].

View File

@ -14,8 +14,6 @@ pyobject_native_type_core!(PyCFunction, ffi::PyCFunction_Type, #checkfunction=ff
impl PyCFunction { impl PyCFunction {
/// Create a new built-in function with keywords. /// Create a new built-in function with keywords.
///
/// See [raw_pycfunction] for documentation on how to get the `fun` argument.
pub fn new_with_keywords<'a>( pub fn new_with_keywords<'a>(
fun: ffi::PyCFunctionWithKeywords, fun: ffi::PyCFunctionWithKeywords,
name: &'static str, name: &'static str,

View File

@ -133,6 +133,12 @@ impl<'a> Iterator for PyTupleIterator<'a> {
} }
} }
impl<'a> ExactSizeIterator for PyTupleIterator<'a> {
fn len(&self) -> usize {
self.length - self.index
}
}
impl<'a> IntoIterator for &'a PyTuple { impl<'a> IntoIterator for &'a PyTuple {
type Item = &'a PyAny; type Item = &'a PyAny;
type IntoIter = PyTupleIterator<'a>; type IntoIter = PyTupleIterator<'a>;

View File

@ -4,7 +4,7 @@ use pyo3::prelude::*;
use pyo3::types::PyCFunction; use pyo3::types::PyCFunction;
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]
use pyo3::types::{PyDateTime, PyFunction}; use pyo3::types::{PyDateTime, PyFunction};
use pyo3::{raw_pycfunction, wrap_pyfunction}; use pyo3::wrap_pyfunction;
mod common; mod common;
@ -164,31 +164,6 @@ fn test_function_with_custom_conversion_error() {
); );
} }
#[test]
fn test_raw_function() {
let gil = Python::acquire_gil();
let py = gil.python();
let raw_func = raw_pycfunction!(optional_bool);
let fun = PyCFunction::new_with_keywords(raw_func, "fun", "", py.into()).unwrap();
let res = fun.call((), None).unwrap().extract::<&str>().unwrap();
assert_eq!(res, "Some(true)");
let res = fun.call((false,), None).unwrap().extract::<&str>().unwrap();
assert_eq!(res, "Some(false)");
let no_module = fun.getattr("__module__").unwrap().is_none();
assert!(no_module);
let module = PyModule::new(py, "cool_module").unwrap();
module.add_function(fun).unwrap();
let res = module
.getattr("fun")
.unwrap()
.call((), None)
.unwrap()
.extract::<&str>()
.unwrap();
assert_eq!(res, "Some(true)");
}
#[pyfunction] #[pyfunction]
fn conversion_error(str_arg: &str, int_arg: i64, tuple_arg: (&str, f64), option_arg: Option<i64>) { fn conversion_error(str_arg: &str, int_arg: i64, tuple_arg: (&str, f64), option_arg: Option<i64>) {
println!( println!(