Skip to content

Commit

Permalink
Merge pull request #2350 from mejrs/ignore-less
Browse files Browse the repository at this point in the history
Expand on AsPyPointer docs and un-ignore doc examples
  • Loading branch information
davidhewitt committed May 6, 2022
2 parents 21bbe3f + 5bdf698 commit bc8641c
Show file tree
Hide file tree
Showing 18 changed files with 300 additions and 114 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -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"] }
Expand Down
5 changes: 5 additions & 0 deletions assets/script.py
@@ -0,0 +1,5 @@
# Used in PyModule examples.


class Blah:
pass
4 changes: 2 additions & 2 deletions guide/src/building_and_distribution.md
Expand Up @@ -213,13 +213,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
```

Expand Down
24 changes: 15 additions & 9 deletions guide/src/building_and_distribution/multiple_python_versions.md
Expand Up @@ -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)]
```

Expand All @@ -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
Expand Down
7 changes: 0 additions & 7 deletions guide/src/class.md
Expand Up @@ -604,13 +604,6 @@ 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'")
```

If the class attribute is defined with `const` code only, one can also annotate associated
constants:

Expand Down
13 changes: 11 additions & 2 deletions guide/src/conversions/traits.md
Expand Up @@ -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
Expand Down
16 changes: 7 additions & 9 deletions guide/src/exception.md
Expand Up @@ -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<()> {
Expand Down Expand Up @@ -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};
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion guide/src/function.md
Expand Up @@ -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.
Expand Down
28 changes: 16 additions & 12 deletions guide/src/migration.md
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -110,7 +110,7 @@ impl PyIterProtocol for MyClass {

After

```rust,ignore
```rust,compile_fail
use pyo3::prelude::*;
use pyo3::types::PyString;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
```

Expand All @@ -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<()> {
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -775,7 +775,10 @@ impl PySequenceProtocol for ByteSequence {
```

After:
```rust,ignore
```rust
# #[allow(deprecated)]
# #[cfg(feature = "pyproto")]
# {
# use pyo3::prelude::*;
# use pyo3::class::PySequenceProtocol;
#[pyclass]
Expand All @@ -790,6 +793,7 @@ impl PySequenceProtocol for ByteSequence {
Ok(Self { elements })
}
}
}
```

[`FromPyObject`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.FromPyObject.html
Expand Down
53 changes: 49 additions & 4 deletions guide/src/parallelism.md
Expand Up @@ -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
Expand All @@ -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))
Expand Down Expand Up @@ -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
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion guide/src/python_from_rust.md
Expand Up @@ -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;
Expand Down

0 comments on commit bc8641c

Please sign in to comment.