Merge pull request #1929 from davidhewitt/call-proto

pymethods: support __call__ proto
This commit is contained in:
David Hewitt 2021-10-23 22:55:30 +01:00 committed by GitHub
commit dc0e29af1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 264 additions and 282 deletions

View File

@ -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

View File

@ -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()
}

View File

@ -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_::*;

View File

@ -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

View File

@ -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)
}

View File

@ -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!(

View File

@ -346,14 +346,21 @@ fn impl_methods_inventory(cls: &syn::Ident) -> TokenStream {
#[doc(hidden)]
pub struct #inventory_cls {
methods: ::std::vec::Vec<::pyo3::class::PyMethodDefType>,
slots: ::std::vec::Vec<::pyo3::ffi::PyType_Slot>,
}
impl ::pyo3::class::impl_::PyMethodsInventory for #inventory_cls {
fn new(methods: ::std::vec::Vec<::pyo3::class::PyMethodDefType>) -> Self {
Self { methods }
fn new(
methods: ::std::vec::Vec<::pyo3::class::PyMethodDefType>,
slots: ::std::vec::Vec<::pyo3::ffi::PyType_Slot>,
) -> Self {
Self { methods, slots }
}
fn get(&'static self) -> &'static [::pyo3::class::PyMethodDefType] {
fn methods(&'static self) -> &'static [::pyo3::class::PyMethodDefType] {
&self.methods
}
fn slots(&'static self) -> &'static [::pyo3::ffi::PyType_Slot] {
&self.slots
}
}
impl ::pyo3::class::impl_::HasMethodsInventory for #cls {
@ -450,15 +457,12 @@ fn impl_class(
};
let (impl_inventory, for_each_py_method) = match methods_type {
PyClassMethodsType::Specialization => (
::std::option::Option::None,
quote! { visitor(collector.py_methods()); },
),
PyClassMethodsType::Specialization => (None, quote! { visitor(collector.py_methods()); }),
PyClassMethodsType::Inventory => (
Some(impl_methods_inventory(cls)),
quote! {
for inventory in ::pyo3::inventory::iter::<<Self as ::pyo3::class::impl_::HasMethodsInventory>::Methods>() {
visitor(::pyo3::class::impl_::PyMethodsInventory::get(inventory));
visitor(::pyo3::class::impl_::PyMethodsInventory::methods(inventory));
}
},
),
@ -466,9 +470,15 @@ fn impl_class(
let methods_protos = match methods_type {
PyClassMethodsType::Specialization => {
Some(quote! { visitor(collector.methods_protocol_slots()); })
quote! { visitor(collector.methods_protocol_slots()); }
}
PyClassMethodsType::Inventory => {
quote! {
for inventory in ::pyo3::inventory::iter::<<Self as ::pyo3::class::impl_::HasMethodsInventory>::Methods>() {
visitor(::pyo3::class::impl_::PyMethodsInventory::slots(inventory));
}
}
}
PyClassMethodsType::Inventory => None,
};
let base = &attr.base;
@ -579,11 +589,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.

View File

@ -85,32 +85,29 @@ pub fn impl_methods(
}
}
let methods_registration = match methods_type {
PyClassMethodsType::Specialization => impl_py_methods(ty, methods),
PyClassMethodsType::Inventory => submit_methods_inventory(ty, methods),
};
add_shared_proto_slots(ty, &mut proto_impls, implemented_proto_fragments);
let protos_registration = match methods_type {
Ok(match methods_type {
PyClassMethodsType::Specialization => {
Some(impl_protos(ty, proto_impls, implemented_proto_fragments))
}
PyClassMethodsType::Inventory => {
if proto_impls.is_empty() {
None
} else {
panic!(
"cannot implement protos in #[pymethods] using `multiple-pymethods` feature"
);
let methods_registration = impl_py_methods(ty, methods);
let protos_registration = impl_protos(ty, proto_impls);
quote! {
#(#trait_impls)*
#protos_registration
#methods_registration
}
}
};
PyClassMethodsType::Inventory => {
let inventory = submit_methods_inventory(ty, methods, proto_impls);
quote! {
#(#trait_impls)*
Ok(quote! {
#(#trait_impls)*
#protos_registration
#methods_registration
#inventory
}
}
})
}
@ -147,11 +144,11 @@ fn impl_py_methods(ty: &syn::Type, methods: Vec<TokenStream>) -> TokenStream {
}
}
fn impl_protos(
fn add_shared_proto_slots(
ty: &syn::Type,
mut proto_impls: Vec<TokenStream>,
proto_impls: &mut Vec<TokenStream>,
mut implemented_proto_fragments: HashSet<String>,
) -> TokenStream {
) {
macro_rules! try_add_shared_slot {
($first:literal, $second:literal, $slot:ident) => {{
let first_implemented = implemented_proto_fragments.remove($first);
@ -184,6 +181,10 @@ fn impl_protos(
);
try_add_shared_slot!("__pow__", "__rpow__", generate_pyclass_pow_slot);
assert!(implemented_proto_fragments.is_empty());
}
fn impl_protos(ty: &syn::Type, proto_impls: Vec<TokenStream>) -> TokenStream {
quote! {
impl ::pyo3::class::impl_::PyMethodsProtocolSlots<#ty>
for ::pyo3::class::impl_::PyClassImplCollector<#ty>
@ -195,16 +196,16 @@ fn impl_protos(
}
}
fn submit_methods_inventory(ty: &syn::Type, methods: Vec<TokenStream>) -> TokenStream {
if methods.is_empty() {
return TokenStream::default();
}
fn submit_methods_inventory(
ty: &syn::Type,
methods: Vec<TokenStream>,
proto_impls: Vec<TokenStream>,
) -> TokenStream {
quote! {
::pyo3::inventory::submit! {
#![crate = ::pyo3] {
type Inventory = <#ty as ::pyo3::class::impl_::HasMethodsInventory>::Methods;
<Inventory as ::pyo3::class::impl_::PyMethodsInventory>::new(::std::vec![#(#methods),*])
<Inventory as ::pyo3::class::impl_::PyMethodsInventory>::new(::std::vec![#(#methods),*], ::std::vec![#(#proto_impls),*])
}
}
}

View File

@ -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 {

View File

@ -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`. |
///

View File

@ -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)]
@ -625,10 +612,13 @@ macro_rules! methods_trait {
#[cfg(all(feature = "macros", feature = "multiple-pymethods"))]
pub trait PyMethodsInventory: inventory::Collect {
/// Create a new instance
fn new(methods: Vec<PyMethodDefType>) -> Self;
fn new(methods: Vec<PyMethodDefType>, slots: Vec<ffi::PyType_Slot>) -> Self;
/// Returns the methods for a single `#[pymethods] impl` block
fn get(&'static self) -> &'static [PyMethodDefType];
fn methods(&'static self) -> &'static [PyMethodDefType];
/// Returns the slots for a single `#[pymethods] impl` block
fn slots(&'static self) -> &'static [ffi::PyType_Slot];
}
/// Implemented for `#[pyclass]` in our proc macro code.
@ -676,6 +666,7 @@ slots_trait!(PyAsyncProtocolSlots, async_protocol_slots);
slots_trait!(PySequenceProtocolSlots, sequence_protocol_slots);
slots_trait!(PyBufferProtocolSlots, buffer_protocol_slots);
// Protocol slots from #[pymethods] if not using inventory.
#[cfg(not(feature = "multiple-pymethods"))]
slots_trait!(PyMethodsProtocolSlots, methods_protocol_slots);

View File

@ -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: () = ();

View File

@ -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() {

View File

@ -363,7 +363,6 @@ impl Dummy {
fn staticmethod() {}
#[classmethod]
fn clsmethod(_: &::pyo3::types::PyType) {}
#[call]
#[args(args = "*", kwds = "**")]
fn __call__(
&self,

View File

@ -1,5 +1,3 @@
#![cfg(not(feature = "multiple-pymethods"))]
use pyo3::class::basic::CompareOp;
use pyo3::prelude::*;
use pyo3::py_run;

View File

@ -2,8 +2,6 @@ mod hygiene {
mod misc;
mod pyclass;
mod pyfunction;
// cannot implement protos in #[pymethods] using `multiple-pymethods` feature
#[cfg(not(feature = "multiple-pymethods"))]
mod pymethods;
mod pymodule;
mod pyproto;

View File

@ -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;
}

View File

@ -20,8 +20,7 @@ impl PyClassWithMultiplePyMethods {
#[pymethods]
impl PyClassWithMultiplePyMethods {
#[call]
fn call(&self) -> &'static str {
fn __call__(&self) -> &'static str {
"call"
}
}

View File

@ -1,5 +1,3 @@
#![cfg(not(feature = "multiple-pymethods"))]
use pyo3::exceptions::PyValueError;
use pyo3::types::{PySlice, PyType};
use pyo3::{exceptions::PyAttributeError, prelude::*};
@ -229,32 +227,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 {

View File

@ -210,30 +210,6 @@ fn sequence() {
py_expect_exception!(py, c, "c['abc']", PyTypeError);
}
#[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");
let nc = Py::new(py, Comparisons { val: 0 }).unwrap();
py_assert!(py, nc, "not callable(nc)");
}
#[pyclass]
#[derive(Debug)]
struct SetItem {

View File

@ -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:766:1
|
775 | / pub trait PyClassBaseType: Sized {
776 | | type Dict;
777 | | type WeakRef;
778 | | type LayoutAsBase: PyCellLayout<Self>;
766 | / pub trait PyClassBaseType: Sized {
767 | | type Dict;
768 | | type WeakRef;
769 | | type LayoutAsBase: PyCellLayout<Self>;
... |
781 | | type Initializer: PyObjectInit<Self>;
782 | | }
772 | | type Initializer: PyObjectInit<Self>;
773 | | }
| |_^ 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:753:47
|
762 | pub struct ThreadCheckerInherited<T: Send, U: PyClassBaseType>(PhantomData<T>, U::ThreadChecker);
753 | 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)

View File

@ -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 = "()"]

View File

@ -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 = "..."]`

View File

@ -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 {

View File

@ -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
|

View File

@ -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:710:33
|
719 | pub struct ThreadCheckerStub<T: Send>(PhantomData<T>);
710 | 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)