diff --git a/Cargo.toml b/Cargo.toml index 27721fb0..6e1e9880 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ rustversion = "1.0" # 1.0.0 requires Rust 1.50 proptest = { version = "0.10.1", default-features = false, features = ["std"] } send_wrapper = "0.5" +serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.61" [build-dependencies] diff --git a/guide/src/class/call.md b/guide/src/class/call.md index c9fe1217..9a470373 100644 --- a/guide/src/class/call.md +++ b/guide/src/class/call.md @@ -14,7 +14,7 @@ is linked at the end. An example crate containing this pyclass can be found [here](https://github.com/PyO3/pyo3/tree/main/examples/decorator) -```rust +```rust,ignore {{#include ../../../examples/decorator/src/lib.rs}} ``` diff --git a/guide/src/features.md b/guide/src/features.md index 19b8bc24..92d04815 100644 --- a/guide/src/features.md +++ b/guide/src/features.md @@ -115,6 +115,11 @@ Enables (de)serialization of Py objects via [serde](https://serde.rs/). This allows to use [`#[derive(Serialize, Deserialize)`](https://serde.rs/derive.html) on structs that hold references to `#[pyclass]` instances ```rust +# #[cfg(feature = "serde")] +# #[allow(dead_code)] +# mod serde_only { +# use pyo3::prelude::*; +# use serde::{Deserialize, Serialize}; #[pyclass] #[derive(Serialize, Deserialize)] @@ -128,4 +133,5 @@ struct User { username: String, permissions: Vec> } +# } ``` diff --git a/guide/src/memory.md b/guide/src/memory.md index 8d970c98..5b43dd78 100644 --- a/guide/src/memory.md +++ b/guide/src/memory.md @@ -23,11 +23,16 @@ held. (If PyO3 could not assume this, every PyO3 API would need to take a very simple and easy-to-understand programs like this: ```rust +# use pyo3::prelude::*; +# use pyo3::types::PyString; +# fn main() -> PyResult<()> { Python::with_gil(|py| -> PyResult<()> { let hello: &PyString = py.eval("\"Hello World!\"", None, None)?.extract()?; println!("Python says: {}", hello); Ok(()) })?; +# Ok(()) +# } ``` Internally, calling `Python::with_gil()` or `Python::acquire_gil()` creates a @@ -39,6 +44,9 @@ it owns are decreased, releasing them to the Python garbage collector. Most of the time we don't have to think about this, but consider the following: ```rust +# use pyo3::prelude::*; +# use pyo3::types::PyString; +# fn main() -> PyResult<()> { Python::with_gil(|py| -> PyResult<()> { for _ in 0..10 { let hello: &PyString = py.eval("\"Hello World!\"", None, None)?.extract()?; @@ -47,6 +55,8 @@ Python::with_gil(|py| -> PyResult<()> { // There are 10 copies of `hello` on Python's heap here. Ok(()) })?; +# Ok(()) +# } ``` We might assume that the `hello` variable's memory is freed at the end of each @@ -62,6 +72,9 @@ In general we don't want unbounded memory growth during loops! One workaround is to acquire and release the GIL with each iteration of the loop. ```rust +# use pyo3::prelude::*; +# use pyo3::types::PyString; +# fn main() -> PyResult<()> { for _ in 0..10 { Python::with_gil(|py| -> PyResult<()> { let hello: &PyString = py.eval("\"Hello World!\"", None, None)?.extract()?; @@ -69,6 +82,8 @@ for _ in 0..10 { Ok(()) })?; // only one copy of `hello` at a time } +# Ok(()) +# } ``` It might not be practical or performant to acquire and release the GIL so many @@ -76,6 +91,9 @@ times. Another workaround is to work with the `GILPool` object directly, but this is unsafe. ```rust +# use pyo3::prelude::*; +# use pyo3::types::PyString; +# fn main() -> PyResult<()> { Python::with_gil(|py| -> PyResult<()> { for _ in 0..10 { let pool = unsafe { py.new_pool() }; @@ -85,6 +103,8 @@ Python::with_gil(|py| -> PyResult<()> { } Ok(()) })?; +# Ok(()) +# } ``` The unsafe method `Python::new_pool` allows you to create a nested `GILPool` @@ -112,11 +132,16 @@ What happens to the memory when the last `Py` is dropped and its reference count reaches zero? It depends whether or not we are holding the GIL. ```rust +# use pyo3::prelude::*; +# use pyo3::types::PyString; +# fn main() -> PyResult<()> { Python::with_gil(|py| -> PyResult<()> { - let hello: Py = py.eval("\"Hello World!\"", None, None)?.extract())?; + let hello: Py = py.eval("\"Hello World!\"", None, None)?.extract()?; println!("Python says: {}", hello.as_ref(py)); Ok(()) -}); +})?; +# Ok(()) +# } ``` At the end of the `Python::with_gil()` closure `hello` is dropped, and then the @@ -129,8 +154,11 @@ This example wasn't very interesting. We could have just used a GIL-bound we are *not* holding the GIL? ```rust +# use pyo3::prelude::*; +# use pyo3::types::PyString; +# fn main() -> PyResult<()> { let hello: Py = Python::with_gil(|py| { - py.eval("\"Hello World!\"", None, None)?.extract()) + py.eval("\"Hello World!\"", None, None)?.extract() })?; // Do some stuff... // Now sometime later in the program we want to access `hello`. @@ -142,7 +170,10 @@ drop(hello); // Memory *not* released here. // Sometime later we need the GIL again for something... Python::with_gil(|py| // Memory for `hello` is released here. +# () ); +# Ok(()) +# } ``` When `hello` is dropped *nothing* happens to the pointed-to memory on Python's @@ -154,8 +185,11 @@ We can avoid the delay in releasing memory if we are careful to drop the `Py` while the GIL is held. ```rust +# use pyo3::prelude::*; +# use pyo3::types::PyString; +# fn main() -> PyResult<()> { let hello: Py = Python::with_gil(|py| { - py.eval("\"Hello World!\"", None, None)?.extract()) + py.eval("\"Hello World!\"", None, None)?.extract() })?; // Do some stuff... // Now sometime later in the program: @@ -163,6 +197,8 @@ Python::with_gil(|py| { println!("Python says: {}", hello.as_ref(py)); drop(hello); // Memory released here. }); +# Ok(()) +# } ``` We could also have used `Py::into_ref()`, which consumes `self`, instead of @@ -172,8 +208,11 @@ that rather than being released immediately, the memory will not be released until the GIL is dropped. ```rust +# use pyo3::prelude::*; +# use pyo3::types::PyString; +# fn main() -> PyResult<()> { let hello: Py = Python::with_gil(|py| { - py.eval("\"Hello World!\"", None, None)?.extract()) + py.eval("\"Hello World!\"", None, None)?.extract() })?; // Do some stuff... // Now sometime later in the program: @@ -183,4 +222,6 @@ Python::with_gil(|py| { // Do more stuff... // Memory released here at end of `with_gil()` closure. }); +# Ok(()) +# } ``` diff --git a/src/lib.rs b/src/lib.rs index 41c4c074..aabe363c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -417,48 +417,42 @@ pub mod doc_test { }; } - macro_rules! doctest { - ($path:expr, $mod:ident) => { - doctest_impl!(include_str!(concat!("../", $path)), $mod); + macro_rules! doctests { + ($($path:expr => $mod:ident),* $(,)?) => { + $(doctest_impl!(include_str!(concat!("../", $path)), $mod);)* }; } - doctest!("README.md", readme_md); - doctest!("guide/src/advanced.md", guide_advanced_md); - doctest!( - "guide/src/building_and_distribution.md", - guide_building_and_distribution_md - ); - doctest!("guide/src/class.md", guide_class_md); - doctest!("guide/src/class/protocols.md", guide_class_protocols_md); - doctest!("guide/src/conversions.md", guide_conversions_md); - doctest!( - "guide/src/conversions/tables.md", - guide_conversions_tables_md - ); + doctests! { + "README.md" => readme_md, + "guide/src/advanced.md" => guide_advanced_md, + "guide/src/building_and_distribution.md" => guide_building_and_distribution_md, + "guide/src/building_and_distribution/multiple_python_versions.md" => guide_bnd_multiple_python_versions_md, + "guide/src/class.md" => guide_class_md, + "guide/src/class/call.md" => guide_class_call, + "guide/src/class/object.md" => guide_class_object, + "guide/src/class/numeric.md" => guide_class_numeric, + "guide/src/class/protocols.md" => guide_class_protocols_md, + "guide/src/conversions.md" => guide_conversions_md, + "guide/src/conversions/tables.md" => guide_conversions_tables_md, + "guide/src/conversions/traits.md" => guide_conversions_traits_md, + "guide/src/debugging.md" => guide_debugging_md, - doctest!( - "guide/src/conversions/traits.md", - guide_conversions_traits_md - ); - doctest!("guide/src/debugging.md", guide_debugging_md); - doctest!("guide/src/exception.md", guide_exception_md); - doctest!("guide/src/function.md", guide_function_md); - doctest!("guide/src/migration.md", guide_migration_md); - doctest!("guide/src/module.md", guide_module_md); - doctest!("guide/src/parallelism.md", guide_parallelism_md); - doctest!("guide/src/python_from_rust.md", guide_python_from_rust_md); - doctest!("guide/src/rust_cpython.md", guide_rust_cpython_md); - doctest!("guide/src/trait_bounds.md", guide_trait_bounds_md); - doctest!("guide/src/types.md", guide_types_md); - doctest!("guide/src/faq.md", faq); - doctest!( - "guide/src/python_typing_hints.md", - guide_python_typing_hints - ); - doctest!("guide/src/class/object.md", guide_class_object); - doctest!("guide/src/class/numeric.md", guide_class_numeric); + // deliberate choice not to test guide/ecosystem because those pages depend on external + // crates such as pyo3_asyncio. - // deliberate choice not to test guide/ecosystem because those pages depend on external crates - // such as pyo3_asyncio. + "guide/src/exception.md" => guide_exception_md, + "guide/src/faq.md" => guide_faq_md, + "guide/src/features.md" => guide_features_md, + "guide/src/function.md" => guide_function_md, + "guide/src/memory.md" => guide_memory_md, + "guide/src/migration.md" => guide_migration_md, + "guide/src/module.md" => guide_module_md, + "guide/src/parallelism.md" => guide_parallelism_md, + "guide/src/python_from_rust.md" => guide_python_from_rust_md, + "guide/src/python_typing_hints.md" => guide_python_typing_hints_md, + "guide/src/rust_cpython.md" => guide_rust_cpython_md, + "guide/src/trait_bounds.md" => guide_trait_bounds_md, + "guide/src/types.md" => guide_types_md, + } }