- Notifications
You must be signed in to change notification settings - Fork1.3k
Python Attributes
RustPython has special attributes to support easy python object building.
These attributes are very common for every code chunk. They eventually turn intobuiltin_function_or_method
asFn(&VirtualMachine, FuncArgs) -> PyResult
.
The common form looks like this:
#[pyfunction]pubfnascii(obj:PyObjectRef,vm:&VirtualMachine) ->PyResult<String>{let repr = vm.to_repr(&obj)?;let ascii =to_ascii(repr.as_str());Ok(ascii)}
Thevm
paramter is just suffix. We add it as the last parameter unless we don't usevm
at all - very rare case. It takes an objectobj
asPyObjectRef
, which is a general python object. It returnsPyResult<String>
, which will turn intoPyResult<PyObjectRef>
the same representation ofPyResult
.
Every return value must be convertible toPyResult
. This is defined asIntoPyResult
trait. So any return value of them must implementIntoPyResult
. It will bePyResult<PyObjectRef>
,PyObjectRef
and anyPyResult<T>
when T implementsIntoPyObject
. Practically we can list them like:
- Any
T
whenPyResult<T>
is possible PyObjectRef
PyResult<()>
and()
asNone
PyRef<T: PyValue>
likePyIntRef
,PyStrRef
T: PyValue
likePyInt
,PyStr
- Numbers like
usize
orf64
forPyInt
andPyFloat
String
forPyStr
- And more types implementing
IntoPyObject
.
Just like the return type, parameters are also described in similar way.
fnmath_comb(n:PyIntRef,k:PyIntRef,vm:&VirtualMachine) ->PyResult<BigInt>{ ...}
For this function,n
andk
are defined asPyIntRef
. This conversion is supported byTryFromObject
trait. When any parameter type isT: TryFromObject
instead ofPyObjectRef
, it will call the conversion function and returnTypeError
during the conversion. Practically, they arePyRef<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 fromPyObjectRef
.
#[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 parameterself
forPyRef<T: PyValue>
and&self
forT: PyValue
.
#[pyclass(...)]implPyStr{ ...#[pymethod(magic)]fncontains(&self,needle:PyStrRef) ->bool{self.value.contains(needle.as_str())} ...}
These parameters are mostly treated just same as other parameter, especially whenSelf
isPyRef<T>
.&self
forT
is sometimes a bit different. The actual object forself
is alwaysPyRef<Self>
, but the features are limited when it is represented as just&Self
. Then there will be a special pattern likezelf: PyRef<Self>
.
...#[pymethod(magic)]fnmul(zelf:PyRef<Self>,value:isize,vm:&VirtualMachine) ->PyRef<Self>{ ...}...
This pattern is just same asself
forPyRef<T>
. So nothing special. Just a different notation.
pyclass
when applied to astruct
and its' associatedimpl
is used to create a python type in Rust side. For now, they essentially consists of 2 steps:
- A data type with
#[pyclass]
attribute. pyclass
on animpl
block gathering various stuff related to python attributes.
DirEntry
type invm/src/stdlib/os.rs
is a nice and compact example of small class.
#[pyclass(name)]#[derive(Debug)]structDirEntry{entry: fs::DirEntry,}#[pyclass]implDirEntry{ ...}
The data type is rust-side payload for the type. For simple usage, check forPyInt
andPyStr
. There are even empty types likePyNone
. 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.
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 ofPyType
. Typically - but not necessarily - a group of slots are defiend as a trait in RustPython.with(...)
will collect python attributes from the traits. Additionallyflags
set the type flags. SeePyStr
andHashable
for the slot traits. See alsoPyFunction
andHAS_DICT
for flags.
This article already coveredpymethod
withpyfunction
.
Sometimes it is required to expose attributes of a class#[pygetset]
allows this to happen
#[pygetset]fnco_posonlyargcount(&self) ->usize{self.code.posonlyarg_countasusize}
If it is desirable to make the variable editable, consider returning andAtomicCell
,RwLock
, orMutex
.
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.
#[pygetset]fnname(&self) ->PyStrRef{self.inner.name()}#[pygetset(setter)]fnset_name(&self,name:PyStrRef){self.inner.set_name(name)}
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 ortp_
prefixed slot name.
In RustPython, most of them are conventionally implemented through a trait.
- Hashable:
__hash__
- Callable:
__call__
- Comparable:
__eq__
,__ne__
,__ge__
,__ge__
,__le__
,__lt__
- Buffer:
tp_as_buffer
...
Note: For now, non-zero-sized payload(#[pyclass]
) withouttp_new
slot will make payload error after creating the instance.