pyo3/types/
function.rs

1#[cfg(feature = "gil-refs")]
2use crate::derive_utils::PyFunctionArguments;
3use crate::ffi_ptr_ext::FfiPtrExt;
4use crate::py_result_ext::PyResultExt;
5use crate::types::capsule::PyCapsuleMethods;
6use crate::types::module::PyModuleMethods;
7#[cfg(feature = "gil-refs")]
8use crate::PyNativeType;
9use crate::{
10    ffi,
11    impl_::pymethods::{self, PyMethodDef},
12    types::{PyCapsule, PyDict, PyModule, PyString, PyTuple},
13};
14use crate::{Bound, IntoPy, Py, PyAny, PyResult, Python};
15use std::cell::UnsafeCell;
16use std::ffi::CStr;
17
18/// Represents a builtin Python function object.
19///
20/// Values of this type are accessed via PyO3's smart pointers, e.g. as
21/// [`Py<PyCFunction>`][crate::Py] or [`Bound<'py, PyCFunction>`][Bound].
22#[repr(transparent)]
23pub struct PyCFunction(PyAny);
24
25pyobject_native_type_core!(PyCFunction, pyobject_native_static_type_object!(ffi::PyCFunction_Type), #checkfunction=ffi::PyCFunction_Check);
26
27impl PyCFunction {
28    /// Deprecated form of [`PyCFunction::new_with_keywords_bound`]
29    #[cfg(feature = "gil-refs")]
30    #[deprecated(
31        since = "0.21.0",
32        note = "`PyCFunction::new_with_keywords` will be replaced by `PyCFunction::new_with_keywords_bound` in a future PyO3 version"
33    )]
34    pub fn new_with_keywords<'a>(
35        fun: ffi::PyCFunctionWithKeywords,
36        name: &'static CStr,
37        doc: &'static CStr,
38        py_or_module: PyFunctionArguments<'a>,
39    ) -> PyResult<&'a Self> {
40        let (py, module) = py_or_module.into_py_and_maybe_module();
41        Self::internal_new(
42            py,
43            &PyMethodDef::cfunction_with_keywords(name, fun, doc),
44            module.map(PyNativeType::as_borrowed).as_deref(),
45        )
46        .map(Bound::into_gil_ref)
47    }
48
49    /// Create a new built-in function with keywords (*args and/or **kwargs).
50    ///
51    /// To create `name` and `doc` static strings on Rust versions older than 1.77 (which added c"" literals),
52    /// use the [`c_str!`](crate::ffi::c_str) macro.
53    pub fn new_with_keywords_bound<'py>(
54        py: Python<'py>,
55        fun: ffi::PyCFunctionWithKeywords,
56        name: &'static CStr,
57        doc: &'static CStr,
58        module: Option<&Bound<'py, PyModule>>,
59    ) -> PyResult<Bound<'py, Self>> {
60        Self::internal_new(
61            py,
62            &PyMethodDef::cfunction_with_keywords(name, fun, doc),
63            module,
64        )
65    }
66
67    /// Deprecated form of [`PyCFunction::new`]
68    #[cfg(feature = "gil-refs")]
69    #[deprecated(
70        since = "0.21.0",
71        note = "`PyCFunction::new` will be replaced by `PyCFunction::new_bound` in a future PyO3 version"
72    )]
73    pub fn new<'a>(
74        fun: ffi::PyCFunction,
75        name: &'static CStr,
76        doc: &'static CStr,
77        py_or_module: PyFunctionArguments<'a>,
78    ) -> PyResult<&'a Self> {
79        let (py, module) = py_or_module.into_py_and_maybe_module();
80        Self::internal_new(
81            py,
82            &PyMethodDef::noargs(name, fun, doc),
83            module.map(PyNativeType::as_borrowed).as_deref(),
84        )
85        .map(Bound::into_gil_ref)
86    }
87
88    /// Create a new built-in function which takes no arguments.
89    ///
90    /// To create `name` and `doc` static strings on Rust versions older than 1.77 (which added c"" literals),
91    /// use the [`c_str!`](crate::ffi::c_str) macro.
92    pub fn new_bound<'py>(
93        py: Python<'py>,
94        fun: ffi::PyCFunction,
95        name: &'static CStr,
96        doc: &'static CStr,
97        module: Option<&Bound<'py, PyModule>>,
98    ) -> PyResult<Bound<'py, Self>> {
99        Self::internal_new(py, &PyMethodDef::noargs(name, fun, doc), module)
100    }
101
102    /// Deprecated form of [`PyCFunction::new_closure`]
103    #[cfg(feature = "gil-refs")]
104    #[deprecated(
105        since = "0.21.0",
106        note = "`PyCFunction::new_closure` will be replaced by `PyCFunction::new_closure_bound` in a future PyO3 version"
107    )]
108    pub fn new_closure<'a, F, R>(
109        py: Python<'a>,
110        name: Option<&'static CStr>,
111        doc: Option<&'static CStr>,
112        closure: F,
113    ) -> PyResult<&'a PyCFunction>
114    where
115        F: Fn(&PyTuple, Option<&PyDict>) -> R + Send + 'static,
116        R: crate::callback::IntoPyCallbackOutput<*mut ffi::PyObject>,
117    {
118        Self::new_closure_bound(py, name, doc, move |args, kwargs| {
119            closure(args.as_gil_ref(), kwargs.map(Bound::as_gil_ref))
120        })
121        .map(Bound::into_gil_ref)
122    }
123
124    /// Create a new function from a closure.
125    ///
126    /// # Examples
127    ///
128    /// ```
129    /// # use pyo3::prelude::*;
130    /// # use pyo3::{py_run, types::{PyCFunction, PyDict, PyTuple}};
131    ///
132    /// Python::with_gil(|py| {
133    ///     let add_one = |args: &Bound<'_, PyTuple>, _kwargs: Option<&Bound<'_, PyDict>>| -> PyResult<_> {
134    ///         let i = args.extract::<(i64,)>()?.0;
135    ///         Ok(i+1)
136    ///     };
137    ///     let add_one = PyCFunction::new_closure_bound(py, None, None, add_one).unwrap();
138    ///     py_run!(py, add_one, "assert add_one(42) == 43");
139    /// });
140    /// ```
141    pub fn new_closure_bound<'py, F, R>(
142        py: Python<'py>,
143        name: Option<&'static CStr>,
144        doc: Option<&'static CStr>,
145        closure: F,
146    ) -> PyResult<Bound<'py, Self>>
147    where
148        F: Fn(&Bound<'_, PyTuple>, Option<&Bound<'_, PyDict>>) -> R + Send + 'static,
149        R: crate::callback::IntoPyCallbackOutput<*mut ffi::PyObject>,
150    {
151        let name = name.unwrap_or(ffi::c_str!("pyo3-closure"));
152        let doc = doc.unwrap_or(ffi::c_str!(""));
153        let method_def =
154            pymethods::PyMethodDef::cfunction_with_keywords(name, run_closure::<F, R>, doc);
155        let def = method_def.as_method_def();
156
157        let capsule = PyCapsule::new_bound(
158            py,
159            ClosureDestructor::<F> {
160                closure,
161                def: UnsafeCell::new(def),
162            },
163            Some(CLOSURE_CAPSULE_NAME.to_owned()),
164        )?;
165
166        // Safety: just created the capsule with type ClosureDestructor<F> above
167        let data = unsafe { capsule.reference::<ClosureDestructor<F>>() };
168
169        unsafe {
170            ffi::PyCFunction_NewEx(data.def.get(), capsule.as_ptr(), std::ptr::null_mut())
171                .assume_owned_or_err(py)
172                .downcast_into_unchecked()
173        }
174    }
175
176    #[doc(hidden)]
177    pub fn internal_new<'py>(
178        py: Python<'py>,
179        method_def: &PyMethodDef,
180        module: Option<&Bound<'py, PyModule>>,
181    ) -> PyResult<Bound<'py, Self>> {
182        let (mod_ptr, module_name): (_, Option<Py<PyString>>) = if let Some(m) = module {
183            let mod_ptr = m.as_ptr();
184            (mod_ptr, Some(m.name()?.into_py(py)))
185        } else {
186            (std::ptr::null_mut(), None)
187        };
188        let def = method_def.as_method_def();
189
190        // FIXME: stop leaking the def
191        let def = Box::into_raw(Box::new(def));
192
193        let module_name_ptr = module_name
194            .as_ref()
195            .map_or(std::ptr::null_mut(), Py::as_ptr);
196
197        unsafe {
198            ffi::PyCFunction_NewEx(def, mod_ptr, module_name_ptr)
199                .assume_owned_or_err(py)
200                .downcast_into_unchecked()
201        }
202    }
203}
204
205static CLOSURE_CAPSULE_NAME: &CStr = ffi::c_str!("pyo3-closure");
206
207unsafe extern "C" fn run_closure<F, R>(
208    capsule_ptr: *mut ffi::PyObject,
209    args: *mut ffi::PyObject,
210    kwargs: *mut ffi::PyObject,
211) -> *mut ffi::PyObject
212where
213    F: Fn(&Bound<'_, PyTuple>, Option<&Bound<'_, PyDict>>) -> R + Send + 'static,
214    R: crate::callback::IntoPyCallbackOutput<*mut ffi::PyObject>,
215{
216    use crate::types::any::PyAnyMethods;
217
218    crate::impl_::trampoline::cfunction_with_keywords(
219        capsule_ptr,
220        args,
221        kwargs,
222        |py, capsule_ptr, args, kwargs| {
223            let boxed_fn: &ClosureDestructor<F> =
224                &*(ffi::PyCapsule_GetPointer(capsule_ptr, CLOSURE_CAPSULE_NAME.as_ptr())
225                    as *mut ClosureDestructor<F>);
226            let args = Bound::ref_from_ptr(py, &args).downcast_unchecked::<PyTuple>();
227            let kwargs = Bound::ref_from_ptr_or_opt(py, &kwargs)
228                .as_ref()
229                .map(|b| b.downcast_unchecked::<PyDict>());
230            let result = (boxed_fn.closure)(args, kwargs);
231            crate::callback::convert(py, result)
232        },
233    )
234}
235
236struct ClosureDestructor<F> {
237    closure: F,
238    // Wrapped in UnsafeCell because Python C-API wants a *mut pointer
239    // to this member.
240    def: UnsafeCell<ffi::PyMethodDef>,
241}
242
243// Safety: F is send and none of the fields are ever mutated
244unsafe impl<F: Send> Send for ClosureDestructor<F> {}
245
246/// Represents a Python function object.
247///
248/// Values of this type are accessed via PyO3's smart pointers, e.g. as
249/// [`Py<PyFunction>`][crate::Py] or [`Bound<'py, PyFunction>`][Bound].
250#[repr(transparent)]
251#[cfg(all(not(Py_LIMITED_API), not(all(PyPy, not(Py_3_8)))))]
252pub struct PyFunction(PyAny);
253
254#[cfg(all(not(Py_LIMITED_API), not(all(PyPy, not(Py_3_8)))))]
255pyobject_native_type_core!(PyFunction, pyobject_native_static_type_object!(ffi::PyFunction_Type), #checkfunction=ffi::PyFunction_Check);