PyO3 began as fork of [rust-cpython](https://github.com/dgrunwald/rust-cpython) when rust-cpython wasn't maintained. Over time PyO3 has become fundamentally different from rust-cpython.
While rust-cpython has a `macro_rules!` based dsl for declaring modules and classes, PyO3 uses proc macros. PyO3 also doesn't change your struct and functions so you can still use them as normal Rust functions.
rust-cpython requires a `Python` parameter for constructing a `PyErr`, so error handling ergonomics is pretty bad. It is not possible to use `?` with Rust errors.
PyO3 on other hand does not require `Python` for constructing a `PyErr`, it is only required if you want to raise an exception in Python with the `PyErr::restore()` method. Due to various `std::convert::From<E> for PyErr` implementations for Rust standard error types `E`, propagating `?` is supported automatically.