subclass support (#64)
This commit is contained in:
parent
181e38b551
commit
ebcd4f5a33
|
@ -10,6 +10,8 @@ Changes
|
||||||
|
|
||||||
* Refactor `PyErr` implementation. Drop `py` parameter from constructor.
|
* Refactor `PyErr` implementation. Drop `py` parameter from constructor.
|
||||||
|
|
||||||
|
* Added subclass support #64
|
||||||
|
|
||||||
|
|
||||||
0.1.0 (07-23-2017)
|
0.1.0 (07-23-2017)
|
||||||
^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Python Class
|
# Python Class
|
||||||
|
|
||||||
Python class generation is powered by unstable [Procedural Macros](https://doc.rust-lang.org/book/first-edition/procedural-macros.html) and
|
Python class generation is powered by unstable [Procedural Macros](https://doc.rust-lang.org/book/first-edition/procedural-macros.html) and
|
||||||
[Specialization](https://github.com/rust-lang/rfcs/blob/master/text/1210-impl-specialization.md) and [Const fn](https://github.com/rust-lang/rfcs/blob/master/text/0911-const-fn.md)
|
[Specialization](https://github.com/rust-lang/rfcs/blob/master/text/1210-impl-specialization.md) and [Const fn](https://github.com/rust-lang/rfcs/blob/master/text/0911-const-fn.md)
|
||||||
features, so you need to turn on `proc_macro` and `specialization` features:
|
features, so you need to turn on `proc_macro` and `specialization` features:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
@ -61,6 +61,7 @@ python object that can be collector `PyGCProtocol` trait has to be implemented.
|
||||||
* `weakref` - adds support for python weak references
|
* `weakref` - adds support for python weak references
|
||||||
* `base=xxx.YYY` - use custom base class. It is not possible to call constructor
|
* `base=xxx.YYY` - use custom base class. It is not possible to call constructor
|
||||||
of base class at the moment. `xxx.YYY`, `xxx` - module name, `YYY` class name.
|
of base class at the moment. `xxx.YYY`, `xxx` - module name, `YYY` class name.
|
||||||
|
* `subclass` - adds subclass support so that Python classes can inherit from this class
|
||||||
|
|
||||||
|
|
||||||
## Constructor
|
## Constructor
|
||||||
|
@ -277,7 +278,7 @@ impl MyClass {
|
||||||
|
|
||||||
By default pyo3 library uses function signature to determine which arguments are required.
|
By default pyo3 library uses function signature to determine which arguments are required.
|
||||||
Then it scans incoming `args` parameter and then incoming `kwargs` parameter. If it can not
|
Then it scans incoming `args` parameter and then incoming `kwargs` parameter. If it can not
|
||||||
find all required parameters, it raises `TypeError` exception.
|
find all required parameters, it raises `TypeError` exception.
|
||||||
It is possible to override default bahavior with `#[args(...)]` attribute. `args` attribute
|
It is possible to override default bahavior with `#[args(...)]` attribute. `args` attribute
|
||||||
accept comma separated list of parameters in form `attr_name="default value"`. Each parameter
|
accept comma separated list of parameters in form `attr_name="default value"`. Each parameter
|
||||||
has to match method parameter by name.
|
has to match method parameter by name.
|
||||||
|
@ -291,15 +292,15 @@ Each parameter could one of following type:
|
||||||
* kwargs="\*\*": "kwargs" is kwyword arguments, coresponds to python's `def meth(**kwargs)`.
|
* kwargs="\*\*": "kwargs" is kwyword arguments, coresponds to python's `def meth(**kwargs)`.
|
||||||
Type of `kwargs` parameter has to be `Option<&PyDict>`.
|
Type of `kwargs` parameter has to be `Option<&PyDict>`.
|
||||||
* arg="Value": arguments with default value. coresponds to python's `def meth(arg=Value)`.
|
* arg="Value": arguments with default value. coresponds to python's `def meth(arg=Value)`.
|
||||||
if `arg` argument is defined after var arguments it is treated as keyword argument.
|
if `arg` argument is defined after var arguments it is treated as keyword argument.
|
||||||
Note that `Value` has to be valid rust code, pyo3 just inserts it into generated
|
Note that `Value` has to be valid rust code, pyo3 just inserts it into generated
|
||||||
code unmodified.
|
code unmodified.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```rust
|
```rust
|
||||||
#[py::methods]
|
#[py::methods]
|
||||||
impl MyClass {
|
impl MyClass {
|
||||||
|
|
||||||
#[args(arg1=true, args="*", arg2=10, kwargs="**")]
|
#[args(arg1=true, args="*", arg2=10, kwargs="**")]
|
||||||
fn method(&self, arg1: bool, args: &PyTuple, arg2: i32, kwargs: Option<&PyTuple>) -> PyResult<i32> {
|
fn method(&self, arg1: bool, args: &PyTuple, arg2: i32, kwargs: Option<&PyTuple>) -> PyResult<i32> {
|
||||||
Ok(1)
|
Ok(1)
|
||||||
|
@ -310,10 +311,10 @@ impl MyClass {
|
||||||
|
|
||||||
## Class customizations
|
## Class customizations
|
||||||
|
|
||||||
Python object model defines several protocols for different object behavior,
|
Python object model defines several protocols for different object behavior,
|
||||||
like sequence, mapping or number protocols. pyo3 library defines separate trait for each
|
like sequence, mapping or number protocols. pyo3 library defines separate trait for each
|
||||||
of them. To provide specific python object behavior you need to implement specific trait
|
of them. To provide specific python object behavior you need to implement specific trait
|
||||||
for your struct. Important note, each protocol implementation block has to be annotated
|
for your struct. Important note, each protocol implementation block has to be annotated
|
||||||
with `#[py::proto]` attribute.
|
with `#[py::proto]` attribute.
|
||||||
|
|
||||||
### Basic object customization
|
### Basic object customization
|
||||||
|
@ -330,49 +331,49 @@ To customize object attribute access define following methods:
|
||||||
|
|
||||||
Each methods coresponds to python's `self.attr`, `self.attr = value` and `del self.attr` code.
|
Each methods coresponds to python's `self.attr`, `self.attr = value` and `del self.attr` code.
|
||||||
|
|
||||||
#### String Conversions
|
#### String Conversions
|
||||||
|
|
||||||
* `fn __repr__(&self) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
|
* `fn __repr__(&self) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
|
||||||
* `fn __str__(&self) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
|
* `fn __str__(&self) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
|
||||||
|
|
||||||
Possible return types for `__str__` and `__repr__` are `PyResult<String>` or `PyResult<PyString>`.
|
Possible return types for `__str__` and `__repr__` are `PyResult<String>` or `PyResult<PyString>`.
|
||||||
In Python 2.7, Unicode strings returned by `__str__` and `__repr__` will be converted to byte strings
|
In Python 2.7, Unicode strings returned by `__str__` and `__repr__` will be converted to byte strings
|
||||||
by the Python runtime, which results in an exception if the string contains non-ASCII characters.
|
by the Python runtime, which results in an exception if the string contains non-ASCII characters.
|
||||||
|
|
||||||
* `fn __bytes__(&self) -> PyResult<PyBytes>`
|
* `fn __bytes__(&self) -> PyResult<PyBytes>`
|
||||||
|
|
||||||
On Python 3.x, provides the conversion to `bytes`.
|
On Python 3.x, provides the conversion to `bytes`.
|
||||||
On Python 2.7, `__bytes__` is allowed but has no effect.
|
On Python 2.7, `__bytes__` is allowed but has no effect.
|
||||||
|
|
||||||
* `fn __unicode__(&self) -> PyResult<PyUnicode>`
|
* `fn __unicode__(&self) -> PyResult<PyUnicode>`
|
||||||
|
|
||||||
On Python 2.7, provides the conversion to `unicode`.
|
On Python 2.7, provides the conversion to `unicode`.
|
||||||
On Python 3.x, `__unicode__` is allowed but has no effect.
|
On Python 3.x, `__unicode__` is allowed but has no effect.
|
||||||
|
|
||||||
* `fn __format__(&self, format_spec: &str) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
|
* `fn __format__(&self, format_spec: &str) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
|
||||||
|
|
||||||
Special method that is used by the `format()` builtin and the `str.format()` method.
|
Special method that is used by the `format()` builtin and the `str.format()` method.
|
||||||
Possible return types are `PyResult<String>` or `PyResult<PyString>`.
|
Possible return types are `PyResult<String>` or `PyResult<PyString>`.
|
||||||
|
|
||||||
#### Comparison operators
|
#### Comparison operators
|
||||||
|
|
||||||
* `fn __richcmp__(&self, other: impl FromPyObject, op: CompareOp) -> PyResult<impl ToPyObject>`
|
* `fn __richcmp__(&self, other: impl FromPyObject, op: CompareOp) -> PyResult<impl ToPyObject>`
|
||||||
|
|
||||||
Overloads Python comparison operations (`==`, `!=`, `<`, `<=`, `>`, and `>=`).
|
Overloads Python comparison operations (`==`, `!=`, `<`, `<=`, `>`, and `>=`).
|
||||||
The `op` argument indicates the comparison operation being performed.
|
The `op` argument indicates the comparison operation being performed.
|
||||||
The return type will normally be `PyResult<bool>`, but any Python object can be returned.
|
The return type will normally be `PyResult<bool>`, but any Python object can be returned.
|
||||||
If `other` is not of the type specified in the signature, the generated code will
|
If `other` is not of the type specified in the signature, the generated code will
|
||||||
automatically `return NotImplemented`.
|
automatically `return NotImplemented`.
|
||||||
|
|
||||||
* `fn __hash__(&self) -> PyResult<impl PrimInt>`
|
* `fn __hash__(&self) -> PyResult<impl PrimInt>`
|
||||||
|
|
||||||
Objects that compare equal must have the same hash value.
|
Objects that compare equal must have the same hash value.
|
||||||
The return type must be `PyResult<T>` where `T` is one of Rust's primitive integer types.
|
The return type must be `PyResult<T>` where `T` is one of Rust's primitive integer types.
|
||||||
|
|
||||||
#### Other methods
|
#### Other methods
|
||||||
|
|
||||||
* `fn __bool__(&self) -> PyResult<bool>`
|
* `fn __bool__(&self) -> PyResult<bool>`
|
||||||
|
|
||||||
Determines the "truthyness" of the object.
|
Determines the "truthyness" of the object.
|
||||||
This method works for both python 3 and python 2,
|
This method works for both python 3 and python 2,
|
||||||
even on Python 2.7 where the Python spelling was `__nonzero__`.
|
even on Python 2.7 where the Python spelling was `__nonzero__`.
|
||||||
|
@ -400,7 +401,7 @@ use pyo3::{py, PyObject, PyGCProtocol, PyVisit, PyTraverseError};
|
||||||
struct ClassWithGCSupport {
|
struct ClassWithGCSupport {
|
||||||
obj: Option<PyObject>,
|
obj: Option<PyObject>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[py::proto]
|
#[py::proto]
|
||||||
impl PyGCProtocol for ClassWithGCSupport {
|
impl PyGCProtocol for ClassWithGCSupport {
|
||||||
fn __traverse__(&self, visit: PyVisit) -> Result<(), PyTraverseError> {
|
fn __traverse__(&self, visit: PyVisit) -> Result<(), PyTraverseError> {
|
||||||
|
@ -409,7 +410,7 @@ impl PyGCProtocol for ClassWithGCSupport {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn __clear__(&mut self) {
|
fn __clear__(&mut self) {
|
||||||
if let Some(obj) = self.obj.take() {
|
if let Some(obj) = self.obj.take() {
|
||||||
// Release reference, this decrements ref counter.
|
// Release reference, this decrements ref counter.
|
||||||
|
@ -421,18 +422,18 @@ impl PyGCProtocol for ClassWithGCSupport {
|
||||||
|
|
||||||
Special protocol trait implementation has to be annotated with `#[py::proto]` attribute.
|
Special protocol trait implementation has to be annotated with `#[py::proto]` attribute.
|
||||||
|
|
||||||
It is also possible to enable gc for custom class using `gc` parameter for `py::class` annotation.
|
It is also possible to enable gc for custom class using `gc` parameter for `py::class` annotation.
|
||||||
i.e. `#[py::class(gc)]`. In that case instances of custom class participate in python garbage
|
i.e. `#[py::class(gc)]`. In that case instances of custom class participate in python garbage
|
||||||
collector, and it is possible to track them with `gc` module methods.
|
collector, and it is possible to track them with `gc` module methods.
|
||||||
|
|
||||||
### Iterator Types
|
### Iterator Types
|
||||||
|
|
||||||
Iterators can be defined using the
|
Iterators can be defined using the
|
||||||
[`PyIterProtocol`](https://pyo3.github.io/pyo3/pyo3/class/iter/trait.PyIterProtocol.html) trait.
|
[`PyIterProtocol`](https://pyo3.github.io/pyo3/pyo3/class/iter/trait.PyIterProtocol.html) trait.
|
||||||
It includes two methods `__iter__` and `__next__`:
|
It includes two methods `__iter__` and `__next__`:
|
||||||
* `fn __iter__(&mut self) -> PyResult<impl IntoPyObject>`
|
* `fn __iter__(&mut self) -> PyResult<impl IntoPyObject>`
|
||||||
* `fn __next__(&mut self) -> PyResult<Option<impl IntoPyObject>>`
|
* `fn __next__(&mut self) -> PyResult<Option<impl IntoPyObject>>`
|
||||||
|
|
||||||
Returning `Ok(None)` from `__next__` indicates that that there are no further items.
|
Returning `Ok(None)` from `__next__` indicates that that there are no further items.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
|
@ -440,6 +440,10 @@ fn parse_attribute(attr: String) -> (HashMap<&'static str, syn::Ident>,
|
||||||
flags.push(syn::Ident::from("_pyo3::typeob::PY_TYPE_FLAG_WEAKREF"));
|
flags.push(syn::Ident::from("_pyo3::typeob::PY_TYPE_FLAG_WEAKREF"));
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
"subclass" => {
|
||||||
|
flags.push(syn::Ident::from("_pyo3::typeob::PY_TYPE_FLAG_BASETYPE"));
|
||||||
|
continue
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
println!("Unsupported parameter: {:?}", key);
|
println!("Unsupported parameter: {:?}", key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,9 @@ pub const PY_TYPE_FLAG_GC: usize = 1<<0;
|
||||||
/// Type object supports python weak references
|
/// Type object supports python weak references
|
||||||
pub const PY_TYPE_FLAG_WEAKREF: usize = 1<<1;
|
pub const PY_TYPE_FLAG_WEAKREF: usize = 1<<1;
|
||||||
|
|
||||||
|
/// Type object can be used as the base type of another type
|
||||||
|
pub const PY_TYPE_FLAG_BASETYPE: usize = 1<<2;
|
||||||
|
|
||||||
|
|
||||||
impl<'a, T: ?Sized> PyTypeInfo for &'a T where T: PyTypeInfo {
|
impl<'a, T: ?Sized> PyTypeInfo for &'a T where T: PyTypeInfo {
|
||||||
type Type = T::Type;
|
type Type = T::Type;
|
||||||
|
@ -307,6 +310,9 @@ fn py_class_flags<T: PyTypeInfo>(type_object: &mut ffi::PyTypeObject) {
|
||||||
} else {
|
} else {
|
||||||
type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT;
|
type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT;
|
||||||
}
|
}
|
||||||
|
if T::FLAGS & PY_TYPE_FLAG_BASETYPE != 0 {
|
||||||
|
type_object.tp_flags |= ffi::Py_TPFLAGS_BASETYPE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(Py_3))]
|
#[cfg(not(Py_3))]
|
||||||
|
@ -321,6 +327,9 @@ fn py_class_flags<T: PyTypeInfo>(type_object: &mut ffi::PyTypeObject) {
|
||||||
if !type_object.tp_as_buffer.is_null() {
|
if !type_object.tp_as_buffer.is_null() {
|
||||||
type_object.tp_flags = type_object.tp_flags | ffi::Py_TPFLAGS_HAVE_NEWBUFFER;
|
type_object.tp_flags = type_object.tp_flags | ffi::Py_TPFLAGS_HAVE_NEWBUFFER;
|
||||||
}
|
}
|
||||||
|
if T::FLAGS & PY_TYPE_FLAG_BASETYPE != 0 {
|
||||||
|
type_object.tp_flags |= ffi::Py_TPFLAGS_BASETYPE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn py_class_method_defs<T>() -> PyResult<(Option<ffi::newfunc>,
|
fn py_class_method_defs<T>() -> PyResult<(Option<ffi::newfunc>,
|
||||||
|
|
|
@ -1239,3 +1239,18 @@ fn meth_args() {
|
||||||
py_run!(py, inst, "assert inst.get_kwargs(1,2,3,t=1,n=2) == [(1,2,3), {'t': 1, 'n': 2}]");
|
py_run!(py, inst, "assert inst.get_kwargs(1,2,3,t=1,n=2) == [(1,2,3), {'t': 1, 'n': 2}]");
|
||||||
// py_expect_exception!(py, inst, "inst.get_kwarg(100)", TypeError);
|
// py_expect_exception!(py, inst, "inst.get_kwarg(100)", TypeError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[py::class(subclass)]
|
||||||
|
struct SubclassAble {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn subclass() {
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
|
||||||
|
let d = PyDict::new(py);
|
||||||
|
d.set_item("SubclassAble", py.get_type::<SubclassAble>()).unwrap();
|
||||||
|
py.run("class A(SubclassAble): pass\nassert issubclass(A, SubclassAble)", None, Some(d))
|
||||||
|
.map_err(|e| e.print(py))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue