Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix being able to call arg-less #[new] with any args from Python #2749

Merged
merged 2 commits into from
Nov 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions guide/src/class.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ Consult the table below to determine which type your constructor should return:

## Inheritance

By default, `PyAny` is used as the base class. To override this default,
By default, `object`, i.e. `PyAny` is used as the base class. To override this default,
use the `extends` parameter for `pyclass` with the full path to the base class.

For convenience, `(T, U)` implements `Into<PyClassInitializer<T>>` where `U` is the
Expand Down Expand Up @@ -310,7 +310,8 @@ impl SubSubClass {
```

You can also inherit native types such as `PyDict`, if they implement
[`PySizedLayout`]({{#PYO3_DOCS_URL}}/pyo3/type_object/trait.PySizedLayout.html). However, this is not supported when building for the Python limited API (aka the `abi3` feature of PyO3).
[`PySizedLayout`]({{#PYO3_DOCS_URL}}/pyo3/type_object/trait.PySizedLayout.html).
This is not supported when building for the Python limited API (aka the `abi3` feature of PyO3).

However, because of some technical problems, we don't currently provide safe upcasting methods for types
that inherit native types. Even in such cases, you can unsafely get a base class by raw pointer conversion.
Expand All @@ -334,6 +335,7 @@ impl DictWithCounter {
fn new() -> Self {
Self::default()
}

fn set(mut self_: PyRefMut<'_, Self>, key: String, value: &PyAny) -> PyResult<()> {
self_.counter.entry(key.clone()).or_insert(0);
let py = self_.py();
Expand Down Expand Up @@ -371,6 +373,42 @@ impl SubClass {
}
```

The `__new__` constructor of a native base class is called implicitly when
creating a new instance from Python. Be sure to accept arguments in the
`#[new]` method that you want the base class to get, even if they are not used
in that `fn`:

```rust
# #[allow(dead_code)]
# #[cfg(not(Py_LIMITED_API))] {
# use pyo3::prelude::*;
use pyo3::types::PyDict;

#[pyclass(extends=PyDict)]
struct MyDict {
private: i32,
}

#[pymethods]
impl MyDict {
#[new]
#[pyo3(signature = (*args, **kwargs))]
fn new(args: &PyAny, kwargs: Option<&PyAny>) -> Self {
Self { private: 0 }
}

// some custom methods that use `private` here...
}
# Python::with_gil(|py| {
# let cls = py.get_type::<MyDict>();
# pyo3::py_run!(py, cls, "cls(a=1, b=2)")
# });
# }
```

Here, the `args` and `kwargs` allow creating instances of the subclass passing
initial items, such as `MyDict(item_sequence)` or `MyDict(a=1, b=2)`.

## Object properties

PyO3 supports two ways to add properties to your `#[pyclass]`:
Expand Down
2 changes: 2 additions & 0 deletions newsfragments/2749.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix a bug that allowed `#[new]` pymethods with no arguments to be called from
Python with any argument list.
4 changes: 0 additions & 4 deletions pyo3-macros-backend/src/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ pub fn impl_arg_params(
py: &syn::Ident,
fastcall: bool,
) -> Result<(TokenStream, Vec<TokenStream>)> {
if spec.signature.arguments.is_empty() {
return Ok((TokenStream::new(), vec![]));
}

let args_array = syn::Ident::new("output", Span::call_site());

if !fastcall && is_forwarded_args(&spec.signature) {
Expand Down
7 changes: 7 additions & 0 deletions tests/test_class_new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::IntoPyDict;

#[pyclass]
struct EmptyClassWithNew {}
Expand All @@ -23,6 +24,12 @@ fn empty_class_with_new() {
.unwrap()
.downcast::<PyCell<EmptyClassWithNew>>()
.is_ok());

// Calling with arbitrary args or kwargs is not ok
assert!(typeobj.call(("some", "args"), None).is_err());
assert!(typeobj
.call((), Some([("some", "kwarg")].into_py_dict(py)))
.is_err());
});
}

Expand Down
2 changes: 1 addition & 1 deletion tests/test_inheritance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ mod inheriting_native_type {
#[pymethods]
impl CustomException {
#[new]
fn new() -> Self {
fn new(_exc_arg: &PyAny) -> Self {
CustomException {
context: "Hello :)",
}
Expand Down