guide: tidy up doctests

This commit is contained in:
David Hewitt 2022-03-04 22:41:25 +00:00
parent 576818dc0c
commit 2bd64c4962
5 changed files with 87 additions and 45 deletions

View file

@ -47,6 +47,7 @@ rustversion = "1.0"
# 1.0.0 requires Rust 1.50 # 1.0.0 requires Rust 1.50
proptest = { version = "0.10.1", default-features = false, features = ["std"] } proptest = { version = "0.10.1", default-features = false, features = ["std"] }
send_wrapper = "0.5" send_wrapper = "0.5"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.61" serde_json = "1.0.61"
[build-dependencies] [build-dependencies]

View file

@ -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) 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}} {{#include ../../../examples/decorator/src/lib.rs}}
``` ```

View file

@ -115,6 +115,11 @@ Enables (de)serialization of Py<T> 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 This allows to use [`#[derive(Serialize, Deserialize)`](https://serde.rs/derive.html) on structs that hold references to `#[pyclass]` instances
```rust ```rust
# #[cfg(feature = "serde")]
# #[allow(dead_code)]
# mod serde_only {
# use pyo3::prelude::*;
# use serde::{Deserialize, Serialize};
#[pyclass] #[pyclass]
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -128,4 +133,5 @@ struct User {
username: String, username: String,
permissions: Vec<Py<Permission>> permissions: Vec<Py<Permission>>
} }
# }
``` ```

View file

@ -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: very simple and easy-to-understand programs like this:
```rust ```rust
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
Python::with_gil(|py| -> PyResult<()> { Python::with_gil(|py| -> PyResult<()> {
let hello: &PyString = py.eval("\"Hello World!\"", None, None)?.extract()?; let hello: &PyString = py.eval("\"Hello World!\"", None, None)?.extract()?;
println!("Python says: {}", hello); println!("Python says: {}", hello);
Ok(()) Ok(())
})?; })?;
# Ok(())
# }
``` ```
Internally, calling `Python::with_gil()` or `Python::acquire_gil()` creates a 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: of the time we don't have to think about this, but consider the following:
```rust ```rust
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
Python::with_gil(|py| -> PyResult<()> { Python::with_gil(|py| -> PyResult<()> {
for _ in 0..10 { for _ in 0..10 {
let hello: &PyString = py.eval("\"Hello World!\"", None, None)?.extract()?; 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. // There are 10 copies of `hello` on Python's heap here.
Ok(()) Ok(())
})?; })?;
# Ok(())
# }
``` ```
We might assume that the `hello` variable's memory is freed at the end of each 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. is to acquire and release the GIL with each iteration of the loop.
```rust ```rust
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
for _ in 0..10 { for _ in 0..10 {
Python::with_gil(|py| -> PyResult<()> { Python::with_gil(|py| -> PyResult<()> {
let hello: &PyString = py.eval("\"Hello World!\"", None, None)?.extract()?; let hello: &PyString = py.eval("\"Hello World!\"", None, None)?.extract()?;
@ -69,6 +82,8 @@ for _ in 0..10 {
Ok(()) Ok(())
})?; // only one copy of `hello` at a time })?; // only one copy of `hello` at a time
} }
# Ok(())
# }
``` ```
It might not be practical or performant to acquire and release the GIL so many 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. this is unsafe.
```rust ```rust
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
Python::with_gil(|py| -> PyResult<()> { Python::with_gil(|py| -> PyResult<()> {
for _ in 0..10 { for _ in 0..10 {
let pool = unsafe { py.new_pool() }; let pool = unsafe { py.new_pool() };
@ -85,6 +103,8 @@ Python::with_gil(|py| -> PyResult<()> {
} }
Ok(()) Ok(())
})?; })?;
# Ok(())
# }
``` ```
The unsafe method `Python::new_pool` allows you to create a nested `GILPool` 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<PyAny>` is dropped and its
reference count reaches zero? It depends whether or not we are holding the GIL. reference count reaches zero? It depends whether or not we are holding the GIL.
```rust ```rust
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
Python::with_gil(|py| -> PyResult<()> { Python::with_gil(|py| -> PyResult<()> {
let hello: Py<PyString> = py.eval("\"Hello World!\"", None, None)?.extract())?; let hello: Py<PyString> = py.eval("\"Hello World!\"", None, None)?.extract()?;
println!("Python says: {}", hello.as_ref(py)); println!("Python says: {}", hello.as_ref(py));
Ok(()) Ok(())
}); })?;
# Ok(())
# }
``` ```
At the end of the `Python::with_gil()` closure `hello` is dropped, and then the 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? we are *not* holding the GIL?
```rust ```rust
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
let hello: Py<PyString> = Python::with_gil(|py| { let hello: Py<PyString> = Python::with_gil(|py| {
py.eval("\"Hello World!\"", None, None)?.extract()) py.eval("\"Hello World!\"", None, None)?.extract()
})?; })?;
// Do some stuff... // Do some stuff...
// Now sometime later in the program we want to access `hello`. // 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... // Sometime later we need the GIL again for something...
Python::with_gil(|py| Python::with_gil(|py|
// Memory for `hello` is released here. // Memory for `hello` is released here.
# ()
); );
# Ok(())
# }
``` ```
When `hello` is dropped *nothing* happens to the pointed-to memory on Python's 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<Any>` while the GIL is held. `Py<Any>` while the GIL is held.
```rust ```rust
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
let hello: Py<PyString> = Python::with_gil(|py| { let hello: Py<PyString> = Python::with_gil(|py| {
py.eval("\"Hello World!\"", None, None)?.extract()) py.eval("\"Hello World!\"", None, None)?.extract()
})?; })?;
// Do some stuff... // Do some stuff...
// Now sometime later in the program: // Now sometime later in the program:
@ -163,6 +197,8 @@ Python::with_gil(|py| {
println!("Python says: {}", hello.as_ref(py)); println!("Python says: {}", hello.as_ref(py));
drop(hello); // Memory released here. drop(hello); // Memory released here.
}); });
# Ok(())
# }
``` ```
We could also have used `Py::into_ref()`, which consumes `self`, instead of 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. until the GIL is dropped.
```rust ```rust
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
let hello: Py<PyString> = Python::with_gil(|py| { let hello: Py<PyString> = Python::with_gil(|py| {
py.eval("\"Hello World!\"", None, None)?.extract()) py.eval("\"Hello World!\"", None, None)?.extract()
})?; })?;
// Do some stuff... // Do some stuff...
// Now sometime later in the program: // Now sometime later in the program:
@ -183,4 +222,6 @@ Python::with_gil(|py| {
// Do more stuff... // Do more stuff...
// Memory released here at end of `with_gil()` closure. // Memory released here at end of `with_gil()` closure.
}); });
# Ok(())
# }
``` ```

View file

@ -417,48 +417,42 @@ pub mod doc_test {
}; };
} }
macro_rules! doctest { macro_rules! doctests {
($path:expr, $mod:ident) => { ($($path:expr => $mod:ident),* $(,)?) => {
doctest_impl!(include_str!(concat!("../", $path)), $mod); $(doctest_impl!(include_str!(concat!("../", $path)), $mod);)*
}; };
} }
doctest!("README.md", readme_md); doctests! {
doctest!("guide/src/advanced.md", guide_advanced_md); "README.md" => readme_md,
doctest!( "guide/src/advanced.md" => guide_advanced_md,
"guide/src/building_and_distribution.md", "guide/src/building_and_distribution.md" => guide_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,
doctest!("guide/src/class.md", guide_class_md); "guide/src/class/call.md" => guide_class_call,
doctest!("guide/src/class/protocols.md", guide_class_protocols_md); "guide/src/class/object.md" => guide_class_object,
doctest!("guide/src/conversions.md", guide_conversions_md); "guide/src/class/numeric.md" => guide_class_numeric,
doctest!( "guide/src/class/protocols.md" => guide_class_protocols_md,
"guide/src/conversions/tables.md", "guide/src/conversions.md" => guide_conversions_md,
guide_conversions_tables_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!( // deliberate choice not to test guide/ecosystem because those pages depend on external
"guide/src/conversions/traits.md", // crates such as pyo3_asyncio.
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 "guide/src/exception.md" => guide_exception_md,
// such as pyo3_asyncio. "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,
}
} }