Merge pull request #2156 from mejrs/object

Add examples for object and numeric protocols
This commit is contained in:
Bruno Kolenbrander 2022-02-23 11:32:24 +01:00 committed by GitHub
commit f379854c50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 763 additions and 72 deletions

View File

@ -8,6 +8,8 @@
- [Python Functions](function.md)
- [Python Classes](class.md)
- [Class customizations](class/protocols.md)
- [Basic object customization](class/object.md)
- [Emulating numeric types](class/numeric.md)
- [Emulating callable objects](class/call.md)
- [Type Conversions](conversions.md)
- [Mapping of Rust types to Python types](conversions/tables.md)]

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
#[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
#[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
#[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
#[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
#[cfg(PyPy)]
```

View File

@ -23,11 +23,24 @@ This chapter will discuss the functionality and configuration these attributes o
To define a custom Python class, add the `#[pyclass]` attribute to a Rust struct or a fieldless enum.
```rust
# #![allow(dead_code)]
# use pyo3::prelude::*;
use pyo3::prelude::*;
#[pyclass]
struct MyClass {
# #[pyo3(get)]
num: i32,
struct Integer{
inner: i32
}
// A "tuple" struct
#[pyclass]
struct Number(i32);
// PyO3 supports custom discriminants in enums
#[pyclass]
enum HttpResponse {
Ok = 200,
NotFound = 404,
Teapot = 418,
// ...
}
#[pyclass]
@ -41,20 +54,59 @@ Because Python objects are freely shared between threads by the Python interpret
The above example generates implementations for [`PyTypeInfo`], [`PyTypeObject`], and [`PyClass`] for `MyClass` and `MyEnum`. To see these generated implementations, refer to the [implementation details](#implementation-details) at the end of this chapter.
## Adding the class to a module
## Constructor
Custom Python classes can then be added to a module using `add_class()`.
By default it is not possible to create an instance of a custom class from Python code.
To declare a constructor, you need to define a method and annotate it with the `#[new]`
attribute. Only Python's `__new__` method can be specified, `__init__` is not available.
```rust
# use pyo3::prelude::*;
# #[pyclass]
# struct MyClass {
# #[allow(dead_code)]
# num: i32,
# }
# struct Number(i32);
#
#[pymethods]
impl Number {
#[new]
fn new(value: i32) -> Self {
Number(value)
}
}
```
Alternatively, if your `new` method may fail you can return `PyResult<Self>`.
```rust
# use pyo3::prelude::*;
# #[pyclass]
# struct Number(i32);
#
#[pymethods]
impl Number {
#[new]
fn new(value: i32) -> Self {
Number(value)
}
}
```
If no method marked with `#[new]` is declared, object instances can only be
created from Rust, but not from Python.
For arguments, see the `Method arguments` section below.
## Adding the class to a module
The next step is to create the module initializer and add our class to it
```rust
# use pyo3::prelude::*;
# #[pyclass]
# struct Number(i32);
#
#[pymodule]
fn mymodule(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<MyClass>()?;
fn my_module(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Number>()?;
Ok(())
}
```
@ -152,52 +204,6 @@ If a custom class contains references to other Python objects that can be collec
* `module="XXX"` - Set the name of the module the class will be shown as defined in. If not given, the class
will be a virtual member of the `builtins` module.
## Constructor
By default it is not possible to create an instance of a custom class from Python code.
To declare a constructor, you need to define a method and annotate it with the `#[new]`
attribute. Only Python's `__new__` method can be specified, `__init__` is not available.
```rust
# use pyo3::prelude::*;
#[pyclass]
struct MyClass {
# #[allow(dead_code)]
num: i32,
}
#[pymethods]
impl MyClass {
#[new]
fn new(num: i32) -> Self {
MyClass { num }
}
}
```
Alternatively, if your `new` method may fail you can return `PyResult<Self>`.
```rust
# use pyo3::prelude::*;
#[pyclass]
struct MyClass {
# #[allow(dead_code)]
num: i32,
}
#[pymethods]
impl MyClass {
#[new]
fn new(num: i32) -> PyResult<Self> {
Ok(MyClass { num })
}
}
```
If no method marked with `#[new]` is declared, object instances can only be
created from Rust, but not from Python.
For arguments, see the `Method arguments` section below.
### Return type
Generally, `#[new]` method have to return `T: Into<PyClassInitializer<Self>>` or

449
guide/src/class/numeric.md Normal file
View File

@ -0,0 +1,449 @@
# Emulating numeric types
At this point we have a `Number` class that we can't actually do any math on!
Before proceeding, we should think about how we want to handle overflows. There are three obvious solutions:
- We can have infinite precision just like Python's `int`. However that would be quite boring - we'd
be reinventing the wheel.
- We can raise exceptions whenever `Number` overflows, but that makes the API painful to use.
- We can wrap around the boundary of `i32`. This is the approach we'll take here. To do that we'll just forward to `i32`'s
`wrapping_*` methods.
### Fixing our constructor
Let's address the first overflow, in `Number`'s constructor:
```python
from my_module import Number
n = Number(1 << 1337)
```
```text
Traceback (most recent call last):
File "example.py", line 3, in <module>
n = Number(1 << 1337)
OverflowError: Python int too large to convert to C long
```
Instead of relying on the default [`FromPyObject`] extraction to parse arguments, we can specify our
own extraction function, using the `#[pyo3(from_py_with = "...")]` attribute. Unfortunately PyO3
doesn't provide a way to wrap Python integers out of the box, but we can do a Python call to mask it
and cast it to an `i32`.
```rust
# #![allow(dead_code)]
use pyo3::prelude::*;
fn wrap(obj: &PyAny) -> Result<i32, PyErr> {
let val = obj.call_method1("__and__", (0xFFFFFFFF_u32,))?;
let val: u32 = val.extract()?;
// 👇 This intentionally overflows!
Ok(val as i32)
}
```
We also add documentation, via `///` comments and the `#[pyo3(text_signature = "...")]` attribute, both of which are visible to Python users.
```rust
# #![allow(dead_code)]
use pyo3::prelude::*;
fn wrap(obj: &PyAny) -> Result<i32, PyErr> {
let val = obj.call_method1("__and__", (0xFFFFFFFF_u32,))?;
let val: u32 = val.extract()?;
Ok(val as i32)
}
/// Did you ever hear the tragedy of Darth Signed The Overfloweth? I thought not.
/// It's not a story C would tell you. It's a Rust legend.
#[pyclass(module = "my_module")]
#[pyo3(text_signature = "(int)")]
struct Number(i32);
#[pymethods]
impl Number {
#[new]
fn new(#[pyo3(from_py_with = "wrap")] value: i32) -> Self {
Self(value)
}
}
```
With that out of the way, let's implement some operators:
```rust
use std::convert::TryInto;
use pyo3::exceptions::{PyZeroDivisionError, PyValueError};
# use pyo3::prelude::*;
#
# #[pyclass]
# struct Number(i32);
#
#[pymethods]
impl Number {
fn __add__(&self, other: &Self) -> Self {
Self(self.0.wrapping_add(other.0))
}
fn __sub__(&self, other: &Self) -> Self {
Self(self.0.wrapping_sub(other.0))
}
fn __mul__(&self, other: &Self) -> Self {
Self(self.0.wrapping_mul(other.0))
}
fn __truediv__(&self, other: &Self) -> PyResult<Self> {
match self.0.checked_div(other.0) {
Some(i) => Ok(Self(i)),
None => Err(PyZeroDivisionError::new_err("division by zero")),
}
}
fn __floordiv__(&self, other: &Self) -> PyResult<Self> {
match self.0.checked_div(other.0) {
Some(i) => Ok(Self(i)),
None => Err(PyZeroDivisionError::new_err("division by zero")),
}
}
fn __rshift__(&self, other: &Self) -> PyResult<Self> {
match other.0.try_into() {
Ok(rhs) => Ok(Self(self.0.wrapping_shr(rhs))),
Err(_) => Err(PyValueError::new_err("negative shift count")),
}
}
fn __lshift__(&self, other: &Self) -> PyResult<Self> {
match other.0.try_into() {
Ok(rhs) => Ok(Self(self.0.wrapping_shl(rhs))),
Err(_) => Err(PyValueError::new_err("negative shift count")),
}
}
}
```
### Unary arithmethic operations
```rust
# use pyo3::prelude::*;
#
# #[pyclass]
# struct Number(i32);
#
#[pymethods]
impl Number {
fn __pos__(slf: PyRef<Self>) -> PyRef<Self> {
slf
}
fn __neg__(&self) -> Self {
Self(-self.0)
}
fn __abs__(&self) -> Self {
Self(self.0.abs())
}
fn __invert__(&self) -> Self {
Self(!self.0)
}
}
```
### Support for the `complex()`, `int()` and `float()` built-in functions.
```rust
# use pyo3::prelude::*;
#
# #[pyclass]
# struct Number(i32);
#
use pyo3::types::PyComplex;
#[pymethods]
impl Number {
fn __int__(&self) -> i32 {
self.0
}
fn __float__(&self) -> f64 {
self.0 as f64
}
fn __complex__<'py>(&self, py: Python<'py>) -> &'py PyComplex {
PyComplex::from_doubles(py, self.0 as f64, 0.0)
}
}
```
We do not implement the in-place operations like `__iadd__` because we do not wish to mutate `Number`.
Similarly we're not interested in supporting operations with different types, so we do not implement
the reflected operations like `__radd__` either.
Now Python can use our `Number` class:
```python
from my_module import Number
def hash_djb2(s: str):
'''
A version of Daniel J. Bernstein's djb2 string hashing algorithm
Like many hashing algorithms, it relies on integer wrapping.
'''
n = Number(0)
five = Number(5)
for x in s:
n = Number(ord(x)) + ((n << five) - n)
return n
assert hash_djb2('l50_50') == Number(-1152549421)
```
### Final code
```rust
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::convert::TryInto;
use pyo3::exceptions::{PyValueError, PyZeroDivisionError};
use pyo3::prelude::*;
use pyo3::class::basic::CompareOp;
use pyo3::types::PyComplex;
fn wrap(obj: &PyAny) -> Result<i32, PyErr> {
let val = obj.call_method1("__and__", (0xFFFFFFFF_u32,))?;
let val: u32 = val.extract()?;
Ok(val as i32)
}
/// Did you ever hear the tragedy of Darth Signed The Overfloweth? I thought not.
/// It's not a story C would tell you. It's a Rust legend.
#[pyclass(module = "my_module")]
#[pyo3(text_signature = "(int)")]
struct Number(i32);
#[pymethods]
impl Number {
#[new]
fn new(#[pyo3(from_py_with = "wrap")] value: i32) -> Self {
Self(value)
}
fn __repr__(&self) -> String {
format!("Number({})", self.0)
}
fn __str__(&self) -> String {
self.0.to_string()
}
fn __hash__(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.0.hash(&mut hasher);
hasher.finish()
}
fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> {
match op {
CompareOp::Lt => Ok(self.0 < other.0),
CompareOp::Le => Ok(self.0 <= other.0),
CompareOp::Eq => Ok(self.0 == other.0),
CompareOp::Ne => Ok(self.0 != other.0),
CompareOp::Gt => Ok(self.0 > other.0),
CompareOp::Ge => Ok(self.0 >= other.0),
}
}
fn __bool__(&self) -> bool {
self.0 != 0
}
fn __add__(&self, other: &Self) -> Self {
Self(self.0.wrapping_add(other.0))
}
fn __sub__(&self, other: &Self) -> Self {
Self(self.0.wrapping_sub(other.0))
}
fn __mul__(&self, other: &Self) -> Self {
Self(self.0.wrapping_mul(other.0))
}
fn __truediv__(&self, other: &Self) -> PyResult<Self> {
match self.0.checked_div(other.0) {
Some(i) => Ok(Self(i)),
None => Err(PyZeroDivisionError::new_err("division by zero")),
}
}
fn __floordiv__(&self, other: &Self) -> PyResult<Self> {
match self.0.checked_div(other.0) {
Some(i) => Ok(Self(i)),
None => Err(PyZeroDivisionError::new_err("division by zero")),
}
}
fn __rshift__(&self, other: &Self) -> PyResult<Self> {
match other.0.try_into() {
Ok(rhs) => Ok(Self(self.0.wrapping_shr(rhs))),
Err(_) => Err(PyValueError::new_err("negative shift count")),
}
}
fn __lshift__(&self, other: &Self) -> PyResult<Self> {
match other.0.try_into() {
Ok(rhs) => Ok(Self(self.0.wrapping_shl(rhs))),
Err(_) => Err(PyValueError::new_err("negative shift count")),
}
}
fn __xor__(&self, other: &Self) -> Self {
Self(self.0 ^ other.0)
}
fn __or__(&self, other: &Self) -> Self {
Self(self.0 | other.0)
}
fn __and__(&self, other: &Self) -> Self {
Self(self.0 & other.0)
}
fn __int__(&self) -> i32 {
self.0
}
fn __float__(&self) -> f64 {
self.0 as f64
}
fn __complex__<'py>(&self, py: Python<'py>) -> &'py PyComplex {
PyComplex::from_doubles(py, self.0 as f64, 0.0)
}
}
#[pymodule]
fn my_module(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Number>()?;
Ok(())
}
# const SCRIPT: &'static str = r#"
# def hash_djb2(s: str):
# n = Number(0)
# five = Number(5)
#
# for x in s:
# n = Number(ord(x)) + ((n << five) - n)
# return n
#
# assert hash_djb2('l50_50') == Number(-1152549421)
# assert hash_djb2('logo') == Number(3327403)
# assert hash_djb2('horizon') == Number(1097468315)
#
#
# assert Number(2) + Number(2) == Number(4)
# assert Number(2) + Number(2) != Number(5)
#
# assert Number(13) - Number(7) == Number(6)
# assert Number(13) - Number(-7) == Number(20)
#
# assert Number(13) / Number(7) == Number(1)
# assert Number(13) // Number(7) == Number(1)
#
# assert Number(13) * Number(7) == Number(13*7)
#
# assert Number(13) > Number(7)
# assert Number(13) < Number(20)
# assert Number(13) == Number(13)
# assert Number(13) >= Number(7)
# assert Number(13) <= Number(20)
# assert Number(13) == Number(13)
#
#
# assert (True if Number(1) else False)
# assert (False if Number(0) else True)
#
#
# assert int(Number(13)) == 13
# assert float(Number(13)) == 13
# assert Number.__doc__ == "Did you ever hear the tragedy of Darth Signed The Overfloweth? I thought not.\nIt's not a story C would tell you. It's a Rust legend."
# assert Number(12345234523452) == Number(1498514748)
# try:
# import inspect
# assert inspect.signature(Number).__str__() == '(int)'
# except ValueError:
# # Not supported with `abi3` before Python 3.10
# pass
# assert Number(1337).__str__() == '1337'
# assert Number(1337).__repr__() == 'Number(1337)'
"#;
#
# use pyo3::type_object::PyTypeObject;
#
# fn main() -> PyResult<()> {
# Python::with_gil(|py| -> PyResult<()> {
# let globals = PyModule::import(py, "__main__")?.dict();
# globals.set_item("Number", Number::type_object(py))?;
#
# py.run(SCRIPT, Some(globals), None)?;
# Ok(())
# })
# }
```
## Appendix: Writing some unsafe code
At the beginning of this chapter we said that PyO3 doesn't provide a way to wrap Python integers out
of the box but that's a half truth. There's not a PyO3 API for it, but there's a Python C API
function that does:
```c
unsigned long PyLong_AsUnsignedLongMask(PyObject *obj)
```
We can call this function from Rust by using [`pyo3::ffi::PyLong_AsUnsignedLongMask`]. This is an *unsafe*
function, which means we have to use an unsafe block to call it and take responsibility for upholding
the contracts of this function. Let's review those contracts:
- The GIL must be held. If it's not, calling this function causes a data race.
- The pointer must be valid, i.e. it must be properly aligned and point to a valid Python object.
Let's create that helper function. The signature has to be `fn(&PyAny) -> PyResult<T>`.
- `&PyAny` represents a checked borrowed reference, so the pointer derived from it is valid (and not null).
- Whenever we have borrowed references to Python objects in scope, it is guaranteed that the GIL is held. This reference is also where we can get a [`Python`] token to use in our call to [`PyErr::take`].
```rust
# #![allow(dead_code)]
use std::os::raw::c_ulong;
use pyo3::prelude::*;
use pyo3::ffi;
use pyo3::conversion::AsPyPointer;
fn wrap(obj: &PyAny) -> Result<i32, PyErr> {
let py: Python = obj.py();
unsafe {
let ptr = obj.as_ptr();
let ret: c_ulong = ffi::PyLong_AsUnsignedLongMask(ptr);
if ret == c_ulong::MAX {
if let Some(err) = PyErr::take(py) {
return Err(err);
}
}
Ok(ret as i32)
}
}
```
[`PyErr::take`]: https://docs.rs/pyo3/latest/pyo3/prelude/struct.PyErr.html#method.take
[`Python`]: https://docs.rs/pyo3/latest/pyo3/struct.Python.html
[`FromPyObject`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.FromPyObject.html
[`pyo3::ffi::PyLong_AsUnsignedLongMask`]: https://docs.rs/pyo3/latest/pyo3/ffi/fn.PyLong_AsUnsignedLongMask.html

232
guide/src/class/object.md Normal file
View File

@ -0,0 +1,232 @@
# Basic object customization
Recall the `Number` class from the previous chapter:
```rust
use pyo3::prelude::*;
#[pyclass]
struct Number(i32);
#[pymethods]
impl Number {
#[new]
fn new(value: i32) -> Self {
Self(value)
}
}
#[pymodule]
fn my_module(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Number>()?;
Ok(())
}
```
At this point Python code can import the module, access the class and create class instances - but
nothing else.
```python
from my_module import Number
n = Number(5)
print(n)
```
```text
<builtins.Number object at 0x000002B4D185D7D0>
```
### String representations
It can't even print an user-readable representation of itself! We can fix that by defining the
`__repr__` and `__str__` methods inside a `#[pymethods]` block. We do this by accessing the value
contained inside `Number`.
```rust
# use pyo3::prelude::*;
#
# #[pyclass]
# struct Number(i32);
#
#[pymethods]
impl Number {
// For `__repr__` we want to return a string that Python code could use to recreate
// the `Number`, like `Number(5)` for example.
fn __repr__(&self) -> String {
// We use the `format!` macro to create a string. Its first argument is a
// format string, followed by any number of parameters which replace the
// `{}`'s in the format string.
//
// 👇 Tuple field access in Rust uses a dot
format!("Number({})", self.0)
}
// `__str__` is generally used to create an "informal" representation, so we
// just forward to `i32`'s `ToString` trait implementation to print a bare number.
fn __str__(&self) -> String {
self.0.to_string()
}
}
```
### Hashing
Let's also implement hashing. We'll just hash the `i32`. For that we need a [`Hasher`]. The one
provided by `std` is [`DefaultHasher`], which uses the [SipHash] algorithm.
```rust
use std::collections::hash_map::DefaultHasher;
// Required to call the `.hash` and `.finish` methods, which are defined on traits.
use std::hash::{Hash, Hasher};
# use pyo3::prelude::*;
#
# #[pyclass]
# struct Number(i32);
#
#[pymethods]
impl Number {
fn __hash__(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.0.hash(&mut hasher);
hasher.finish()
}
}
```
> **Note**: When implementing `__hash__` and comparisons, it is important that the following property holds:
>
> ```text
> k1 == k2 -> hash(k1) == hash(k2)
> ```
>
> In other words, if two keys are equal, their hashes must also be equal. In addition you must take
> care that your classes' hash doesn't change during its lifetime. In this tutorial we do that by not
> letting Python code change our `Number` class. In other words, it is immutable.
>
> By default, all `#[pyclass]` types have a default hash implementation from Python.
> Types which should not be hashable can override this by setting `__hash__` to None.
> This is the same mechanism as for a pure-Python class. This is done like so:
>
> ```rust
> # use pyo3::prelude::*;
> #[pyclass]
> struct NotHashable { }
>
> #[pymethods]
> impl NotHashable {
> #[classattr]
> const __hash__: Option<Py<PyAny>> = None;
>}
> ```
### Comparisons
Unlike in Python, PyO3 does not provide the magic comparison methods you might expect like `__eq__`,
`__lt__` and so on. Instead you have to implement all six operations at once with `__richcmp__`.
This method will be called with a value of `CompareOp` depending on the operation.
```rust
use pyo3::class::basic::CompareOp;
# use pyo3::prelude::*;
#
# #[pyclass]
# struct Number(i32);
#
#[pymethods]
impl Number {
fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> {
match op {
CompareOp::Lt => Ok(self.0 < other.0),
CompareOp::Le => Ok(self.0 <= other.0),
CompareOp::Eq => Ok(self.0 == other.0),
CompareOp::Ne => Ok(self.0 != other.0),
CompareOp::Gt => Ok(self.0 > other.0),
CompareOp::Ge => Ok(self.0 >= other.0),
}
}
}
```
### Truthyness
We'll consider `Number` to be `True` if it is nonzero:
```rust
# use pyo3::prelude::*;
#
# #[pyclass]
# struct Number(i32);
#
#[pymethods]
impl Number {
fn __bool__(&self) -> bool {
self.0 != 0
}
}
```
### Final code
```rust
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use pyo3::prelude::*;
use pyo3::class::basic::CompareOp;
#[pyclass]
struct Number(i32);
#[pymethods]
impl Number {
#[new]
fn new(value: i32) -> Self {
Self(value)
}
fn __repr__(&self) -> String {
format!("Number({})", self.0)
}
fn __str__(&self) -> String {
self.0.to_string()
}
fn __hash__(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.0.hash(&mut hasher);
hasher.finish()
}
fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> {
match op {
CompareOp::Lt => Ok(self.0 < other.0),
CompareOp::Le => Ok(self.0 <= other.0),
CompareOp::Eq => Ok(self.0 == other.0),
CompareOp::Ne => Ok(self.0 != other.0),
CompareOp::Gt => Ok(self.0 > other.0),
CompareOp::Ge => Ok(self.0 >= other.0),
}
}
fn __bool__(&self) -> bool {
self.0 != 0
}
}
#[pymodule]
fn my_module(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Number>()?;
Ok(())
}
```
[`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html
[`Hasher`]: https://doc.rust-lang.org/std/hash/trait.Hasher.html
[`DefaultHasher`]: https://doc.rust-lang.org/std/collections/hash_map/struct.DefaultHasher.html
[SipHash]: https://en.wikipedia.org/wiki/SipHash

View File

@ -35,7 +35,7 @@ structs is not supported.
The derivation generates code that will attempt to access the attribute `my_string` on
the Python object, i.e. `obj.getattr("my_string")`, and call `extract()` on the attribute.
```
```rust
use pyo3::prelude::*;
#[derive(FromPyObject)]
@ -65,7 +65,7 @@ struct RustyStruct {
By setting the `#[pyo3(item)]` attribute on the field, PyO3 will attempt to extract the value by calling the `get_item` method on the Python object.
```
```rust
use pyo3::prelude::*;
@ -90,7 +90,7 @@ struct RustyStruct {
The argument passed to `getattr` and `get_item` can also be configured:
```
```rust
use pyo3::prelude::*;
#[derive(FromPyObject)]
@ -135,7 +135,7 @@ Tuple structs are also supported but do not allow customizing the extraction. Th
always assumed to be a Python tuple with the same length as the Rust type, the `n`th field
is extracted from the `n`th item in the Python tuple.
```
```rust
use pyo3::prelude::*;
#[derive(FromPyObject)]
@ -158,7 +158,7 @@ struct RustyTuple(String, String);
Tuple structs with a single field are treated as wrapper types which are described in the
following section. To override this behaviour and ensure that the input is in fact a tuple,
specify the struct as
```
```rust
use pyo3::prelude::*;
#[derive(FromPyObject)]
@ -184,7 +184,7 @@ in extracting directly from the input object, i.e. `obj.extract()`, rather than
an item or attribute. This behaviour is enabled per default for newtype structs and tuple-variants
with a single field.
```
```rust
use pyo3::prelude::*;
#[derive(FromPyObject)]
@ -223,7 +223,7 @@ i.e. a tuple variant assumes that the input is a Python tuple, and a struct vari
extracting fields as attributes but can be configured in the same manner. The `transparent`
attribute can be applied to single-field-variants.
```
```rust
use pyo3::prelude::*;
#[derive(FromPyObject)]
@ -370,7 +370,7 @@ tested variants is returned. The names reported in the error message can be cust
through the `#[pyo3(annotation = "name")]` attribute, e.g. to use conventional Python type
names:
```
```rust
use pyo3::prelude::*;
#[derive(FromPyObject)]
@ -454,7 +454,7 @@ All types in PyO3 implement this trait, as does a `#[pyclass]` which doesn't use
Occasionally you may choose to implement this for custom types which are mapped to Python types
_without_ having a unique python type.
```
```rust
use pyo3::prelude::*;
struct MyPyObjectWrapper(PyObject);

View File

@ -447,6 +447,8 @@ pub mod doc_test {
"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.