Skip to content

Commit

Permalink
Review feedback.
Browse files Browse the repository at this point in the history
  • Loading branch information
stuhood committed May 16, 2023
1 parent 6c3fd07 commit 8818b2f
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 47 deletions.
42 changes: 21 additions & 21 deletions guide/src/class.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,27 +121,6 @@ created from Rust, but not from Python.

For arguments, see the [`Method arguments`](#method-arguments) section below.

### Constructors which accept a class argument

To create a constructor which takes a positional class argument, you can additionally mark your constructor with `#[classmethod]`:
```rust
# use pyo3::prelude::*;
# use pyo3::types::PyType;
# #[pyclass]
# struct BaseClass(PyObject);
#
#[pymethods]
impl BaseClass {
#[new]
#[classmethod]
fn py_new<'p>(cls: &'p PyType, py: Python<'p>) -> PyResult<Self> {
// Get an abstract attribute (presumably) declared on a subclass of this class.
let subclass_attr = cls.getattr("a_class_attr")?;
Ok(Self(subclass_attr.to_object(py)))
}
}
```

## Adding the class to a module

The next step is to create the module initializer and add our class to it:
Expand Down Expand Up @@ -628,6 +607,27 @@ Declares a class method callable from Python.
* For details on `parameter-list`, see the documentation of `Method arguments` section.
* The return type must be `PyResult<T>` or `T` for some `T` that implements `IntoPy<PyObject>`.

### Constructors which accept a class argument

To create a constructor which takes a positional class argument, you can combine the `#[classmethod]` and `#[new]` modifiers:
```rust
# use pyo3::prelude::*;
# use pyo3::types::PyType;
# #[pyclass]
# struct BaseClass(PyObject);
#
#[pymethods]
impl BaseClass {
#[new]
#[classmethod]
fn py_new<'p>(cls: &'p PyType, py: Python<'p>) -> PyResult<Self> {
// Get an abstract attribute (presumably) declared on a subclass of this class.
let subclass_attr = cls.getattr("a_class_attr")?;
Ok(Self(subclass_attr.to_object(py)))
}
}
```

## Static methods

To create a static method for a custom class, the method needs to be annotated with the
Expand Down
17 changes: 17 additions & 0 deletions pytests/src/pyclasses.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use pyo3::iter::IterNextOutput;
use pyo3::prelude::*;
use pyo3::types::PyType;

#[pyclass]
struct EmptyClass {}
Expand Down Expand Up @@ -35,9 +36,25 @@ impl PyClassIter {
}
}

/// Demonstrates a base class which can operate on the relevant subclass in its constructor.
#[pyclass(subclass)]
#[derive(Clone, Debug)]
struct AssertingBaseClass;

#[pymethods]
impl AssertingBaseClass {
#[new]
#[classmethod]
fn new(cls: &PyType, expected_type: &PyType) -> Self {
assert!(cls.is(expected_type));
Self
}
}

#[pymodule]
pub fn pyclasses(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_class::<EmptyClass>()?;
m.add_class::<PyClassIter>()?;
m.add_class::<AssertingBaseClass>()?;
Ok(())
}
9 changes: 9 additions & 0 deletions pytests/tests/test_pyclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,12 @@ def test_iter():
with pytest.raises(StopIteration) as excinfo:
next(i)
assert excinfo.value.value == "Ended"


class AssertingSubClass(pyclasses.AssertingBaseClass):
pass


def test_new_classmethod():
# The `AssertingBaseClass` constructor asserts that it is passed the relevant subclass.
_ = AssertingSubClass(expected_type=AssertingSubClass)
26 changes: 0 additions & 26 deletions tests/test_class_new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,29 +204,3 @@ fn new_with_custom_error() {
assert_eq!(err.to_string(), "ValueError: custom error");
});
}

#[pyclass]
#[derive(Clone, Debug)]
struct NewWithClassMethod;

#[pymethods]
impl NewWithClassMethod {
#[new]
#[classmethod]
fn new(cls: &PyType) -> PyResult<Self> {
assert!(cls.is_subclass_of::<NewWithClassMethod>()?);
Ok(Self)
}
}

#[test]
fn new_with_class_method() {
Python::with_gil(|py| {
let typeobj = py.get_type::<NewWithClassMethod>();
typeobj
.call0()
.unwrap()
.extract::<NewWithClassMethod>()
.unwrap();
});
}

0 comments on commit 8818b2f

Please sign in to comment.