pymethods: support __call__ proto
This commit is contained in:
parent
f5f000e5a4
commit
4b2345fe80
|
@ -45,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Move Py_DecodeLocale from sysmodule to fileutils. [#1887](https://github.com/PyO3/pyo3/pull/1887)
|
||||
- Deprecate `PySys_AddWarnOption`, `PySys_AddWarnOptionUnicode` and `PySys_HasWarnOptions`. [#1887](https://github.com/PyO3/pyo3/pull/1887)
|
||||
- Remove function PyTuple_ClearFreeList from python 3.9 above. [#1887](https://github.com/PyO3/pyo3/pull/1887)
|
||||
- Deprecate `#[call]` attribute in favor of using `fn __call__`. [#1929](https://github.com/PyO3/pyo3/pull/1929)
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
@ -16,8 +16,7 @@ impl MyClass {
|
|||
Self { elements }
|
||||
}
|
||||
|
||||
#[call]
|
||||
fn call(&mut self, new_element: i32) -> usize {
|
||||
fn __call__(&mut self, new_element: i32) -> usize {
|
||||
self.elements.push(new_element);
|
||||
self.elements.len()
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ This chapter will discuss the functionality and configuration these attributes o
|
|||
- [`#[setter]`](#object-properties-using-getter-and-setter)
|
||||
- [`#[staticmethod]`](#static-methods)
|
||||
- [`#[classmethod]`](#class-methods)
|
||||
- [`#[call]`](#callable-objects)
|
||||
- [`#[classattr]`](#class-attributes)
|
||||
- [`#[args]`](#method-arguments)
|
||||
- [`#[pyproto]`](class/protocols.html)
|
||||
|
@ -46,7 +45,7 @@ Custom Python classes can then be added to a module using `add_class()`.
|
|||
# use pyo3::prelude::*;
|
||||
# #[pyclass]
|
||||
# struct MyClass {
|
||||
# #[allow(dead_code)]
|
||||
# #[allow(dead_code)]
|
||||
# num: i32,
|
||||
# }
|
||||
#[pymodule]
|
||||
|
@ -613,74 +612,6 @@ impl MyClass {
|
|||
}
|
||||
```
|
||||
|
||||
## Callable objects
|
||||
|
||||
To specify a custom `__call__` method for a custom class, the method needs to be annotated with
|
||||
the `#[call]` attribute. Arguments of the method are specified as for instance methods.
|
||||
|
||||
The following pyclass is a basic decorator - its constructor takes a Python object
|
||||
as argument and calls that object when called.
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::{PyDict, PyTuple};
|
||||
#
|
||||
#[pyclass(name = "counter")]
|
||||
struct PyCounter {
|
||||
count: u64,
|
||||
wraps: Py<PyAny>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyCounter {
|
||||
#[new]
|
||||
fn __new__(wraps: Py<PyAny>) -> Self {
|
||||
PyCounter { count: 0, wraps }
|
||||
}
|
||||
|
||||
#[call]
|
||||
#[args(args = "*", kwargs = "**")]
|
||||
fn __call__(
|
||||
&mut self,
|
||||
py: Python,
|
||||
args: &PyTuple,
|
||||
kwargs: Option<&PyDict>,
|
||||
) -> PyResult<Py<PyAny>> {
|
||||
self.count += 1;
|
||||
let name = self.wraps.getattr(py, "__name__").unwrap();
|
||||
|
||||
println!("{} has been called {} time(s).", name, self.count);
|
||||
self.wraps.call(py, args, kwargs)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Python code:
|
||||
|
||||
```python
|
||||
@counter
|
||||
def say_hello():
|
||||
print("hello")
|
||||
|
||||
say_hello()
|
||||
say_hello()
|
||||
say_hello()
|
||||
say_hello()
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
say_hello has been called 1 time(s).
|
||||
hello
|
||||
say_hello has been called 2 time(s).
|
||||
hello
|
||||
say_hello has been called 3 time(s).
|
||||
hello
|
||||
say_hello has been called 4 time(s).
|
||||
hello
|
||||
```
|
||||
|
||||
## Method arguments
|
||||
|
||||
By default, PyO3 uses function signatures to determine which arguments are required. Then it scans
|
||||
|
@ -848,11 +779,6 @@ impl pyo3::class::impl_::PyClassImpl for MyClass {
|
|||
let collector = PyClassImplCollector::<Self>::new();
|
||||
collector.free_impl()
|
||||
}
|
||||
fn get_call() -> Option<pyo3::ffi::PyCFunctionWithKeywords> {
|
||||
use pyo3::class::impl_::*;
|
||||
let collector = PyClassImplCollector::<Self>::new();
|
||||
collector.call_impl()
|
||||
}
|
||||
fn for_each_proto_slot(visitor: &mut dyn FnMut(&[pyo3::ffi::PyType_Slot])) {
|
||||
// Implementation which uses dtolnay specialization to load all slots.
|
||||
use pyo3::class::impl_::*;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
## Class customizations
|
||||
# Class customizations
|
||||
|
||||
PyO3 uses the `#[pyproto]` attribute in combination with special traits to implement certain protocol (aka `__dunder__`) methods of Python classes. The special traits are listed in this chapter of the guide. See also the [documentation for the `pyo3::class` module]({{#PYO3_DOCS_URL}}/pyo3/class/index.html).
|
||||
|
||||
|
@ -10,11 +10,11 @@ All `#[pyproto]` methods can return `T` instead of `PyResult<T>` if the method i
|
|||
|
||||
There are many "dunder" methods which are not included in any of PyO3's protocol traits, such as `__dir__`. These methods can be implemented in `#[pymethods]` as already covered in the previous section.
|
||||
|
||||
### Basic object customization
|
||||
## Basic object customization
|
||||
|
||||
The [`PyObjectProtocol`] trait provides several basic customizations.
|
||||
|
||||
#### Attribute access
|
||||
### Attribute access
|
||||
|
||||
To customize object attribute access, define the following methods:
|
||||
|
||||
|
@ -24,14 +24,14 @@ To customize object attribute access, define the following methods:
|
|||
|
||||
Each method corresponds 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 __str__(&self) -> PyResult<impl ToPyObject<ObjectType=PyString>>`
|
||||
|
||||
Possible return types for `__str__` and `__repr__` are `PyResult<String>` or `PyResult<PyString>`.
|
||||
|
||||
#### Comparison operators
|
||||
### Comparison operators
|
||||
|
||||
* `fn __richcmp__(&self, other: impl FromPyObject, op: CompareOp) -> PyResult<impl ToPyObject>`
|
||||
|
||||
|
@ -46,13 +46,78 @@ Each method corresponds to Python's `self.attr`, `self.attr = value` and `del se
|
|||
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.
|
||||
|
||||
#### Other methods
|
||||
### Other methods
|
||||
|
||||
* `fn __bool__(&self) -> PyResult<bool>`
|
||||
|
||||
Determines the "truthyness" of the object.
|
||||
|
||||
### Emulating numeric types
|
||||
## Callable objects
|
||||
|
||||
Custom classes can be callable if they have a `#[pymethod]` named `__call__`.
|
||||
|
||||
The following pyclass is a basic decorator - its constructor takes a Python object
|
||||
as argument and calls that object when called.
|
||||
|
||||
```rust
|
||||
# use pyo3::prelude::*;
|
||||
# use pyo3::types::{PyDict, PyTuple};
|
||||
#
|
||||
#[pyclass(name = "counter")]
|
||||
struct PyCounter {
|
||||
count: u64,
|
||||
wraps: Py<PyAny>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyCounter {
|
||||
#[new]
|
||||
fn __new__(wraps: Py<PyAny>) -> Self {
|
||||
PyCounter { count: 0, wraps }
|
||||
}
|
||||
|
||||
fn __call__(
|
||||
&mut self,
|
||||
py: Python,
|
||||
args: &PyTuple,
|
||||
kwargs: Option<&PyDict>,
|
||||
) -> PyResult<Py<PyAny>> {
|
||||
self.count += 1;
|
||||
let name = self.wraps.getattr(py, "__name__").unwrap();
|
||||
|
||||
println!("{} has been called {} time(s).", name, self.count);
|
||||
self.wraps.call(py, args, kwargs)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Python code:
|
||||
|
||||
```python
|
||||
@counter
|
||||
def say_hello():
|
||||
print("hello")
|
||||
|
||||
say_hello()
|
||||
say_hello()
|
||||
say_hello()
|
||||
say_hello()
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
say_hello has been called 1 time(s).
|
||||
hello
|
||||
say_hello has been called 2 time(s).
|
||||
hello
|
||||
say_hello has been called 3 time(s).
|
||||
hello
|
||||
say_hello has been called 4 time(s).
|
||||
hello
|
||||
```
|
||||
|
||||
## Emulating numeric types
|
||||
|
||||
The [`PyNumberProtocol`] trait can be implemented to emulate [numeric types](https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types).
|
||||
|
||||
|
@ -132,7 +197,7 @@ Other:
|
|||
|
||||
* `fn __index__(&'p self) -> PyResult<impl ToPyObject>`
|
||||
|
||||
### Emulating sequential containers (such as lists or tuples)
|
||||
## Emulating sequential containers (such as lists or tuples)
|
||||
|
||||
The [`PySequenceProtocol`] trait can be implemented to emulate
|
||||
[sequential container types](https://docs.python.org/3/reference/datamodel.html#emulating-container-types).
|
||||
|
@ -195,7 +260,7 @@ where _N_ is the length of the sequence.
|
|||
Used by the `*=` operator, after trying the numeric in place multiplication via
|
||||
the `PyNumberProtocol` trait method.
|
||||
|
||||
### Emulating mapping containers (such as dictionaries)
|
||||
## Emulating mapping containers (such as dictionaries)
|
||||
|
||||
The [`PyMappingProtocol`] trait allows to emulate
|
||||
[mapping container types](https://docs.python.org/3/reference/datamodel.html#emulating-container-types).
|
||||
|
@ -228,7 +293,7 @@ For a mapping, the keys may be Python objects of arbitrary type.
|
|||
The same exceptions should be raised for improper key values as
|
||||
for the `__getitem__()` method.
|
||||
|
||||
### Garbage Collector Integration
|
||||
## Garbage Collector Integration
|
||||
|
||||
If your type owns references to other Python objects, you will need to
|
||||
integrate with Python's garbage collector so that the GC is aware of
|
||||
|
@ -280,7 +345,7 @@ at compile time:
|
|||
struct GCTracked {} // Fails because it does not implement PyGCProtocol
|
||||
```
|
||||
|
||||
### Iterator Types
|
||||
## Iterator Types
|
||||
|
||||
Iterators can be defined using the
|
||||
[`PyIterProtocol`]({{#PYO3_DOCS_URL}}/pyo3/class/iter/trait.PyIterProtocol.html) trait.
|
||||
|
@ -365,7 +430,7 @@ impl PyIterProtocol for Container {
|
|||
For more details on Python's iteration protocols, check out [the "Iterator Types" section of the library
|
||||
documentation](https://docs.python.org/3/library/stdtypes.html#iterator-types).
|
||||
|
||||
#### Returning a value from iteration
|
||||
### Returning a value from iteration
|
||||
|
||||
This guide has so far shown how to use `Option<T>` to implement yielding values during iteration.
|
||||
In Python a generator can also return a value. To express this in Rust, PyO3 provides the
|
||||
|
|
|
@ -6,6 +6,7 @@ pub enum Deprecation {
|
|||
PyfnNameArgument,
|
||||
PyModuleNameArgument,
|
||||
TextSignatureAttribute,
|
||||
CallAttribute,
|
||||
}
|
||||
|
||||
impl Deprecation {
|
||||
|
@ -15,6 +16,7 @@ impl Deprecation {
|
|||
Deprecation::PyfnNameArgument => "PYFN_NAME_ARGUMENT",
|
||||
Deprecation::PyModuleNameArgument => "PYMODULE_NAME_ARGUMENT",
|
||||
Deprecation::TextSignatureAttribute => "TEXT_SIGNATURE_ATTRIBUTE",
|
||||
Deprecation::CallAttribute => "CALL_ATTRIBUTE",
|
||||
};
|
||||
syn::Ident::new(string, span)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
|
||||
use crate::attributes::TextSignatureAttribute;
|
||||
use crate::deprecations::Deprecation;
|
||||
use crate::params::{accept_args_kwargs, impl_arg_params};
|
||||
use crate::pyfunction::PyFunctionOptions;
|
||||
use crate::pyfunction::{PyFunctionArgPyO3Attributes, PyFunctionSignature};
|
||||
|
@ -65,8 +66,6 @@ impl<'a> FnArg<'a> {
|
|||
pub enum MethodTypeAttribute {
|
||||
/// #[new]
|
||||
New,
|
||||
/// #[call]
|
||||
Call,
|
||||
/// #[classmethod]
|
||||
ClassMethod,
|
||||
/// #[classattr]
|
||||
|
@ -84,7 +83,6 @@ pub enum FnType {
|
|||
Getter(SelfType),
|
||||
Setter(SelfType),
|
||||
Fn(SelfType),
|
||||
FnCall(SelfType),
|
||||
FnNew,
|
||||
FnClass,
|
||||
FnStatic,
|
||||
|
@ -99,11 +97,10 @@ impl FnType {
|
|||
error_mode: ExtractErrorMode,
|
||||
) -> TokenStream {
|
||||
match self {
|
||||
FnType::Getter(st) | FnType::Setter(st) | FnType::Fn(st) | FnType::FnCall(st) => st
|
||||
.receiver(
|
||||
cls.expect("no class given for Fn with a \"self\" receiver"),
|
||||
error_mode,
|
||||
),
|
||||
FnType::Getter(st) | FnType::Setter(st) | FnType::Fn(st) => st.receiver(
|
||||
cls.expect("no class given for Fn with a \"self\" receiver"),
|
||||
error_mode,
|
||||
),
|
||||
FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => {
|
||||
quote!()
|
||||
}
|
||||
|
@ -260,31 +257,22 @@ impl<'a> FnSpec<'a> {
|
|||
meth_attrs: &mut Vec<syn::Attribute>,
|
||||
options: PyFunctionOptions,
|
||||
) -> Result<FnSpec<'a>> {
|
||||
let PyFunctionOptions {
|
||||
text_signature,
|
||||
name,
|
||||
mut deprecations,
|
||||
..
|
||||
} = options;
|
||||
|
||||
let MethodAttributes {
|
||||
ty: fn_type_attr,
|
||||
args: fn_attrs,
|
||||
mut python_name,
|
||||
} = parse_method_attributes(meth_attrs, options.name.map(|name| name.0))?;
|
||||
|
||||
match fn_type_attr {
|
||||
Some(MethodTypeAttribute::New) => {
|
||||
if let Some(name) = &python_name {
|
||||
bail_spanned!(name.span() => "`name` not allowed with `#[new]`");
|
||||
}
|
||||
python_name = Some(syn::Ident::new("__new__", Span::call_site()))
|
||||
}
|
||||
Some(MethodTypeAttribute::Call) => {
|
||||
if let Some(name) = &python_name {
|
||||
bail_spanned!(name.span() => "`name` not allowed with `#[call]`");
|
||||
}
|
||||
python_name = Some(syn::Ident::new("__call__", Span::call_site()))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} = parse_method_attributes(meth_attrs, name.map(|name| name.0), &mut deprecations)?;
|
||||
|
||||
let (fn_type, skip_first_arg, fixed_convention) =
|
||||
Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?;
|
||||
Self::ensure_text_signature_on_valid_method(&fn_type, options.text_signature.as_ref())?;
|
||||
Self::ensure_text_signature_on_valid_method(&fn_type, text_signature.as_ref())?;
|
||||
|
||||
let name = &sig.ident;
|
||||
let ty = get_return_info(&sig.output);
|
||||
|
@ -292,10 +280,7 @@ impl<'a> FnSpec<'a> {
|
|||
|
||||
let doc = utils::get_doc(
|
||||
meth_attrs,
|
||||
options
|
||||
.text_signature
|
||||
.as_ref()
|
||||
.map(|attr| (&python_name, attr)),
|
||||
text_signature.as_ref().map(|attr| (&python_name, attr)),
|
||||
);
|
||||
|
||||
let arguments: Vec<_> = if skip_first_arg {
|
||||
|
@ -323,7 +308,7 @@ impl<'a> FnSpec<'a> {
|
|||
args: arguments,
|
||||
output: ty,
|
||||
doc,
|
||||
deprecations: options.deprecations,
|
||||
deprecations,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -342,10 +327,7 @@ impl<'a> FnSpec<'a> {
|
|||
"text_signature not allowed on __new__; if you want to add a signature on \
|
||||
__new__, put it on the struct definition instead"
|
||||
),
|
||||
FnType::FnCall(_)
|
||||
| FnType::Getter(_)
|
||||
| FnType::Setter(_)
|
||||
| FnType::ClassAttribute => bail_spanned!(
|
||||
FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => bail_spanned!(
|
||||
text_signature.kw.span() => "text_signature not allowed with this method type"
|
||||
),
|
||||
_ => {}
|
||||
|
@ -389,14 +371,13 @@ impl<'a> FnSpec<'a> {
|
|||
(FnType::ClassAttribute, false, None)
|
||||
}
|
||||
Some(MethodTypeAttribute::New) => {
|
||||
if let Some(name) = &python_name {
|
||||
bail_spanned!(name.span() => "`name` not allowed with `#[new]`");
|
||||
}
|
||||
*python_name = Some(syn::Ident::new("__new__", Span::call_site()));
|
||||
(FnType::FnNew, false, Some(CallingConvention::TpNew))
|
||||
}
|
||||
Some(MethodTypeAttribute::ClassMethod) => (FnType::FnClass, true, None),
|
||||
Some(MethodTypeAttribute::Call) => (
|
||||
FnType::FnCall(parse_receiver("expected receiver for #[call]")?),
|
||||
true,
|
||||
Some(CallingConvention::Varargs),
|
||||
),
|
||||
Some(MethodTypeAttribute::Getter) => {
|
||||
// Strip off "get_" prefix if needed
|
||||
if python_name.is_none() {
|
||||
|
@ -622,6 +603,7 @@ struct MethodAttributes {
|
|||
fn parse_method_attributes(
|
||||
attrs: &mut Vec<syn::Attribute>,
|
||||
mut python_name: Option<syn::Ident>,
|
||||
deprecations: &mut Deprecations,
|
||||
) -> Result<MethodAttributes> {
|
||||
let mut new_attrs = Vec::new();
|
||||
let mut args = Vec::new();
|
||||
|
@ -644,7 +626,12 @@ fn parse_method_attributes(
|
|||
} else if name.is_ident("init") || name.is_ident("__init__") {
|
||||
bail_spanned!(name.span() => "#[init] is disabled since PyO3 0.9.0");
|
||||
} else if name.is_ident("call") || name.is_ident("__call__") {
|
||||
set_ty!(MethodTypeAttribute::Call, name);
|
||||
deprecations.push(Deprecation::CallAttribute, name.span());
|
||||
ensure_spanned!(
|
||||
python_name.is_none(),
|
||||
python_name.span() => "`name` may not be used with `#[call]`"
|
||||
);
|
||||
python_name = Some(syn::Ident::new("__call__", Span::call_site()));
|
||||
} else if name.is_ident("classmethod") {
|
||||
set_ty!(MethodTypeAttribute::ClassMethod, name);
|
||||
} else if name.is_ident("staticmethod") {
|
||||
|
@ -674,7 +661,11 @@ fn parse_method_attributes(
|
|||
} else if path.is_ident("init") {
|
||||
bail_spanned!(path.span() => "#[init] is disabled since PyO3 0.9.0");
|
||||
} else if path.is_ident("call") {
|
||||
set_ty!(MethodTypeAttribute::Call, path);
|
||||
ensure_spanned!(
|
||||
python_name.is_none(),
|
||||
python_name.span() => "`name` may not be used with `#[call]`"
|
||||
);
|
||||
python_name = Some(syn::Ident::new("__call__", Span::call_site()));
|
||||
} else if path.is_ident("setter") || path.is_ident("getter") {
|
||||
if let syn::AttrStyle::Inner(_) = attr.style {
|
||||
bail_spanned!(
|
||||
|
|
|
@ -579,11 +579,6 @@ fn impl_class(
|
|||
let collector = PyClassImplCollector::<Self>::new();
|
||||
collector.free_impl()
|
||||
}
|
||||
fn get_call() -> ::std::option::Option<::pyo3::ffi::PyCFunctionWithKeywords> {
|
||||
use ::pyo3::class::impl_::*;
|
||||
let collector = PyClassImplCollector::<Self>::new();
|
||||
collector.call_impl()
|
||||
}
|
||||
|
||||
fn for_each_proto_slot(visitor: &mut dyn ::std::ops::FnMut(&[::pyo3::ffi::PyType_Slot])) {
|
||||
// Implementation which uses dtolnay specialization to load all slots.
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use crate::attributes::NameAttribute;
|
||||
use crate::method::ExtractErrorMode;
|
||||
use crate::method::{CallingConvention, ExtractErrorMode};
|
||||
use crate::utils::{ensure_not_async_fn, unwrap_ty_group, PythonDoc};
|
||||
use crate::{deprecations::Deprecations, utils};
|
||||
use crate::{
|
||||
|
@ -38,6 +38,8 @@ pub fn gen_py_method(
|
|||
if let Some(slot_def) = pyproto(&method_name) {
|
||||
let slot = slot_def.generate_type_slot(cls, &spec)?;
|
||||
return Ok(GeneratedPyMethod::Proto(slot));
|
||||
} else if method_name == "__call__" {
|
||||
return Ok(GeneratedPyMethod::Proto(impl_call_slot(cls, spec)?));
|
||||
}
|
||||
|
||||
if let Some(slot_fragment_def) = pyproto_fragment(&method_name) {
|
||||
|
@ -60,7 +62,6 @@ pub fn gen_py_method(
|
|||
)?),
|
||||
// special prototypes
|
||||
FnType::FnNew => GeneratedPyMethod::TraitImpl(impl_py_method_def_new(cls, &spec)?),
|
||||
FnType::FnCall(_) => GeneratedPyMethod::TraitImpl(impl_py_method_def_call(cls, &spec)?),
|
||||
FnType::ClassAttribute => GeneratedPyMethod::Method(impl_py_class_attribute(cls, &spec)),
|
||||
FnType::Getter(self_type) => GeneratedPyMethod::Method(impl_py_getter_def(
|
||||
cls,
|
||||
|
@ -136,19 +137,20 @@ fn impl_py_method_def_new(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream>
|
|||
})
|
||||
}
|
||||
|
||||
fn impl_py_method_def_call(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> {
|
||||
fn impl_call_slot(cls: &syn::Type, mut spec: FnSpec) -> Result<TokenStream> {
|
||||
// HACK: __call__ proto slot must always use varargs calling convention, so change the spec.
|
||||
// Probably indicates there's a refactoring opportunity somewhere.
|
||||
spec.convention = CallingConvention::Varargs;
|
||||
|
||||
let wrapper_ident = syn::Ident::new("__wrap", Span::call_site());
|
||||
let wrapper = spec.get_wrapper_function(&wrapper_ident, Some(cls))?;
|
||||
Ok(quote! {
|
||||
impl ::pyo3::class::impl_::PyClassCallImpl<#cls> for ::pyo3::class::impl_::PyClassImplCollector<#cls> {
|
||||
fn call_impl(self) -> ::std::option::Option<::pyo3::ffi::PyCFunctionWithKeywords> {
|
||||
::std::option::Option::Some({
|
||||
#wrapper
|
||||
#wrapper_ident
|
||||
})
|
||||
}
|
||||
Ok(quote! {{
|
||||
#wrapper
|
||||
::pyo3::ffi::PyType_Slot {
|
||||
slot: ::pyo3::ffi::Py_tp_call,
|
||||
pfunc: __wrap as ::pyo3::ffi::ternaryfunc as _
|
||||
}
|
||||
})
|
||||
}})
|
||||
}
|
||||
|
||||
fn impl_py_class_attribute(cls: &syn::Type, spec: &FnSpec) -> TokenStream {
|
||||
|
|
|
@ -133,7 +133,6 @@ pub fn pyclass(attr: TokenStream, input: TokenStream) -> TokenStream {
|
|||
/// | [`#[getter]`][5] and [`#[setter]`][5] | These define getters and setters, similar to Python's `@property` decorator. This is useful for getters/setters that require computation or side effects; if that is not the case consider using [`#[pyo3(get, set)]`][11] on the struct's field(s).|
|
||||
/// | [`#[staticmethod]`][6]| Defines the method as a staticmethod, like Python's `@staticmethod` decorator.|
|
||||
/// | [`#[classmethod]`][7] | Defines the method as a classmethod, like Python's `@classmethod` decorator.|
|
||||
/// | [`#[call]`][8] | Allows Python code to call a class instance as a function, like Python's `__call__` method. |
|
||||
/// | [`#[classattr]`][9] | Defines a class variable. |
|
||||
/// | [`#[args]`][10] | Define a method's default arguments and allows the function to receive `*args` and `**kwargs`. |
|
||||
///
|
||||
|
|
|
@ -71,9 +71,6 @@ pub trait PyClassImpl: Sized {
|
|||
fn get_new() -> Option<ffi::newfunc> {
|
||||
None
|
||||
}
|
||||
fn get_call() -> Option<ffi::PyCFunctionWithKeywords> {
|
||||
None
|
||||
}
|
||||
fn get_alloc() -> Option<ffi::allocfunc> {
|
||||
None
|
||||
}
|
||||
|
@ -98,16 +95,6 @@ impl<T> PyClassNewImpl<T> for &'_ PyClassImplCollector<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait PyClassCallImpl<T> {
|
||||
fn call_impl(self) -> Option<ffi::PyCFunctionWithKeywords>;
|
||||
}
|
||||
|
||||
impl<T> PyClassCallImpl<T> for &'_ PyClassImplCollector<T> {
|
||||
fn call_impl(self) -> Option<ffi::PyCFunctionWithKeywords> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! slot_fragment_trait {
|
||||
($trait_name:ident, $($default_method:tt)*) => {
|
||||
#[allow(non_camel_case_types)]
|
||||
|
|
|
@ -23,3 +23,6 @@ pub const PYMODULE_NAME_ARGUMENT: () = ();
|
|||
note = "use `#[pyo3(text_signature = \"...\")]` instead of `#[text_signature = \"...\"]`"
|
||||
)]
|
||||
pub const TEXT_SIGNATURE_ATTRIBUTE: () = ();
|
||||
|
||||
#[deprecated(since = "0.15.0", note = "use `fn __call__` instead of `#[call]`")]
|
||||
pub const CALL_ATTRIBUTE: () = ();
|
||||
|
|
|
@ -83,10 +83,6 @@ where
|
|||
slots.push(ffi::Py_tp_free, free as _);
|
||||
}
|
||||
|
||||
if let Some(call_meth) = T::get_call() {
|
||||
slots.push(ffi::Py_tp_call, call_meth as _);
|
||||
}
|
||||
|
||||
if cfg!(Py_3_9) {
|
||||
let members = py_class_members::<T>();
|
||||
if !members.is_empty() {
|
||||
|
|
|
@ -363,7 +363,6 @@ impl Dummy {
|
|||
fn staticmethod() {}
|
||||
#[classmethod]
|
||||
fn clsmethod(_: &::pyo3::types::PyType) {}
|
||||
#[call]
|
||||
#[args(args = "*", kwds = "**")]
|
||||
fn __call__(
|
||||
&self,
|
||||
|
|
|
@ -868,8 +868,7 @@ impl r#RawIdents {
|
|||
self.r#subsubtype = r#subsubtype;
|
||||
}
|
||||
|
||||
#[call]
|
||||
pub fn r#call(&mut self, r#type: PyObject) {
|
||||
pub fn r#__call__(&mut self, r#type: PyObject) {
|
||||
self.r#type = r#type;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,8 +20,7 @@ impl PyClassWithMultiplePyMethods {
|
|||
|
||||
#[pymethods]
|
||||
impl PyClassWithMultiplePyMethods {
|
||||
#[call]
|
||||
fn call(&self) -> &'static str {
|
||||
fn __call__(&self) -> &'static str {
|
||||
"call"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -229,32 +229,57 @@ fn iterator() {
|
|||
}
|
||||
|
||||
#[pyclass]
|
||||
struct Callable {}
|
||||
struct Callable;
|
||||
|
||||
#[pymethods]
|
||||
impl Callable {
|
||||
#[__call__]
|
||||
fn __call__(&self, arg: i32) -> i32 {
|
||||
arg * 6
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass]
|
||||
struct EmptyClass;
|
||||
struct NotCallable;
|
||||
|
||||
#[test]
|
||||
fn callable() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
|
||||
let c = Py::new(py, Callable {}).unwrap();
|
||||
let c = Py::new(py, Callable).unwrap();
|
||||
py_assert!(py, c, "callable(c)");
|
||||
py_assert!(py, c, "c(7) == 42");
|
||||
|
||||
let nc = Py::new(py, EmptyClass).unwrap();
|
||||
let nc = Py::new(py, NotCallable).unwrap();
|
||||
py_assert!(py, nc, "not callable(nc)");
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
mod deprecated {
|
||||
use super::*;
|
||||
|
||||
#[pyclass]
|
||||
struct Callable;
|
||||
|
||||
#[pymethods]
|
||||
impl Callable {
|
||||
#[__call__]
|
||||
fn __call__(&self, arg: i32) -> i32 {
|
||||
arg * 6
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn callable() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
|
||||
let c = Py::new(py, Callable).unwrap();
|
||||
py_assert!(py, c, "callable(c)");
|
||||
py_assert!(py, c, "c(7) == 42");
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass]
|
||||
#[derive(Debug)]
|
||||
struct SetItem {
|
||||
|
|
|
@ -6,15 +6,15 @@ error[E0277]: the trait bound `PyDict: PyClass` is not satisfied
|
|||
|
|
||||
= note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict`
|
||||
note: required by a bound in `PyClassBaseType`
|
||||
--> src/class/impl_.rs:775:1
|
||||
--> src/class/impl_.rs:762:1
|
||||
|
|
||||
775 | / pub trait PyClassBaseType: Sized {
|
||||
776 | | type Dict;
|
||||
777 | | type WeakRef;
|
||||
778 | | type LayoutAsBase: PyCellLayout<Self>;
|
||||
762 | / pub trait PyClassBaseType: Sized {
|
||||
763 | | type Dict;
|
||||
764 | | type WeakRef;
|
||||
765 | | type LayoutAsBase: PyCellLayout<Self>;
|
||||
... |
|
||||
781 | | type Initializer: PyObjectInit<Self>;
|
||||
782 | | }
|
||||
768 | | type Initializer: PyObjectInit<Self>;
|
||||
769 | | }
|
||||
| |_^ required by this bound in `PyClassBaseType`
|
||||
= note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
|
@ -26,8 +26,8 @@ error[E0277]: the trait bound `PyDict: PyClass` is not satisfied
|
|||
|
|
||||
= note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict`
|
||||
note: required by a bound in `ThreadCheckerInherited`
|
||||
--> src/class/impl_.rs:762:47
|
||||
--> src/class/impl_.rs:749:47
|
||||
|
|
||||
762 | pub struct ThreadCheckerInherited<T: Send, U: PyClassBaseType>(PhantomData<T>, U::ThreadChecker);
|
||||
749 | pub struct ThreadCheckerInherited<T: Send, U: PyClassBaseType>(PhantomData<T>, U::ThreadChecker);
|
||||
| ^^^^^^^^^^^^^^^ required by this bound in `ThreadCheckerInherited`
|
||||
= note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
|
|
@ -24,6 +24,15 @@ impl TestClass {
|
|||
fn deprecated_name_staticmethod() {}
|
||||
}
|
||||
|
||||
#[pyclass]
|
||||
struct DeprecatedCall;
|
||||
|
||||
#[pymethods]
|
||||
impl DeprecatedCall {
|
||||
#[call]
|
||||
fn deprecated_call(&self) {}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
#[name = "foo"]
|
||||
#[text_signature = "()"]
|
||||
|
|
|
@ -34,34 +34,40 @@ error: use of deprecated constant `pyo3::impl_::deprecations::TEXT_SIGNATURE_ATT
|
|||
23 | #[text_signature = "()"]
|
||||
| ^
|
||||
|
||||
error: use of deprecated constant `pyo3::impl_::deprecations::NAME_ATTRIBUTE`: use `#[pyo3(name = "...")]` instead of `#[name = "..."]`
|
||||
--> tests/ui/deprecations.rs:28:1
|
||||
error: use of deprecated constant `pyo3::impl_::deprecations::CALL_ATTRIBUTE`: use `fn __call__` instead of `#[call]`
|
||||
--> tests/ui/deprecations.rs:32:7
|
||||
|
|
||||
28 | #[name = "foo"]
|
||||
32 | #[call]
|
||||
| ^^^^
|
||||
|
||||
error: use of deprecated constant `pyo3::impl_::deprecations::NAME_ATTRIBUTE`: use `#[pyo3(name = "...")]` instead of `#[name = "..."]`
|
||||
--> tests/ui/deprecations.rs:37:1
|
||||
|
|
||||
37 | #[name = "foo"]
|
||||
| ^
|
||||
|
||||
error: use of deprecated constant `pyo3::impl_::deprecations::TEXT_SIGNATURE_ATTRIBUTE`: use `#[pyo3(text_signature = "...")]` instead of `#[text_signature = "..."]`
|
||||
--> tests/ui/deprecations.rs:29:1
|
||||
--> tests/ui/deprecations.rs:38:1
|
||||
|
|
||||
29 | #[text_signature = "()"]
|
||||
38 | #[text_signature = "()"]
|
||||
| ^
|
||||
|
||||
error: use of deprecated constant `pyo3::impl_::deprecations::PYFN_NAME_ARGUMENT`: use `#[pyfn(m)] #[pyo3(name = "...")]` instead of `#[pyfn(m, "...")]`
|
||||
--> tests/ui/deprecations.rs:34:15
|
||||
--> tests/ui/deprecations.rs:43:15
|
||||
|
|
||||
34 | #[pyfn(m, "some_name")]
|
||||
43 | #[pyfn(m, "some_name")]
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
error: use of deprecated constant `pyo3::impl_::deprecations::TEXT_SIGNATURE_ATTRIBUTE`: use `#[pyo3(text_signature = "...")]` instead of `#[text_signature = "..."]`
|
||||
--> tests/ui/deprecations.rs:35:5
|
||||
--> tests/ui/deprecations.rs:44:5
|
||||
|
|
||||
35 | #[text_signature = "()"]
|
||||
44 | #[text_signature = "()"]
|
||||
| ^
|
||||
|
||||
error: use of deprecated constant `pyo3::impl_::deprecations::PYMODULE_NAME_ARGUMENT`: use `#[pymodule] #[pyo3(name = "...")]` instead of `#[pymodule(...)]`
|
||||
--> tests/ui/deprecations.rs:32:12
|
||||
--> tests/ui/deprecations.rs:41:12
|
||||
|
|
||||
32 | #[pymodule(deprecated_module_name)]
|
||||
41 | #[pymodule(deprecated_module_name)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: use of deprecated constant `pyo3::impl_::deprecations::TEXT_SIGNATURE_ATTRIBUTE`: use `#[pyo3(text_signature = "...")]` instead of `#[text_signature = "..."]`
|
||||
|
|
|
@ -52,12 +52,12 @@ impl MyClass {
|
|||
fn text_signature_on_new() {}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
#[call]
|
||||
#[pyo3(text_signature = "()")]
|
||||
fn text_signature_on_call(&self) {}
|
||||
}
|
||||
// FIXME: this doesn't fail - should refuse text signature on protocol methods in general?
|
||||
// #[pymethods]
|
||||
// impl MyClass {
|
||||
// #[pyo3(text_signature = "()")]
|
||||
// fn __call__(&self) {}
|
||||
// }
|
||||
|
||||
#[pymethods]
|
||||
impl MyClass {
|
||||
|
|
|
@ -40,12 +40,6 @@ error: text_signature not allowed on __new__; if you want to add a signature on
|
|||
51 | #[pyo3(text_signature = "()")]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: text_signature not allowed with this method type
|
||||
--> tests/ui/invalid_pymethods.rs:58:12
|
||||
|
|
||||
58 | #[pyo3(text_signature = "()")]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: text_signature not allowed with this method type
|
||||
--> tests/ui/invalid_pymethods.rs:65:12
|
||||
|
|
||||
|
|
|
@ -11,8 +11,8 @@ note: required because it appears within the type `NotThreadSafe`
|
|||
5 | struct NotThreadSafe {
|
||||
| ^^^^^^^^^^^^^
|
||||
note: required by a bound in `ThreadCheckerStub`
|
||||
--> src/class/impl_.rs:719:33
|
||||
--> src/class/impl_.rs:706:33
|
||||
|
|
||||
719 | pub struct ThreadCheckerStub<T: Send>(PhantomData<T>);
|
||||
706 | pub struct ThreadCheckerStub<T: Send>(PhantomData<T>);
|
||||
| ^^^^ required by this bound in `ThreadCheckerStub`
|
||||
= note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
|
Loading…
Reference in New Issue