diff --git a/Cargo.toml b/Cargo.toml index 95401ea39cc..7ce281fc67e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } diff --git a/assets/script.py b/assets/script.py new file mode 100644 index 00000000000..897bd3ec096 --- /dev/null +++ b/assets/script.py @@ -0,0 +1,5 @@ +# Used in PyModule examples. + + +class Blah: + pass diff --git a/guide/src/building_and_distribution.md b/guide/src/building_and_distribution.md index aacb8694485..1e9079b2526 100644 --- a/guide/src/building_and_distribution.md +++ b/guide/src/building_and_distribution.md @@ -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 ``` diff --git a/guide/src/building_and_distribution/multiple_python_versions.md b/guide/src/building_and_distribution/multiple_python_versions.md index e7bdd9868f8..fbdf8042a9f 100644 --- a/guide/src/building_and_distribution/multiple_python_versions.md +++ b/guide/src/building_and_distribution/multiple_python_versions.md @@ -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 diff --git a/guide/src/class.md b/guide/src/class.md index d5a0c12c739..4ce944911c4 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -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: diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index 1bfcc56f596..fbfd8e797c7 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -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 = 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 = list.extract()?; +# assert_eq!(&v, &[102, 111, 111]); +# Ok(()) +# }) +# } ``` This method is available for many Python object types, and can produce a wide diff --git a/guide/src/exception.md b/guide/src/exception.md index f205e0357c4..68b4846eee0 100644 --- a/guide/src/exception.md +++ b/guide/src/exception.md @@ -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 { # } ``` -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 diff --git a/guide/src/function.md b/guide/src/function.md index 1fa6c9cad34..41550c18414 100644 --- a/guide/src/function.md +++ b/guide/src/function.md @@ -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. diff --git a/guide/src/migration.md b/guide/src/migration.md index bd9f53e5581..b2b1b2cfca3 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -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::("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 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::empty(py).into(); @@ -722,7 +722,7 @@ If `T` implements `Clone`, you can extract `T` itself. In addition, you can also extract `&PyCell`, 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,10 @@ impl PySequenceProtocol for ByteSequence { ``` After: -```rust,ignore +```rust +# #[allow(deprecated)] +# #[cfg(feature = "pyproto")] +# { # use pyo3::prelude::*; # use pyo3::class::PySequenceProtocol; #[pyclass] @@ -790,6 +793,7 @@ impl PySequenceProtocol for ByteSequence { Ok(Self { elements }) } } +} ``` [`FromPyObject`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.FromPyObject.html diff --git a/guide/src/parallelism.md b/guide/src/parallelism.md index f35f1da5ced..2fd9e06680e 100644 --- a/guide/src/parallelism.md +++ b/guide/src/parallelism.md @@ -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 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/guide/src/python_from_rust.md b/guide/src/python_from_rust.md index 98d706df0ec..46857d497f2 100644 --- a/guide/src/python_from_rust.md +++ b/guide/src/python_from_rust.md @@ -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; diff --git a/src/callback.rs b/src/callback.rs index 7b487c48424..62861f9ce34 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -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 diff --git a/src/conversion.rs b/src/conversion.rs index 8a005a26b36..3f4b5452464 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -10,30 +10,88 @@ use crate::{ }; use std::ptr::NonNull; -/// This trait represents that **we can do zero-cost conversion from the object -/// to a FFI pointer**. +/// Returns a borrowed pointer to a Python object. /// -/// This trait is implemented for types that internally wrap a pointer to a Python object. +/// The returned pointer will be valid for as long as `self` is. It may be null depending on the +/// implementation. /// /// # Examples /// +/// ```rust +/// use pyo3::prelude::*; +/// use pyo3::AsPyPointer; +/// use pyo3::types::PyString; +/// use pyo3::ffi; +/// +/// Python::with_gil(|py| { +/// let s: Py = "foo".into_py(py); +/// let ptr = s.as_ptr(); +/// +/// let is_really_a_pystring = unsafe { ffi::PyUnicode_CheckExact(ptr) }; +/// assert_eq!(is_really_a_pystring, 1); +/// }); /// ``` -/// use pyo3::{prelude::*, AsPyPointer}; +/// +/// # Safety +/// +/// It is your responsibility to make sure that the underlying Python object is not dropped too +/// early. For example, the following code will cause undefined behavior: +/// +/// ```rust,no_run +/// # use pyo3::prelude::*; +/// # use pyo3::AsPyPointer; +/// # use pyo3::ffi; +/// # /// Python::with_gil(|py| { -/// let dict = pyo3::types::PyDict::new(py); -/// // All native object wrappers implement AsPyPointer!!! -/// assert_ne!(dict.as_ptr(), std::ptr::null_mut()); +/// let ptr: *mut ffi::PyObject = 0xabad1dea_u32.into_py(py).as_ptr(); +/// +/// let isnt_a_pystring = unsafe { +/// // `ptr` is dangling, this is UB +/// ffi::PyUnicode_CheckExact(ptr) +/// }; +/// # assert_eq!(isnt_a_pystring, 0); /// }); /// ``` +/// +/// This happens because the pointer returned by `as_ptr` does not carry any lifetime information +/// and the Python object is dropped immediately after the `0xabad1dea_u32.into_py(py).as_ptr()` +/// expression is evaluated. To fix the problem, bind Python object to a local variable like earlier +/// to keep the Python object alive until the end of its scope. pub trait AsPyPointer { - /// Retrieves the underlying FFI pointer (as a borrowed pointer). + /// Returns the underlying FFI pointer as a borrowed pointer. fn as_ptr(&self) -> *mut ffi::PyObject; } -/// This trait allows retrieving the underlying FFI pointer from Python objects. +/// Returns an owned pointer to a Python object. +/// +/// The returned pointer will be valid until you decrease its reference count. It may be null +/// depending on the implementation. +/// It is your responsibility to decrease the reference count of the pointer to avoid leaking memory. +/// +/// # Examples +/// +/// ```rust +/// use pyo3::prelude::*; +/// use pyo3::IntoPyPointer; +/// use pyo3::types::PyString; +/// use pyo3::ffi; +/// +/// Python::with_gil(|py| { +/// let s: Py = "foo".into_py(py); +/// let ptr = s.into_ptr(); +/// +/// let is_really_a_pystring = unsafe { ffi::PyUnicode_CheckExact(ptr) }; +/// assert_eq!(is_really_a_pystring, 1); +/// +/// // Because we took ownership of the pointer, +/// // we must manually decrement it to avoid leaking memory +/// unsafe { ffi::Py_DECREF(ptr) }; +/// }); +/// ``` pub trait IntoPyPointer { - /// Retrieves the underlying FFI pointer. Whether pointer owned or borrowed - /// depends on implementation. + /// Returns the underlying FFI pointer as an owned pointer. + /// + /// If `self` has ownership of the underlying pointer, it will "steal" ownership of it. fn into_ptr(self) -> *mut ffi::PyObject; } @@ -188,17 +246,32 @@ pub trait IntoPy: Sized { fn into_py(self, py: Python<'_>) -> T; } -/// `FromPyObject` is implemented by various types that can be extracted from -/// a Python object reference. +/// Extract a type from a Python object. /// -/// Normal usage is through the helper methods `Py::extract` or `PyAny::extract`: /// -/// ```rust,ignore -/// let obj: Py = ...; -/// let value: &TargetType = obj.extract(py)?; +/// Normal usage is through the `extract` methods on [`Py`] and [`PyAny`], which forward to this trait. /// -/// let any: &PyAny = ...; -/// let value: &TargetType = any.extract()?; +/// # Examples +/// +/// ```rust +/// use pyo3::prelude::*; +/// use pyo3::types::PyString; +/// +/// # fn main() -> PyResult<()> { +/// Python::with_gil(|py| { +/// let obj: Py = PyString::new(py, "blah").into(); +/// +/// // Straight from an owned reference +/// let s: &str = obj.extract(py)?; +/// # assert_eq!(s, "blah"); +/// +/// // Or from a borrowed reference +/// let obj: &PyString = obj.as_ref(py); +/// let s: &str = obj.extract()?; +/// # assert_eq!(s, "blah"); +/// # Ok(()) +/// }) +/// # } /// ``` /// /// Note: depending on the implementation, the lifetime of the extracted result may diff --git a/src/err/mod.rs b/src/err/mod.rs index db1b2e40509..eda65b1dc9b 100644 --- a/src/err/mod.rs +++ b/src/err/mod.rs @@ -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 + /// + /// ``` + /// use pyo3::prelude::*; + /// use pyo3::exceptions::PyTypeError; /// - /// ```ignore - /// return Err(PyErr::new::("Error message")); + /// #[pyfunction] + /// fn always_throws() -> PyResult<()> { + /// Err(PyErr::new::("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::(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::(py)) + /// # }); /// ``` #[inline] pub fn new(args: A) -> PyErr diff --git a/src/gil.rs b/src/gil.rs index 1aa84d3c224..d5258ed60fc 100644 --- a/src/gil.rs +++ b/src/gil.rs @@ -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: F) -> R diff --git a/src/lib.rs b/src/lib.rs index effa4f4787d..654a14dc4d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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. diff --git a/src/pycell.rs b/src/pycell.rs index b8eb967986b..17fee01dc71 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -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 = py.from_borrowed_ptr(slf); -//! let mut _ref: PyRefMut = 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>()?; +//! let mut _ref = _cell.try_borrow_mut()?; +//! let _slf: &mut Number = &mut *_ref; +//! _pyo3::callback::convert(_py, Number::increment(_slf)) +//! }), +//! ) //! } //! ``` //! diff --git a/src/types/module.rs b/src/types/module.rs index ae5fcd307ff..0b43a16812f 100644 --- a/src/types/module.rs +++ b/src/types/module.rs @@ -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 - /// - /// Include a file at compile time by using [`std::include_str` macro][1]: + /// # Example: bundle in a file at compile time with [`include_str!`][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,