Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add native Function types. #1163

Merged
merged 3 commits into from
Sep 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Add macro attribute to `#[pyfn]` and `#[pyfunction]` to pass the module of a Python function to the function
body. [#1143](https://github.com/PyO3/pyo3/pull/1143)
- Add `add_function()` and `add_submodule()` functions to `PyModule` [#1143](https://github.com/PyO3/pyo3/pull/1143)
- Add native `PyCFunction` and `PyFunction` types, change `add_function` to take a wrapper returning
a `&PyCFunction`instead of `PyObject`. [#1163](https://github.com/PyO3/pyo3/pull/1163)

### Changed
- Exception types have been renamed from e.g. `RuntimeError` to `PyRuntimeError`, and are now only accessible by `&T` or `Py<T>` similar to other Python-native types. The old names continue to exist but are deprecated. [#1024](https://github.com/PyO3/pyo3/pull/1024)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
/// A Python module implemented in Rust.
#[pymodule]
fn string_sum(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum_as_string))?;
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;

Ok(())
}
Expand Down
28 changes: 14 additions & 14 deletions examples/rustapi_module/src/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,29 +215,29 @@ impl TzClass {

#[pymodule]
fn datetime(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(make_date))?;
m.add_function(wrap_pyfunction!(get_date_tuple))?;
m.add_function(wrap_pyfunction!(date_from_timestamp))?;
m.add_function(wrap_pyfunction!(make_time))?;
m.add_function(wrap_pyfunction!(get_time_tuple))?;
m.add_function(wrap_pyfunction!(make_delta))?;
m.add_function(wrap_pyfunction!(get_delta_tuple))?;
m.add_function(wrap_pyfunction!(make_datetime))?;
m.add_function(wrap_pyfunction!(get_datetime_tuple))?;
m.add_function(wrap_pyfunction!(datetime_from_timestamp))?;
m.add_function(wrap_pyfunction!(make_date, m)?)?;
m.add_function(wrap_pyfunction!(get_date_tuple, m)?)?;
m.add_function(wrap_pyfunction!(date_from_timestamp, m)?)?;
m.add_function(wrap_pyfunction!(make_time, m)?)?;
m.add_function(wrap_pyfunction!(get_time_tuple, m)?)?;
m.add_function(wrap_pyfunction!(make_delta, m)?)?;
m.add_function(wrap_pyfunction!(get_delta_tuple, m)?)?;
m.add_function(wrap_pyfunction!(make_datetime, m)?)?;
m.add_function(wrap_pyfunction!(get_datetime_tuple, m)?)?;
m.add_function(wrap_pyfunction!(datetime_from_timestamp, m)?)?;
Comment on lines +218 to +227
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you change these?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So they make use of the two-argument form of wrap_pyfunction. The other choices are using the soon-to-be-deprecated add_wrapped or calling the function returned by the single-argument form.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other choices are using the soon-to-be-deprecated add_wrapped or calling the function returned by the single-argument form.

But why passing module to PyCFunction_NewEx is such important?
We can set module anywhen.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do we gain from passing Python instead of the pymodule? I'd say setting the module should be the default for PyCFunctions

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I got it.
I looked around related APIs but there's no API for setting module other than the m_module field itself. Maybe you're correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can expose this in the api of PyCFunction but I'd be careful with setting this in e.g. add_function. Otherwise a module could "steal" another modules function.

So imo that would need some more (careful) thought to figure out how to deep copy a function when adding it to a module instead of just taking another reference to a function already existing elsewhere.

For now I'd suggest to leave it as is and address this in 0.13


// Python 3.6+ functions
#[cfg(Py_3_6)]
{
m.add_function(wrap_pyfunction!(time_with_fold))?;
m.add_function(wrap_pyfunction!(time_with_fold, m)?)?;
#[cfg(not(PyPy))]
{
m.add_function(wrap_pyfunction!(get_time_tuple_fold))?;
m.add_function(wrap_pyfunction!(get_datetime_tuple_fold))?;
m.add_function(wrap_pyfunction!(get_time_tuple_fold, m)?)?;
m.add_function(wrap_pyfunction!(get_datetime_tuple_fold, m)?)?;
}
}

m.add_function(wrap_pyfunction!(issue_219))?;
m.add_function(wrap_pyfunction!(issue_219, m)?)?;
m.add_class::<TzClass>()?;

Ok(())
Expand Down
2 changes: 1 addition & 1 deletion examples/rustapi_module/src/othermod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn double(x: i32) -> i32 {

#[pymodule]
fn othermod(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(double))?;
m.add_function(wrap_pyfunction!(double, m)?)?;

m.add_class::<ModClass>()?;

Expand Down
4 changes: 2 additions & 2 deletions examples/word-count/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ fn count_line(line: &str, needle: &str) -> usize {
#[pymodule]
fn word_count(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(search))?;
m.add_function(wrap_pyfunction!(search_sequential))?;
m.add_function(wrap_pyfunction!(search_sequential_allow_threads))?;
m.add_function(wrap_pyfunction!(search_sequential, m)?)?;
m.add_function(wrap_pyfunction!(search_sequential_allow_threads, m)?)?;

Ok(())
}
6 changes: 3 additions & 3 deletions guide/src/function.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ fn double(x: usize) -> usize {

#[pymodule]
fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(double)).unwrap();
m.add_function(wrap_pyfunction!(double, m)?).unwrap();

Ok(())
}
Expand Down Expand Up @@ -65,7 +65,7 @@ fn num_kwds(kwds: Option<&PyDict>) -> usize {

#[pymodule]
fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(num_kwds)).unwrap();
m.add_function(wrap_pyfunction!(num_kwds, m)?).unwrap();
Ok(())
}

Expand Down Expand Up @@ -206,7 +206,7 @@ fn pyfunction_with_module(module: &PyModule) -> PyResult<&str> {

#[pymodule]
fn module_with_fn(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(pyfunction_with_module))
m.add_function(wrap_pyfunction!(pyfunction_with_module, m)?)
}

# fn main() {}
Expand Down
2 changes: 1 addition & 1 deletion guide/src/module.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ fn subfunction() -> String {
}

fn init_submodule(module: &PyModule) -> PyResult<()> {
module.add_function(wrap_pyfunction!(subfunction))?;
module.add_function(wrap_pyfunction!(subfunction, module)?)?;
Ok(())
}

Expand Down
2 changes: 1 addition & 1 deletion guide/src/trait_bounds.md
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ pub struct UserModel {
#[pymodule]
fn trait_exposure(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<UserModel>()?;
m.add_function(wrap_pyfunction!(solve_wrapper))?;
m.add_function(wrap_pyfunction!(solve_wrapper, m)?)?;
Ok(())
}

Expand Down
55 changes: 15 additions & 40 deletions pyo3-derive-backend/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) -> syn::Result<()> {
let item: syn::ItemFn = syn::parse_quote! {
fn block_wrapper() {
#function_to_python
#module_name.add_function(&#function_wrapper_ident)?;
#module_name.add_function(#function_wrapper_ident(#module_name)?)?;
}
};
stmts.extend(item.block.stmts.into_iter());
Expand Down Expand Up @@ -204,50 +204,26 @@ pub fn add_fn_to_module(

let python_name = &spec.python_name;

let wrapper = function_c_wrapper(&func.sig.ident, &spec, pyfn_attrs.pass_module);

let name = &func.sig.ident;
let wrapper_ident = format_ident!("__pyo3_raw_{}", name);
let wrapper = function_c_wrapper(name, &wrapper_ident, &spec, pyfn_attrs.pass_module);
Ok(quote! {
#wrapper
fn #function_wrapper_ident<'a>(
args: impl Into<pyo3::derive_utils::WrapPyFunctionArguments<'a>>
) -> pyo3::PyResult<pyo3::PyObject> {
let arg = args.into();
let (py, maybe_module) = arg.into_py_and_maybe_module();
#wrapper

let _def = pyo3::class::PyMethodDef {
ml_name: stringify!(#python_name),
ml_meth: pyo3::class::PyMethodType::PyCFunctionWithKeywords(__wrap),
ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS,
ml_doc: #doc,
};

let (mod_ptr, name) = if let Some(m) = maybe_module {
let mod_ptr = <pyo3::types::PyModule as ::pyo3::conversion::AsPyPointer>::as_ptr(m);
let name = m.name()?;
let name = <&str as pyo3::conversion::IntoPy<PyObject>>::into_py(name, py);
(mod_ptr, <PyObject as pyo3::AsPyPointer>::as_ptr(&name))
} else {
(std::ptr::null_mut(), std::ptr::null_mut())
};

let function = unsafe {
pyo3::PyObject::from_owned_ptr(
py,
pyo3::ffi::PyCFunction_NewEx(
Box::into_raw(Box::new(_def.as_method_def())),
mod_ptr,
name
)
)
};

Ok(function)
args: impl Into<pyo3::derive_utils::PyFunctionArguments<'a>>
) -> pyo3::PyResult<&'a pyo3::types::PyCFunction> {
pyo3::types::PyCFunction::new_with_keywords(#wrapper_ident, stringify!(#python_name), #doc, args.into())
}
})
}

/// Generate static function wrapper (PyCFunction, PyCFunctionWithKeywords)
fn function_c_wrapper(name: &Ident, spec: &method::FnSpec<'_>, pass_module: bool) -> TokenStream {
fn function_c_wrapper(
name: &Ident,
wrapper_ident: &Ident,
spec: &method::FnSpec<'_>,
pass_module: bool,
) -> TokenStream {
let names: Vec<Ident> = get_arg_names(&spec);
let cb;
let slf_module;
Expand All @@ -265,9 +241,8 @@ fn function_c_wrapper(name: &Ident, spec: &method::FnSpec<'_>, pass_module: bool
slf_module = None;
};
let body = pymethod::impl_arg_params(spec, None, cb);

quote! {
unsafe extern "C" fn __wrap(
unsafe extern "C" fn #wrapper_ident(
_slf: *mut pyo3::ffi::PyObject,
_args: *mut pyo3::ffi::PyObject,
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
Expand Down
21 changes: 10 additions & 11 deletions src/derive_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,32 +209,31 @@ where
}

/// Enum to abstract over the arguments of Python function wrappers.
#[doc(hidden)]
pub enum WrapPyFunctionArguments<'a> {
pub enum PyFunctionArguments<'a> {
Python(Python<'a>),
PyModule(&'a PyModule),
}

impl<'a> WrapPyFunctionArguments<'a> {
impl<'a> PyFunctionArguments<'a> {
pub fn into_py_and_maybe_module(self) -> (Python<'a>, Option<&'a PyModule>) {
match self {
WrapPyFunctionArguments::Python(py) => (py, None),
WrapPyFunctionArguments::PyModule(module) => {
PyFunctionArguments::Python(py) => (py, None),
PyFunctionArguments::PyModule(module) => {
let py = module.py();
(py, Some(module))
}
}
}
}

impl<'a> From<Python<'a>> for WrapPyFunctionArguments<'a> {
fn from(py: Python<'a>) -> WrapPyFunctionArguments<'a> {
WrapPyFunctionArguments::Python(py)
impl<'a> From<Python<'a>> for PyFunctionArguments<'a> {
fn from(py: Python<'a>) -> PyFunctionArguments<'a> {
PyFunctionArguments::Python(py)
}
}

impl<'a> From<&'a PyModule> for WrapPyFunctionArguments<'a> {
fn from(module: &'a PyModule) -> WrapPyFunctionArguments<'a> {
WrapPyFunctionArguments::PyModule(module)
impl<'a> From<&'a PyModule> for PyFunctionArguments<'a> {
fn from(module: &'a PyModule) -> PyFunctionArguments<'a> {
PyFunctionArguments::PyModule(module)
}
}
34 changes: 34 additions & 0 deletions src/ffi/funcobject.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use std::os::raw::c_int;

use crate::ffi::object::{PyObject, PyTypeObject, Py_TYPE};

#[cfg_attr(windows, link(name = "pythonXY"))]
extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyFunction_Type")]
pub static mut PyFunction_Type: PyTypeObject;
}

#[inline]
pub unsafe fn PyFunction_Check(op: *mut PyObject) -> c_int {
(Py_TYPE(op) == &mut PyFunction_Type) as c_int
}

extern "C" {
pub fn PyFunction_NewWithQualName(
code: *mut PyObject,
globals: *mut PyObject,
qualname: *mut PyObject,
) -> *mut PyObject;
pub fn PyFunction_New(code: *mut PyObject, globals: *mut PyObject) -> *mut PyObject;
pub fn PyFunction_Code(op: *mut PyObject) -> *mut PyObject;
pub fn PyFunction_GetGlobals(op: *mut PyObject) -> *mut PyObject;
pub fn PyFunction_GetModule(op: *mut PyObject) -> *mut PyObject;
pub fn PyFunction_GetDefaults(op: *mut PyObject) -> *mut PyObject;
pub fn PyFunction_SetDefaults(op: *mut PyObject, defaults: *mut PyObject) -> c_int;
pub fn PyFunction_GetKwDefaults(op: *mut PyObject) -> *mut PyObject;
pub fn PyFunction_SetKwDefaults(op: *mut PyObject, defaults: *mut PyObject) -> c_int;
pub fn PyFunction_GetClosure(op: *mut PyObject) -> *mut PyObject;
pub fn PyFunction_SetClosure(op: *mut PyObject, closure: *mut PyObject) -> c_int;
pub fn PyFunction_GetAnnotations(op: *mut PyObject) -> *mut PyObject;
pub fn PyFunction_SetAnnotations(op: *mut PyObject, annotations: *mut PyObject) -> c_int;
}
3 changes: 3 additions & 0 deletions src/ffi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub use self::eval::*;
pub use self::fileobject::*;
pub use self::floatobject::*;
pub use self::frameobject::PyFrameObject;
pub use self::funcobject::*;
pub use self::genobject::*;
pub use self::import::*;
pub use self::intrcheck::*;
Expand Down Expand Up @@ -157,3 +158,5 @@ pub mod frameobject {

pub(crate) mod datetime;
pub(crate) mod marshal;

pub(crate) mod funcobject;
16 changes: 15 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
//! #[pymodule]
//! /// A Python module implemented in Rust.
//! fn string_sum(py: Python, m: &PyModule) -> PyResult<()> {
//! m.add_function(wrap_pyfunction!(sum_as_string))?;
//! m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
//!
//! Ok(())
//! }
Expand Down Expand Up @@ -218,6 +218,20 @@ macro_rules! wrap_pyfunction {
($function_name: ident) => {{
&pyo3::paste::expr! { [<__pyo3_get_function_ $function_name>] }
}};

($function_name: ident, $arg: expr) => {
pyo3::wrap_pyfunction!($function_name)(pyo3::derive_utils::PyFunctionArguments::from($arg))
};
}

/// Returns the function that is called in the C-FFI.
///
/// Use this together with `#[pyfunction]` and [types::PyCFunction].
#[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.
Expand Down
2 changes: 1 addition & 1 deletion src/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ impl<'p> Python<'p> {
/// let gil = Python::acquire_gil();
/// let py = gil.python();
/// let m = PyModule::new(py, "pcount").unwrap();
/// m.add_function(wrap_pyfunction!(parallel_count)).unwrap();
/// m.add_function(wrap_pyfunction!(parallel_count, m).unwrap()).unwrap();
/// let locals = [("pcount", m)].into_py_dict(py);
/// py.run(r#"
/// s = ["Flow", "my", "tears", "the", "Policeman", "Said"]
Expand Down
Loading