Merge pull request #1619 from birkenfeld/fastcall
Implement METH_FASTCALL for pyfunctions.
This commit is contained in:
commit
a5810eaffa
|
@ -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)
|
||||||
|
|
|
@ -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)`.
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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<()> {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
29
src/lib.rs
29
src/lib.rs
|
@ -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].
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
Loading…
Reference in New Issue