Merge pull request #3800 from snuderl/PyCFunction-bound-api

PyCFunction bound api
This commit is contained in:
David Hewitt 2024-02-06 18:28:40 +00:00 committed by GitHub
commit 030a618e0d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 81 additions and 25 deletions

View file

@ -6,5 +6,5 @@ pub fn _wrap_pyfunction<'a>(
method_def: &PyMethodDef,
py_or_module: impl Into<PyFunctionArguments<'a>>,
) -> PyResult<&'a PyCFunction> {
PyCFunction::internal_new(method_def, py_or_module.into())
PyCFunction::internal_new(method_def, py_or_module.into()).map(|x| x.into_gil_ref())
}

View file

@ -1,6 +1,8 @@
use crate::derive_utils::PyFunctionArguments;
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::methods::PyMethodDefDestructor;
use crate::prelude::*;
use crate::py_result_ext::PyResultExt;
use crate::types::capsule::PyCapsuleMethods;
use crate::{
ffi,
@ -17,13 +19,30 @@ pub struct PyCFunction(PyAny);
pyobject_native_type_core!(PyCFunction, pyobject_native_static_type_object!(ffi::PyCFunction_Type), #checkfunction=ffi::PyCFunction_Check);
impl PyCFunction {
/// Create a new built-in function with keywords (*args and/or **kwargs).
/// Deprecated form of [`PyCFunction::new_with_keywords_bound`]
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyCFunction::new_with_keywords` will be replaced by `PyCFunction::new_with_keywords_bound` in a future PyO3 version"
)
)]
pub fn new_with_keywords<'a>(
fun: ffi::PyCFunctionWithKeywords,
name: &'static str,
doc: &'static str,
py_or_module: PyFunctionArguments<'a>,
) -> PyResult<&'a Self> {
Self::new_with_keywords_bound(fun, name, doc, py_or_module).map(Bound::into_gil_ref)
}
/// Create a new built-in function with keywords (*args and/or **kwargs).
pub fn new_with_keywords_bound<'a>(
fun: ffi::PyCFunctionWithKeywords,
name: &'static str,
doc: &'static str,
py_or_module: PyFunctionArguments<'a>,
) -> PyResult<Bound<'a, Self>> {
Self::internal_new(
&PyMethodDef::cfunction_with_keywords(
name,
@ -34,19 +53,57 @@ impl PyCFunction {
)
}
/// Create a new built-in function which takes no arguments.
/// Deprecated form of [`PyCFunction::new`]
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyCFunction::new` will be replaced by `PyCFunction::new_bound` in a future PyO3 version"
)
)]
pub fn new<'a>(
fun: ffi::PyCFunction,
name: &'static str,
doc: &'static str,
py_or_module: PyFunctionArguments<'a>,
) -> PyResult<&'a Self> {
Self::new_bound(fun, name, doc, py_or_module).map(Bound::into_gil_ref)
}
/// Create a new built-in function which takes no arguments.
pub fn new_bound<'a>(
fun: ffi::PyCFunction,
name: &'static str,
doc: &'static str,
py_or_module: PyFunctionArguments<'a>,
) -> PyResult<Bound<'a, Self>> {
Self::internal_new(
&PyMethodDef::noargs(name, pymethods::PyCFunction(fun), doc),
py_or_module,
)
}
/// Deprecated form of [`PyCFunction::new_closure`]
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyCFunction::new_closure` will be replaced by `PyCFunction::new_closure_bound` in a future PyO3 version"
)
)]
pub fn new_closure<'a, F, R>(
py: Python<'a>,
name: Option<&'static str>,
doc: Option<&'static str>,
closure: F,
) -> PyResult<&'a PyCFunction>
where
F: Fn(&PyTuple, Option<&PyDict>) -> R + Send + 'static,
R: crate::callback::IntoPyCallbackOutput<*mut ffi::PyObject>,
{
Self::new_closure_bound(py, name, doc, closure).map(Bound::into_gil_ref)
}
/// Create a new function from a closure.
///
/// # Examples
@ -60,16 +117,16 @@ impl PyCFunction {
/// let i = args.extract::<(i64,)>()?.0;
/// Ok(i+1)
/// };
/// let add_one = PyCFunction::new_closure(py, None, None, add_one).unwrap();
/// let add_one = PyCFunction::new_closure_bound(py, None, None, add_one).unwrap();
/// py_run!(py, add_one, "assert add_one(42) == 43");
/// });
/// ```
pub fn new_closure<'a, F, R>(
pub fn new_closure_bound<'a, F, R>(
py: Python<'a>,
name: Option<&'static str>,
doc: Option<&'static str>,
closure: F,
) -> PyResult<&'a PyCFunction>
) -> PyResult<Bound<'a, Self>>
where
F: Fn(&PyTuple, Option<&PyDict>) -> R + Send + 'static,
R: crate::callback::IntoPyCallbackOutput<*mut ffi::PyObject>,
@ -95,11 +152,9 @@ impl PyCFunction {
let data = unsafe { capsule.reference::<ClosureDestructor<F>>() };
unsafe {
py.from_owned_ptr_or_err::<PyCFunction>(ffi::PyCFunction_NewEx(
data.def.get(),
capsule.as_ptr(),
std::ptr::null_mut(),
))
ffi::PyCFunction_NewEx(data.def.get(), capsule.as_ptr(), std::ptr::null_mut())
.assume_owned_or_err(py)
.downcast_into_unchecked()
}
}
@ -107,7 +162,7 @@ impl PyCFunction {
pub fn internal_new<'py>(
method_def: &PyMethodDef,
py_or_module: PyFunctionArguments<'py>,
) -> PyResult<&'py Self> {
) -> PyResult<Bound<'py, Self>> {
let (py, module) = py_or_module.into_py_and_maybe_module();
let (mod_ptr, module_name): (_, Option<Py<PyString>>) = if let Some(m) = module {
let mod_ptr = m.as_ptr();
@ -126,11 +181,9 @@ impl PyCFunction {
.map_or(std::ptr::null_mut(), Py::as_ptr);
unsafe {
py.from_owned_ptr_or_err::<PyCFunction>(ffi::PyCFunction_NewEx(
def,
mod_ptr,
module_name_ptr,
))
ffi::PyCFunction_NewEx(def, mod_ptr, module_name_ptr)
.assume_owned_or_err(py)
.downcast_into_unchecked()
}
}
}

View file

@ -323,7 +323,7 @@ fn test_pycfunction_new() {
ffi::PyLong_FromLong(4200)
}
let py_fn = PyCFunction::new(
let py_fn = PyCFunction::new_bound(
c_fn,
"py_fn",
"py_fn for test (this is the docstring)",
@ -380,7 +380,7 @@ fn test_pycfunction_new_with_keywords() {
ffi::PyLong_FromLong(foo * bar)
}
let py_fn = PyCFunction::new_with_keywords(
let py_fn = PyCFunction::new_with_keywords_bound(
c_fn,
"py_fn",
"py_fn for test (this is the docstring)",
@ -422,7 +422,7 @@ fn test_closure() {
})
};
let closure_py =
PyCFunction::new_closure(py, Some("test_fn"), Some("test_fn doc"), f).unwrap();
PyCFunction::new_closure_bound(py, Some("test_fn"), Some("test_fn doc"), f).unwrap();
py_assert!(py, closure_py, "closure_py(42) == [43]");
py_assert!(py, closure_py, "closure_py.__name__ == 'test_fn'");
@ -445,7 +445,7 @@ fn test_closure_counter() {
*counter += 1;
Ok(*counter)
};
let counter_py = PyCFunction::new_closure(py, None, None, counter_fn).unwrap();
let counter_py = PyCFunction::new_closure_bound(py, None, None, counter_fn).unwrap();
py_assert!(py, counter_py, "counter_py() == 1");
py_assert!(py, counter_py, "counter_py() == 2");

View file

@ -10,7 +10,9 @@ fn main() {
println!("This is five: {:?}", ref_.len());
Ok(())
};
PyCFunction::new_closure(py, None, None, closure_fn).unwrap().into()
PyCFunction::new_closure_bound(py, None, None, closure_fn)
.unwrap()
.into()
});
Python::with_gil(|py| {

View file

@ -6,7 +6,8 @@ error[E0597]: `local_data` does not live long enough
7 | let ref_: &[u8] = &local_data;
| ^^^^^^^^^^^ borrowed value does not live long enough
...
13 | PyCFunction::new_closure(py, None, None, closure_fn).unwrap().into()
| ---------------------------------------------------- argument requires that `local_data` is borrowed for `'static`
14 | });
13 | PyCFunction::new_closure_bound(py, None, None, closure_fn)
| ---------------------------------------------------------- argument requires that `local_data` is borrowed for `'static`
...
16 | });
| - `local_data` dropped here while still borrowed