You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Ashwin Naren edited this page Feb 21, 2025
·
11 revisions
RustPython has special attributes to support easy python object building.
pyfunction, pymethod
These attributes are very common for every code chunk. They eventually turn into builtin_function_or_method as Fn(&VirtualMachine, FuncArgs) -> PyResult.
The vm paramter is just suffix. We add it as the last parameter unless we don't use vm at all - very rare case. It takes an object obj as PyObjectRef, which is a general python object. It returns PyResult<String>, which will turn into PyResult<PyObjectRef> the same representation of PyResult.
Every return value must be convertible to PyResult. This is defined as IntoPyResult trait. So any return value of them must implement IntoPyResult. It will be PyResult<PyObjectRef>, PyObjectRef and any PyResult<T> when T implements IntoPyObject. Practically we can list them like:
Any T when PyResult<T> is possible
PyObjectRef
PyResult<()> and () as None
PyRef<T: PyValue> like PyIntRef, PyStrRef
T: PyValue like PyInt, PyStr
Numbers like usize or f64 for PyInt and PyFloat
String for PyStr
And more types implementing IntoPyObject.
Just like the return type, parameters are also described in similar way.
For this function, n and k are defined as PyIntRef. This conversion is supported by TryFromObject trait. When any parameter type is T: TryFromObject instead of PyObjectRef, it will call the conversion function and return TypeError during the conversion. Practically, they are PyRef<T: PyValue> and a few more types like numbers and strings.
Note that all those conversions are done by runtime, which are identical as manual conversions when we call the conversion function manually from PyObjectRef.
#[pyfunction] is used to create a free function. #[pymethod] is used to create a method which can be bound. So here can be usually another prefix parameter self for PyRef<T: PyValue> and &self for T: PyValue.
These parameters are mostly treated just same as other parameter, especially when Self is PyRef<T>. &self for T is sometimes a bit different. The actual object for self is always PyRef<Self>, but the features are limited when it is represented as just &Self. Then there will be a special pattern like zelf: PyRef<Self>.
The data type is rust-side payload for the type. For simple usage, check for PyInt and PyStr. There are even empty types like PyNone. For complex types, PyDict will be interesting.
pyclass macro helps to implement a few traits with given attrs.
module: false for builtins and a string for others. This field will be automatically filled when defined in #[pymodule] macro.
name: The class name.
base: A rust type name of base class for inheritance. Mostly empty. See PyBool for an example.
metaclass: A rust type name of metaclass when required. Mostly empty.
#[pyclass] on impl and python attributes
This part is the most interesting part. Basically #[pyclass] collects python attributes. A class can contains #[pymethod], #[pyclassmethod], #[pygetset] and #[pyslot]. These attributes will be covered in next sections.
One of important feature of #[pyclass] is filling slots of PyType. Typically - but not necessarily - a group of slots are defiend as a trait in RustPython. with(...) will collect python attributes from the traits. Additionally flags set the type flags. See PyStr and Hashable for the slot traits. See also PyFunction and HAS_DICT for flags.
pymethod, pyclassmethod
This article already covered pymethod with pyfunction.
pygetset
Sometimes it is required to expose attributes of a class #[pygetset] allows this to happen
If it is desirable to make the variable editable, consider returning and AtomicCell, RwLock, or Mutex.
If this is not feasible, or if it is desired to run some checks when writing to the attribute, using #[pygetset] coupled with #[pygetset(setter)] allows for separate get and set functions.
slots provide fast path for a few frequently-accessed type attributes. #[pyslot] connects the annotated function to each slot. The function name must be same as slot name or tp_ prefixed slot name.
In RustPython, most of them are conventionally implemented through a trait.