Un-ignore and expand on doc examples

This commit is contained in:
mejrs 2022-05-03 00:47:09 +02:00
parent f34b92a368
commit f1e5d4c9a1
17 changed files with 224 additions and 91 deletions

View File

@ -48,6 +48,7 @@ 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"
rayon = "1.0.2"
[build-dependencies]
pyo3-build-config = { path = "pyo3-build-config", version = "0.16.4", features = ["resolve-config"] }

4
assets/script.py Normal file
View File

@ -0,0 +1,4 @@
# Used in PyModule examples.
class Blah:
pass

View File

@ -212,13 +212,13 @@ The known complications are:
Significantly different compiler versions may see errors like this:
```ignore
```text
lto1: fatal error: bytecode stream in file 'rust-numpy/target/release/deps/libpyo3-6a7fb2ed970dbf26.rlib' generated with LTO version 6.0 instead of the expected 6.2
```
Mismatching flags may lead to errors like this:
```ignore
```text
/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/libpython3.9.a(zlibmodule.o): relocation R_X86_64_32 against `.data' can not be used when making a PIE object; recompile with -fPIE
```

View File

@ -53,31 +53,31 @@ After these steps you are ready to annotate your code!
The `#[cfg]` flags added by `pyo3-build-cfg` can be combined with all of Rust's logic in the `#[cfg]` attribute to create very precise conditional code generation. The following are some common patterns implemented using these flags:
```rust,ignore
```text
#[cfg(Py_3_7)]
```
This `#[cfg]` marks code that will only be present on Python 3.7 and upwards. There are similar options `Py_3_8`, `Py_3_9`, `Py_3_10` and so on for each minor version.
```rust,ignore
```text
#[cfg(not(Py_3_7))]
```
This `#[cfg]` marks code that will only be present on Python versions before (but not including) Python 3.7.
```rust,ignore
```text
#[cfg(not(Py_LIMITED_API))]
```
This `#[cfg]` marks code that is only available when building for the unlimited Python API (i.e. PyO3's `abi3` feature is not enabled). This might be useful if you want to ship your extension module as an `abi3` wheel and also allow users to compile it from source to make use of optimizations only possible with the unlimited API.
```rust,ignore
```text
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
```
This `#[cfg]` marks code which is available when running Python 3.9 or newer, or when using the unlimited API with an older Python version. Patterns like this are commonly seen on Python APIs which were added to the limited Python API in a specific minor version.
```rust,ignore
```text
#[cfg(PyPy)]
```
@ -93,10 +93,16 @@ There's no way to detect your user doing that at compile time, so instead you ne
PyO3 provides the APIs [`Python::version()`] and [`Python::version_info()`] to query the running Python version. This allows you to do the following, for example:
```rust,ignore
if py.version_info() >= (3, 9) {
// run this code only if Python 3.9 or up
}
```rust
use pyo3::Python;
Python::with_gil(|py| {
// PyO3 supports Python 3.7 and up.
assert!(py.version_info() >= (3, 7));
assert!(py.version_info() >= (3, 7, 0));
});
```
[`Python::version()`]: {{#PYO3_DOCS_URL}}/pyo3/struct.Python.html#method.version

View File

@ -606,9 +606,24 @@ Python::with_gil(|py| {
Note that unlike class variables defined in Python code, class attributes defined in Rust cannot
be mutated at all:
```rust,ignore
// Would raise a `TypeError: can't set attributes of built-in/extension type 'MyClass'`
pyo3::py_run!(py, my_class, "my_class.my_attribute = 'foo'")
```rust,should_panic
# use pyo3::prelude::*;
# #[pyclass]
# struct MyClass {}
# #[pymethods]
# impl MyClass {
# #[classattr]
# fn my_attribute() -> String {
# "hello".to_string()
# }
# }
#
Python::with_gil(|py| {
let my_class = py.get_type::<MyClass>();
// Would raise a `TypeError: can't set attributes of built-in/extension type 'MyClass'`
pyo3::py_run!(py, my_class, "my_class.my_attribute = 'foo'")
});
```
If the class attribute is defined with `const` code only, one can also annotate associated

View File

@ -8,8 +8,17 @@ The easiest way to convert a Python object to a Rust value is using
`.extract()`. It returns a `PyResult` with a type error if the conversion
fails, so usually you will use something like
```ignore
let v: Vec<i32> = obj.extract()?;
```rust
# use pyo3::prelude::*;
# use pyo3::types::PyList;
# fn main() -> PyResult<()> {
# Python::with_gil(|py| {
# let list = PyList::new(py, b"foo");
let v: Vec<i32> = list.extract()?;
# assert_eq!(&v, &[102, 111, 111]);
# Ok(())
# })
# }
```
This method is available for many Python object types, and can produce a wide

View File

@ -33,9 +33,12 @@ Python::with_gil(|py| {
When using PyO3 to create an extension module, you can add the new exception to
the module like this, so that it is importable from Python:
```rust,ignore
```rust
use pyo3::prelude::*;
use pyo3::types::PyModule;
use pyo3::exceptions::PyException;
create_exception!(mymodule, CustomError, PyException);
pyo3::create_exception!(mymodule, CustomError, PyException);
#[pymodule]
fn mymodule(py: Python<'_>, m: &PyModule) -> PyResult<()> {
@ -77,7 +80,7 @@ fn divide(a: i32, b: i32) -> PyResult<i32> {
# }
```
You can also manually write and fetch errors in the Python interpreter's global state:
You can manually write and fetch errors in the Python interpreter's global state:
```rust
use pyo3::{Python, PyErr};
@ -90,12 +93,7 @@ Python::with_gil(|py| {
});
```
If you already have a Python exception object, you can simply call [`PyErr::from_value`].
```rust,ignore
PyErr::from_value(py, err).restore(py);
```
If you already have a Python exception object, you can use [`PyErr::from_value`] to create a `PyErr` from it.
## Checking exception types

View File

@ -207,7 +207,7 @@ fn sub(a: u64, b: u64) -> u64 {
When annotated like this, signatures are also correctly displayed in IPython.
```ignore
```text
>>> pyo3_test.add?
Signature: pyo3_test.add(a, b, /)
Docstring: This function adds two unsigned 64-bit integers.

View File

@ -46,7 +46,7 @@ To migrate, update trait bounds and imports from `PyTypeObject` to `PyTypeInfo`.
Before:
```rust,ignore
```rust,compile_fail
use pyo3::Python;
use pyo3::type_object::PyTypeObject;
use pyo3::types::PyType;
@ -85,7 +85,7 @@ Migration from `#[pyproto]` to `#[pymethods]` is straightforward; copying the ex
Before:
```rust,ignore
```rust,compile_fail
use pyo3::prelude::*;
use pyo3::class::{PyBasicProtocol, PyIterProtocol};
use pyo3::types::PyString;
@ -110,7 +110,7 @@ impl PyIterProtocol for MyClass {
After
```rust,ignore
```rust,compile_fail
use pyo3::prelude::*;
use pyo3::types::PyString;
@ -274,7 +274,7 @@ To migrate just move the affected methods from a `#[pyproto]` to a `#[pymethods]
Before:
```rust,ignore
```rust,compile_fail
use pyo3::prelude::*;
use pyo3::class::basic::PyBasicProtocol;
@ -358,7 +358,7 @@ Exception types](#exception-types-have-been-reworked)).
This implementation was redundant. Just construct the `Result::Err` variant directly.
Before:
```rust,ignore
```rust,compile_fail
let result: PyResult<()> = PyErr::new::<TypeError, _>("error message").into();
```
@ -376,13 +376,13 @@ makes it possible to interact with Python exception objects.
The new types also have names starting with the "Py" prefix. For example, before:
```rust,ignore
```rust,compile_fail
let err: PyErr = TypeError::py_err("error message");
```
After:
```rust,ignore
```rust,compile_fail
# use pyo3::{PyErr, PyResult, Python, type_object::PyTypeObject};
# use pyo3::exceptions::{PyBaseException, PyTypeError};
# Python::with_gil(|py| -> PyResult<()> {
@ -407,7 +407,7 @@ Now there is only one way to define the conversion, `IntoPy`, so downstream crat
adjust accordingly.
Before:
```rust,ignore
```rust,compile_fail
# use pyo3::prelude::*;
struct MyPyObjectWrapper(PyObject);
@ -433,7 +433,7 @@ impl IntoPy<PyObject> for MyPyObjectWrapper {
Similarly, code which was using the `FromPy` trait can be trivially rewritten to use `IntoPy`.
Before:
```rust,ignore
```rust,compile_fail
# use pyo3::prelude::*;
# Python::with_gil(|py| {
let obj = PyObject::from_py(1.234, py);
@ -461,7 +461,7 @@ This should require no code changes except removing `use pyo3::AsPyRef` for code
`pyo3::prelude::*`.
Before:
```rust,ignore
```rust,compile_fail
use pyo3::{AsPyRef, Py, types::PyList};
# pyo3::Python::with_gil(|py| {
let list_py: Py<PyList> = PyList::empty(py).into();
@ -722,7 +722,7 @@ If `T` implements `Clone`, you can extract `T` itself.
In addition, you can also extract `&PyCell<T>`, though you rarely need it.
Before:
```ignore
```compile_fail
let obj: &PyAny = create_obj();
let obj_ref: &MyClass = obj.extract().unwrap();
let obj_ref_mut: &mut MyClass = obj.extract().unwrap();
@ -775,7 +775,9 @@ impl PySequenceProtocol for ByteSequence {
```
After:
```rust,ignore
```rust
# #[cfg(feature = "pyproto")]
# {
# use pyo3::prelude::*;
# use pyo3::class::PySequenceProtocol;
#[pyclass]
@ -790,6 +792,7 @@ impl PySequenceProtocol for ByteSequence {
Ok(Self { elements })
}
}
}
```
[`FromPyObject`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.FromPyObject.html

View File

@ -3,7 +3,25 @@
CPython has the infamous [Global Interpreter Lock](https://docs.python.org/3/glossary.html#term-global-interpreter-lock), which prevents several threads from executing Python bytecode in parallel. This makes threading in Python a bad fit for [CPU-bound](https://stackoverflow.com/questions/868568/) tasks and often forces developers to accept the overhead of multiprocessing.
In PyO3 parallelism can be easily achieved in Rust-only code. Let's take a look at our [word-count](https://github.com/PyO3/pyo3/blob/main/examples/word-count/src/lib.rs) example, where we have a `search` function that utilizes the [rayon](https://github.com/rayon-rs/rayon) crate to count words in parallel.
```rust, ignore
```rust,no_run
# #![allow(dead_code)]
use pyo3::prelude::*;
// These traits let us use `par_lines` and `map`.
use rayon::str::ParallelString;
use rayon::iter::ParallelIterator;
/// Count the occurrences of needle in line, case insensitive
fn count_line(line: &str, needle: &str) -> usize {
let mut total = 0;
for word in line.split(' ') {
if word == needle {
total += 1;
}
}
total
}
#[pyfunction]
fn search(contents: &str, needle: &str) -> usize {
contents
@ -14,14 +32,41 @@ fn search(contents: &str, needle: &str) -> usize {
```
But let's assume you have a long running Rust function which you would like to execute several times in parallel. For the sake of example let's take a sequential version of the word count:
```rust, ignore
```rust,no_run
# #![allow(dead_code)]
# fn count_line(line: &str, needle: &str) -> usize {
# let mut total = 0;
# for word in line.split(' ') {
# if word == needle {
# total += 1;
# }
# }
# total
# }
#
fn search_sequential(contents: &str, needle: &str) -> usize {
contents.lines().map(|line| count_line(line, needle)).sum()
}
```
To enable parallel execution of this function, the [`Python::allow_threads`] method can be used to temporarily release the GIL, thus allowing other Python threads to run. We then have a function exposed to the Python runtime which calls `search_sequential` inside a closure passed to [`Python::allow_threads`] to enable true parallelism:
```rust, ignore
```rust,no_run
# #![allow(dead_code)]
# use pyo3::prelude::*;
#
# fn count_line(line: &str, needle: &str) -> usize {
# let mut total = 0;
# for word in line.split(' ') {
# if word == needle {
# total += 1;
# }
# }
# total
# }
#
# fn search_sequential(contents: &str, needle: &str) -> usize {
# contents.lines().map(|line| count_line(line, needle)).sum()
# }
#[pyfunction]
fn search_sequential_allow_threads(py: Python<'_>, contents: &str, needle: &str) -> usize {
py.allow_threads(|| search_sequential(contents, needle))
@ -59,7 +104,7 @@ We are using `pytest-benchmark` to benchmark four word count functions:
The benchmark script can be found [here](https://github.com/PyO3/pyo3/blob/main/examples/word-count/tests/test_word_count.py), and we can run `nox` in the `word-count` folder to benchmark these functions.
While the results of the benchmark of course depend on your machine, the relative results should be similar to this (mid 2020):
```ignore
```text
-------------------------------------------------------------------------------------------------- benchmark: 4 tests -------------------------------------------------------------------------------------------------
Name (time in ms) Min Max Mean StdDev Median IQR Outliers OPS Rounds Iterations
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -311,7 +311,7 @@ from anywhere as long as your `app.py` is in the expected directory (in this exa
that directory is `/usr/share/python_app`).
`src/main.rs`:
```ignore
```no_run
use pyo3::prelude::*;
use pyo3::types::PyList;
use std::fs;

View File

@ -210,7 +210,7 @@ macro_rules! callback_body {
///
/// For example this pyfunction:
///
/// ```ignore
/// ```no_compile
/// fn foo(&self) -> &Bar {
/// &self.bar
/// }
@ -218,7 +218,7 @@ macro_rules! callback_body {
///
/// It is wrapped in proc macros with handle_panic like so:
///
/// ```ignore
/// ```no_compile
/// pyo3::callback::handle_panic(|_py| {
/// let _slf = #slf;
/// pyo3::callback::convert(_py, #foo)
@ -227,7 +227,7 @@ macro_rules! callback_body {
///
/// If callback_body was used instead:
///
/// ```ignore
/// ```no_compile
/// pyo3::callback_body!(py, {
/// let _slf = #slf;
/// #foo

View File

@ -77,15 +77,40 @@ impl PyErr {
/// If an error occurs during normalization (for example if `T` is not a Python type which
/// extends from `BaseException`), then a different error may be produced during normalization.
///
/// # Example
/// # Examples
///
/// ```ignore
/// return Err(PyErr::new::<exceptions::PyTypeError, _>("Error message"));
/// ```
/// use pyo3::prelude::*;
/// use pyo3::exceptions::PyTypeError;
///
/// #[pyfunction]
/// fn always_throws() -> PyResult<()> {
/// Err(PyErr::new::<PyTypeError, _>("Error message"))
/// }
/// #
/// # Python::with_gil(|py| {
/// # let fun = pyo3::wrap_pyfunction!(always_throws, py).unwrap();
/// # let err = fun.call0().expect_err("called a function that should always return an error but the return value was Ok");
/// # assert!(err.is_instance_of::<PyTypeError>(py))
/// # });
/// ```
///
/// In most cases, you can use a concrete exception's constructor instead, which is equivalent:
/// ```ignore
/// return Err(exceptions::PyTypeError::new_err("Error message"));
/// In most cases, you can use a concrete exception's constructor instead:
///
/// ```
/// use pyo3::prelude::*;
/// use pyo3::exceptions::PyTypeError;
///
/// #[pyfunction]
/// fn always_throws() -> PyResult<()> {
/// Err(PyTypeError::new_err("Error message"))
/// }
/// #
/// # Python::with_gil(|py| {
/// # let fun = pyo3::wrap_pyfunction!(always_throws, py).unwrap();
/// # let err = fun.call0().expect_err("called a function that should always return an error but the return value was Ok");
/// # assert!(err.is_instance_of::<PyTypeError>(py))
/// # });
/// ```
#[inline]
pub fn new<T, A>(args: A) -> PyErr

View File

@ -97,14 +97,16 @@ pub fn prepare_freethreaded_python() {
/// - The return value of the closure must not contain any Python value, _including_ `PyResult`.
///
/// # Examples
/// ```rust
/// use pyo3::prelude::*;
///
/// # fn main() -> PyResult<()>{
/// ```rust
/// unsafe {
/// pyo3::with_embedded_python_interpreter(|py| py.run("print('Hello World')", None, None))
/// pyo3::with_embedded_python_interpreter(|py| {
/// if let Err(e) = py.run("print('Hello World')", None, None){
/// // We must make sure to not return a `PyErr`!
/// e.print(py);
/// }
/// });
/// }
/// # }
/// ```
#[cfg(not(PyPy))]
pub unsafe fn with_embedded_python_interpreter<F, R>(f: F) -> R

View File

@ -8,17 +8,17 @@
rustdoc::bare_urls
)
)]
#![warn(elided_lifetimes_in_paths, unused_lifetimes)]
#![warn(rust_2018_idioms, unused_lifetimes)]
// Deny some lints in doctests.
// Use `#[allow(...)]` locally to override.
#![doc(test(attr(
deny(
elided_lifetimes_in_paths,
rust_2018_idioms,
unused_lifetimes,
rust_2021_prelude_collisions,
warnings
),
allow(unused_variables, unused_assignments)
allow(unused_variables, unused_assignments, unused_extern_crates)
)))]
//! Rust bindings to the Python interpreter.

View File

@ -41,15 +41,39 @@
//! The [`#[pymethods]`](crate::pymethods) proc macro will generate this wrapper function (and more),
//! using [`PyCell`] under the hood:
//!
//! ```ignore
//! ```rust
//! # use pyo3::prelude::*;
//! # #[pyclass]
//! # struct Number {
//! # inner: u32,
//! # }
//! #
//! # #[pymethods]
//! # impl Number {
//! # fn increment(&mut self) {
//! # self.inner += 1;
//! # }
//! # }
//! #
//! // This function is exported to Python.
//! unsafe extern "C" fn __wrap(slf: *mut PyObject, _args: *mut PyObject) -> *mut PyObject {
//! pyo3::callback::handle_panic(|py| {
//! let cell: &PyCell<Number> = py.from_borrowed_ptr(slf);
//! let mut _ref: PyRefMut<Number> = cell.try_borrow_mut()?;
//! let slf: &mut Number = &mut _ref;
//! pyo3::callback::convert(py, Number::increment(slf))
//! })
//! unsafe extern "C" fn __wrap(
//! _slf: *mut ::pyo3::ffi::PyObject,
//! _args: *mut ::pyo3::ffi::PyObject,
//! ) -> *mut ::pyo3::ffi::PyObject {
//! use :: pyo3 as _pyo3;
//! let gil = _pyo3::GILPool::new();
//! let _py = gil.python();
//! _pyo3::callback::panic_result_into_callback_output(
//! _py,
//! ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> {
//! let _cell = _py
//! .from_borrowed_ptr::<_pyo3::PyAny>(_slf)
//! .downcast::<_pyo3::PyCell<Number>>()?;
//! let mut _ref = _cell.try_borrow_mut()?;
//! let _slf: &mut Number = &mut *_ref;
//! _pyo3::callback::convert(_py, Number::increment(_slf))
//! }),
//! )
//! }
//! ```
//!

View File

@ -92,43 +92,44 @@ impl PyModule {
/// - Any Python exceptions are raised while initializing the module.
/// - Any of the arguments cannot be converted to [`CString`](std::ffi::CString)s.
///
/// # Examples
/// # Example: bundle in a file at compile time with [`include_str!`][1]:
///
/// Include a file at compile time by using [`std::include_str` macro][1]:
///
/// ```ignore
/// ```rust
/// use pyo3::prelude::*;
///
/// # fn main() -> PyResult<()> {
/// let code = include_str!("../example.py");
/// Python::with_gil(|py| -> PyResult<()> {
/// PyModule::from_code(py, code, "example", "example")?;
/// Ok(())
/// })?;
/// Ok(())
/// // This path is resolved relative to this file.
/// let code = include_str!("../../assets/script.py");
///
/// Python::with_gil(|py| -> PyResult<()> {
/// PyModule::from_code(py, code, "example", "example")?;
/// Ok(())
/// })?;
/// # Ok(())
/// # }
/// ```
///
/// Load a file at runtime by using [`std::fs::read_to_string`][2] function. It is recommended
/// to use an absolute path to your Python files because then your binary can be run from
/// anywhere:
/// # Example: Load a file at runtime with [`std::fs::read_to_string`][2].
///
/// ```ignore
/// use std::fs;
/// ```rust
/// use pyo3::prelude::*;
///
/// # fn main() -> PyResult<()> {
/// let code = fs::read_to_string("/some/absolute/path/to/example.py")?;
/// Python::with_gil(|py| -> PyResult<()> {
/// PyModule::from_code(py, &code, "example", "example")?;
/// Ok(())
/// })?;
/// Ok(())
/// // This path is resolved by however the platform resolves paths,
/// // which also makes this less portable. Consider using `include_str`
/// // if you just want to bundle a script with your module.
/// let code = std::fs::read_to_string("assets/script.py")?;
///
/// Python::with_gil(|py| -> PyResult<()> {
/// PyModule::from_code(py, &code, "example", "example")?;
/// Ok(())
/// })?;
/// Ok(())
/// # }
/// ```
///
/// [1]: https://doc.rust-lang.org/std/macro.include_str.html
/// [2]: https://doc.rust-lang.org/std/fs/fn.read_to_string.html
/// [1]: std::include_str
/// [2]: std::fs::read_to_string
pub fn from_code<'p>(
py: Python<'p>,
code: &str,