Skip to content

Commit da505bd

Browse files
committed
Guide: focus on the module way of creating python modules
1 parent ee29399 commit da505bd

File tree

2 files changed

+63
-89
lines changed

2 files changed

+63
-89
lines changed

README.md

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -77,21 +77,18 @@ pyo3 = { version = "0.26.0", features = ["extension-module"] }
7777
**`src/lib.rs`**
7878

7979
```rust
80-
use pyo3::prelude::*;
81-
82-
/// Formats the sum of two numbers as string.
83-
#[pyfunction]
84-
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
85-
Ok((a + b).to_string())
86-
}
87-
88-
/// A Python module implemented in Rust. The name of this function must match
80+
/// A Python module implemented in Rust. The name of this module must match
8981
/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to
9082
/// import the module.
91-
#[pymodule]
92-
fn string_sum(m: &Bound<'_, PyModule>) -> PyResult<()> {
93-
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
94-
Ok(())
83+
#[pyo3::pymodule]
84+
mod string_sum {
85+
use pyo3::prelude::*;
86+
87+
/// Formats the sum of two numbers as string.
88+
#[pyfunction]
89+
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
90+
Ok((a + b).to_string())
91+
}
9592
}
9693
```
9794

guide/src/module.md

Lines changed: 53 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
You can create a module using `#[pymodule]`:
44

55
```rust,no_run
6+
# mod declarative_module_basic_test {
67
use pyo3::prelude::*;
78
89
#[pyfunction]
@@ -12,18 +13,21 @@ fn double(x: usize) -> usize {
1213
1314
/// This module is implemented in Rust.
1415
#[pymodule]
15-
fn my_extension(m: &Bound<'_, PyModule>) -> PyResult<()> {
16-
m.add_function(wrap_pyfunction!(double, m)?)
16+
mod my_extension {
17+
#[pymodule_export]
18+
use super::double; // The double function is made available from Python, works also with classes
1719
}
20+
# }
1821
```
1922

20-
The `#[pymodule]` procedural macro takes care of exporting the initialization function of your
21-
module to Python.
23+
The `#[pymodule]` procedural macro takes care of creating the initialization function of your
24+
module and expose it to Python.
2225

23-
The module's name defaults to the name of the Rust function. You can override the module name by
26+
The module's name defaults to the name of the Rust module. You can override the module name by
2427
using `#[pyo3(name = "custom_name")]`:
2528

2629
```rust,no_run
30+
# mod declarative_module_custom_name_test {
2731
use pyo3::prelude::*;
2832
2933
#[pyfunction]
@@ -32,9 +36,11 @@ fn double(x: usize) -> usize {
3236
}
3337
3438
#[pymodule(name = "custom_name")]
35-
fn my_extension(m: &Bound<'_, PyModule>) -> PyResult<()> {
36-
m.add_function(wrap_pyfunction!(double, m)?)
39+
mod my_extension {
40+
#[pymodule_export]
41+
use super::double;
3742
}
43+
# }
3844
```
3945

4046
The name of the module must match the name of the `.so` or `.pyd`
@@ -48,8 +54,7 @@ To import the module, either:
4854

4955
## Documentation
5056

51-
The [Rust doc comments](https://doc.rust-lang.org/stable/book/ch03-04-comments.html) of the module
52-
initialization function will be applied automatically as the Python docstring of your module.
57+
The [Rust doc comments](https://doc.rust-lang.org/stable/book/ch03-04-comments.html) of the Rust module will be applied automatically as the Python docstring of your module.
5358

5459
For example, building off of the above code, this will print `This module is implemented in Rust.`:
5560

@@ -61,75 +66,54 @@ print(my_extension.__doc__)
6166

6267
## Python submodules
6368

64-
You can create a module hierarchy within a single extension module by using
65-
[`Bound<'_, PyModule>::add_submodule()`]({{#PYO3_DOCS_URL}}/pyo3/prelude/trait.PyModuleMethods.html#tymethod.add_submodule).
66-
For example, you could define the modules `parent_module` and `parent_module.child_module`.
69+
You can create a module hierarchy within a single extension module by just `use`ing modules like functions or classes.
70+
For example, you could define the modules `parent_module` and `parent_module.child_module`:
6771

6872
```rust
73+
# mod declarative_module_submodule_test {
6974
use pyo3::prelude::*;
7075

7176
#[pymodule]
72-
fn parent_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
73-
register_child_module(m)?;
74-
Ok(())
77+
mod parent_module {
78+
#[pymodule_export]
79+
use super::child_module;
7580
}
7681

77-
fn register_child_module(parent_module: &Bound<'_, PyModule>) -> PyResult<()> {
78-
let child_module = PyModule::new(parent_module.py(), "child_module")?;
79-
child_module.add_function(wrap_pyfunction!(func, &child_module)?)?;
80-
parent_module.add_submodule(&child_module)
82+
#[pymodule]
83+
mod child_module {
84+
#[pymodule_export]
85+
use super::func;
8186
}
8287

8388
#[pyfunction]
8489
fn func() -> String {
8590
"func".to_string()
8691
}
87-
88-
# Python::attach(|py| {
89-
# use pyo3::wrap_pymodule;
90-
# use pyo3::types::IntoPyDict;
91-
# use pyo3::ffi::c_str;
92-
# let parent_module = wrap_pymodule!(parent_module)(py);
93-
# let ctx = [("parent_module", parent_module)].into_py_dict(py).unwrap();
94-
#
95-
# py.run(c_str!("assert parent_module.child_module.func() == 'func'"), None, Some(&ctx)).unwrap();
96-
# })
92+
# }
9793
```
9894

9995
Note that this does not define a package, so this won’t allow Python code to directly import
10096
submodules by using `from parent_module import child_module`. For more information, see
10197
[#759](https://github.com/PyO3/pyo3/issues/759) and
10298
[#1517](https://github.com/PyO3/pyo3/issues/1517#issuecomment-808664021).
10399

104-
It is not necessary to add `#[pymodule]` on nested modules, which is only required on the top-level module.
100+
## Inline declaration
105101

106-
## Declarative modules
107-
108-
Another syntax based on Rust inline modules is also available to declare modules.
102+
It is possible to declare functions, classes, sub-modules and constants inline in a module:
109103

110104
For example:
111105
```rust,no_run
112106
# mod declarative_module_test {
113-
use pyo3::prelude::*;
114-
115-
#[pyfunction]
116-
fn double(x: usize) -> usize {
117-
x * 2
118-
}
119-
120-
#[pymodule]
107+
#[pyo3::pymodule]
121108
mod my_extension {
122-
use super::*;
123-
124-
#[pymodule_export]
125-
use super::double; // Exports the double function as part of the module
109+
use pyo3::prelude::*;
126110
127111
#[pymodule_export]
128112
const PI: f64 = std::f64::consts::PI; // Exports PI constant as part of the module
129113
130114
#[pyfunction] // This will be part of the module
131-
fn triple(x: usize) -> usize {
132-
x * 3
115+
fn double(x: usize) -> usize {
116+
x * 2
133117
}
134118
135119
#[pyclass] // This will be part of the module
@@ -138,47 +122,40 @@ mod my_extension {
138122
#[pymodule]
139123
mod submodule {
140124
// This is a submodule
141-
}
142-
143-
#[pymodule_init]
144-
fn init(m: &Bound<'_, PyModule>) -> PyResult<()> {
145-
// Arbitrary code to run at the module initialization
146-
m.add("double2", m.getattr("double")?)
125+
use pyo3::prelude::*;
126+
127+
#[pyclass]
128+
struct Nested;
147129
}
148130
}
149131
# }
150132
```
151133

152-
The `#[pymodule]` macro automatically sets the `module` attribute of the `#[pyclass]` macros declared inside of it with its name.
134+
In this case, `#[pymodule]` macro automatically sets the `module` attribute of the `#[pyclass]` macros declared inside of it with its name.
153135
For nested modules, the name of the parent module is automatically added.
154-
In the following example, the `Unit` class will have for `module` `my_extension.submodule` because it is properly nested
155-
but the `Ext` class will have for `module` the default `builtins` because it not nested.
136+
In the previous example, the `Nested` class will have for `module` `my_extension.submodule`.
156137

157-
```rust,no_run
158-
# mod declarative_module_module_attr_test {
159-
use pyo3::prelude::*;
138+
You can provide the `submodule` argument to `#[pymodule()]` for modules that are not top-level modules in order for them to properly generate the `#[pyclass]` `module` attribute automatically.
160139

161-
#[pyclass]
162-
struct Ext;
140+
## Procedural initialization
163141

164-
#[pymodule]
142+
If the macros provided by PyO3 are not enough, it is possible to run code at the module initialization:
143+
```rust,no_run
144+
# mod procedural_module_test {
145+
#[pyo3::pymodule]
165146
mod my_extension {
166-
use super::*;
167-
168-
#[pymodule_export]
169-
use super::Ext;
147+
use pyo3::prelude::*;
170148
171-
#[pymodule]
172-
mod submodule {
173-
use super::*;
174-
// This is a submodule
149+
#[pyfunction]
150+
fn double(x: usize) -> usize {
151+
x * 2
152+
}
175153
176-
#[pyclass] // This will be part of the module
177-
struct Unit;
154+
#[pymodule_init]
155+
fn init(m: &Bound<'_, PyModule>) -> PyResult<()> {
156+
// Arbitrary code to run at the module initialization
157+
m.add("double2", m.getattr("double")?)
178158
}
179159
}
180160
# }
181-
```
182-
It is possible to customize the `module` value for a `#[pymodule]` with the `#[pyo3(module = "MY_MODULE")]` option.
183-
184-
You can provide the `submodule` argument to `pymodule()` for modules that are not top-level modules -- it is automatically set for modules nested inside of a `#[pymodule]`.
161+
```

0 commit comments

Comments
 (0)