Macro cpython::py_class[][src]

macro_rules! py_class {
    (class $class:ident |$py: ident| { $( $body:tt )* }) => { ... };
    ($visibility:vis class $class:ident |$py: ident| { $( $body:tt )* }) => { ... };
}

Defines new python extension class. A py_class! macro invocation generates code that declares a new Python class. Additionally, it generates a Rust struct of the same name, which allows accessing instances of that Python class from Rust.

Syntax

py_class!(pub class MyType |py| { ... })

Example

use cpython::{Python, PyResult, PyDict, py_class};

py_class!(class MyType |py| {
    data number: i32;
    def __new__(_cls, arg: i32) -> PyResult<MyType> {
        MyType::create_instance(py, arg)
    }
    def half(&self) -> PyResult<i32> {
        println!("half() was called with self={:?}", self.number(py));
        Ok(self.number(py) / 2)
    }
});

fn main() {
    let gil = Python::acquire_gil();
    let py = gil.python();
    let dict = PyDict::new(py);
    dict.set_item(py, "MyType", py.get_type::<MyType>()).unwrap();
    py.run("assert MyType(42).half() == 21", None, Some(&dict)).unwrap();
}

Generated Rust type

The above example generates the following Rust type:

struct MyType { ... }

impl ToPythonObject for MyType { ... }
impl PythonObject for MyType { ... }
impl PythonObjectWithCheckedDowncast for MyType { ... }
impl PythonObjectWithTypeObject for MyType { ... }
impl PythonObjectFromPyClassMacro for MyType { ... }

impl MyType {
    fn create_instance(py: Python, number: i32) -> PyResult<MyType> { ... }

    // data accessors
    fn number<'a>(&'a self, py: Python<'a>) -> &'a i32 { ... }

    // functions callable from python
    pub fn __new__(_cls: &PyType, py: Python, arg: i32) -> PyResult<MyType> {
        MyType::create_instance(py, arg)
    }

    pub fn half(&self, py: Python) -> PyResult<i32> {
        println!("half() was called with self={:?}", self.number(py));
        Ok(self.number(py) / 2)
    }
}

py_class body

The body of a py_class! supports the following definitions:

Data declarations

data data_name: data_type;

Declares a data field within the Python class. Used to store Rust data directly in the Python object instance.

Because Python code can pass all Python objects to other threads, data_type must be Send + 'static.

Because Python object instances can be freely shared (Python has no concept of "ownership"), data fields cannot be declared as mut. If mutability is required, you have to use interior mutability (Cell or RefCell).

If data members are used to store references to other Python objects, make sure to read the section "Garbage Collector Integration".

Data declarations are not accessible from Python. On the Rust side, data is accessed through the automatically generated accessor functions:

impl MyType {
    fn data_name<'a>(&'a self, py: Python<'a>) -> &'a data_type { ... }
}

Shared data declarations

@shared data data_name: data_type;

Declares a "shareable" data field within the Python class. A data field of this type can be borrowed immutably by another Python object such as a Python iterator. See PySharedRefCell documentation for details.

On the Rust side, data is accessed through the automatically generated accessor functions:

impl MyType {
    fn data_name<'a>(&'a self, py: Python<'a>) -> PySharedRef<'a, $data_type> { ... }
}

Instance methods

def method_name(&self, parameter-list) -> PyResult<...> { ... } pub(crate) def method_name(&self, parameter-list) -> PyResult<...> { ... }

Declares an instance method callable from Python.

Class methods

@classmethod def method_name(cls, parameter-list) -> PyResult<...> { ... } @classmethod pub(crate) def method_name(cls, parameter-list) -> PyResult<...> { ... }

Declares a class method callable from Python.

Static methods

@staticmethod def method_name(parameter-list) -> PyResult<...> { ... } @staticmethod pub(crate) def method_name(parameter-list) -> PyResult<...> { ... }

Declares a static method callable from Python.

Properties

@property def property_name(&self) -> PyResult<...> { ... }

@property_name.setter def set_method_name(&self, value: Option<impl FromPyObject>) -> PyResult<()> { ... }

Declares a Python data attribute backed by Rust methods to get its value and, optionally, to set or delete it.

Setter details

new

def __new__(cls, parameter-list) -> PyResult<...> { ... }

Declares a constructor method callable from Python.

Garbage Collector Integration

If your type owns references to other python objects, you will need to integrate with Python's garbage collector so that the GC is aware of those references. To do this, implement the special member functions __traverse__ and __clear__. These correspond to the slots tp_traverse and tp_clear in the Python C API.

__traverse__ must call visit.call() for each reference to another python object.

__clear__ must clear out any mutable references to other python objects (thus breaking reference cycles). Immutable references do not have to be cleared, as every cycle must contain at least one mutable reference.

Example:

use std::{mem, cell};
use cpython::{PyObject, PyDrop, py_class};

py_class!(class ClassWithGCSupport |py| {
    data obj: cell::RefCell<Option<PyObject>>;

    def __traverse__(&self, visit) {
        if let Some(ref obj) = *self.obj(py).borrow() {
            visit.call(obj)?
        }
        Ok(())
    }

    def __clear__(&self) {
        let old_obj = mem::replace(&mut *self.obj(py).borrow_mut(), None);
        // Release reference only after the mutable borrow has expired,
        // see Caution note below.
        old_obj.release_ref(py);
    }
});

Caution: __traverse__ may be called by the garbage collector:

If you are using RefCell<PyObject>, you must not perform any of the above operations while your code holds a mutable borrow, or you may cause the borrow in __traverse__ to panic.

This is why the example above uses the mem::replace/release_ref dance: release_ref (or the implicit Drop) can only be called safely in a separate statement, after the mutable borrow on the RefCell has expired.

Note that this restriction applies not only to __clear__, but to all methods that use RefCell::borrow_mut.

Iterator Types

Iterators can be defined using the Python special methods __iter__ and __next__:

Example:

use std::cell::RefCell;
use cpython::{PyObject, PyClone, PyResult, py_class};

py_class!(class MyIterator |py| {
    data iter: RefCell<Box<Iterator<Item=PyObject> + Send>>;

    def __iter__(&self) -> PyResult<Self> {
        Ok(self.clone_ref(py))
    }

    def __next__(&self) -> PyResult<Option<PyObject>> {
        Ok(self.iter(py).borrow_mut().next())
    }
});

String Conversions

Comparison operators

Emulating Container Types

Arithmetic methods

Context Manager

Other Special Methods

Errors