Merge pull request #1287 from davidhewitt/vectorcall

vectorcall: fix bindings & use for call0 and call_method0
This commit is contained in:
Yuji Kanagawa 2020-11-26 18:18:59 +09:00 committed by GitHub
commit 47a731bd5d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 463 additions and 112 deletions

View file

@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Add context.h functions (`PyContext_New`, etc) to FFI. [#1259](https://github.com/PyO3/pyo3/pull/1259)
- Add `PyAny::is_instance()` method. [#1276](https://github.com/PyO3/pyo3/pull/1276)
- Add support for conversion between `char` and `PyString`. [#1282](https://github.com/PyO3/pyo3/pull/1282)
- Add FFI definitions for `PyBuffer_SizeFromFormat`, `PyObject_LengthHint`, `PyObject_CallNoArgs`, `PyObject_CallOneArg`, `PyObject_CallMethodNoArgs`, `PyObject_CallMethodOneArg`, `PyObject_VectorcallDict`, and `PyObject_VectorcallMethod`. [#1287](https://github.com/PyO3/pyo3/pull/1287)
### Changed
- Change return type `PyType::name()` from `Cow<str>` to `PyResult<&str>`. [#1152](https://github.com/PyO3/pyo3/pull/1152)
@ -29,6 +30,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Change `Debug` and `Display` impls for `PyException` to be consistent with `PyAny`. [#1275](https://github.com/PyO3/pyo3/pull/1275)
- Change `Debug` impl of `PyErr` to output more helpful information (acquiring the GIL if necessary). [#1275](https://github.com/PyO3/pyo3/pull/1275)
- Rename `PyTypeInfo::is_instance` and `PyTypeInfo::is_exact_instance` to `PyTypeInfo::is_type_of` and `PyTypeInfo::is_exact_type_of`. [#1278](https://github.com/PyO3/pyo3/pull/1278)
- Optimize `PyAny::call0`, `Py::call0` and `PyAny::call_method0` and `Py::call_method0` on Python 3.9 and up. [#1287](https://github.com/PyO3/pyo3/pull/1285)
- Deprecate `Python::is_instance`, `Python::is_subclass`, `Python::release`, and `Python::xdecref`. [#1292](https://github.com/PyO3/pyo3/pull/1292)
### Removed
@ -39,6 +41,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Fix missing field in `PyCodeObject` struct (`co_posonlyargcount`) - caused invalid access to other fields in Python >3.7. [#1260](https://github.com/PyO3/pyo3/pull/1260)
- Fix building for `x86_64-unknown-linux-musl` target from `x86_65-unknown-linux-gnu` host. [#1267](https://github.com/PyO3/pyo3/pull/1267)
- Fix `#[text_signature]` interacting badly with rust `r#raw_identifiers`. [#1286](https://github.com/PyO3/pyo3/pull/1286)
- Fix FFI definitions for `PyObject_Vectorcall` and `PyVectorcall_Call`. [#1287](https://github.com/PyO3/pyo3/pull/1285)
- Fix building with Anaconda python inside a virtualenv. [#1290](https://github.com/PyO3/pyo3/pull/1290)
## [0.12.3] - 2020-10-12

View file

@ -15,6 +15,7 @@ build = "build.rs"
edition = "2018"
[dependencies]
cfg-if = { version = "1.0" }
ctor = { version = "0.1", optional = true }
indoc = { version = "1.0.3", optional = true }
inventory = { version = "0.1.4", optional = true }

53
benches/bench_call.rs Normal file
View file

@ -0,0 +1,53 @@
#![feature(test)]
extern crate test;
use pyo3::prelude::*;
use test::Bencher;
macro_rules! test_module {
($py:ident, $code:literal) => {
PyModule::from_code($py, indoc::indoc!($code), file!(), "test_module")
.expect("module creation failed")
};
}
#[bench]
fn bench_call_0(b: &mut Bencher) {
Python::with_gil(|py| {
let module = test_module!(
py,
r#"
def foo(): pass
"#
);
let foo = module.getattr("foo").unwrap();
b.iter(|| {
for _ in 0..1000 {
foo.call0().unwrap();
}
});
})
}
#[bench]
fn bench_call_method_0(b: &mut Bencher) {
Python::with_gil(|py| {
let module = test_module!(
py,
r#"
class Foo:
def foo(self): pass
"#
);
let foo = module.getattr("Foo").unwrap().call0().unwrap();
b.iter(|| {
for _ in 0..1000 {
foo.call_method0("foo").unwrap();
}
});
})
}

View file

@ -0,0 +1,306 @@
use crate::ffi::{PyObject, Py_TYPE, Py_buffer, Py_ssize_t};
use libc::{c_char, c_int, c_void};
#[cfg(all(Py_3_8, not(PyPy)))]
use crate::ffi::{
vectorcallfunc, PyCallable_Check, PyThreadState, PyThreadState_GET, PyTuple_Check,
PyType_HasFeature, Py_TPFLAGS_HAVE_VECTORCALL,
};
#[cfg(all(Py_3_8, not(PyPy)))]
use libc::size_t;
extern "C" {
#[cfg(all(Py_3_8, not(PyPy)))]
pub fn _PyStack_AsDict(values: *const *mut PyObject, kwnames: *mut PyObject) -> *mut PyObject;
}
#[cfg(all(Py_3_8, not(PyPy)))]
const _PY_FASTCALL_SMALL_STACK: size_t = 5;
extern "C" {
#[cfg(all(Py_3_8, not(PyPy)))]
pub fn _Py_CheckFunctionResult(
tstate: *mut PyThreadState,
callable: *mut PyObject,
result: *mut PyObject,
where_: *const c_char,
) -> *mut PyObject;
#[cfg(all(Py_3_8, not(PyPy)))]
pub fn _PyObject_MakeTpCall(
tstate: *mut PyThreadState,
callable: *mut PyObject,
args: *const *mut PyObject,
nargs: Py_ssize_t,
keywords: *mut PyObject,
) -> *mut PyObject;
}
#[cfg(all(Py_3_8, not(PyPy)))]
const PY_VECTORCALL_ARGUMENTS_OFFSET: Py_ssize_t =
1 << (8 * std::mem::size_of::<Py_ssize_t>() as Py_ssize_t - 1);
#[cfg(all(Py_3_8, not(PyPy)))]
#[inline(always)]
pub unsafe fn PyVectorcall_NARGS(n: size_t) -> Py_ssize_t {
assert!(n <= (Py_ssize_t::MAX as size_t));
(n as Py_ssize_t) & !PY_VECTORCALL_ARGUMENTS_OFFSET
}
#[cfg(all(Py_3_8, not(PyPy)))]
#[inline(always)]
pub unsafe fn PyVectorcall_Function(callable: *mut PyObject) -> Option<vectorcallfunc> {
assert!(!callable.is_null());
let tp = Py_TYPE(callable);
if PyType_HasFeature(tp, Py_TPFLAGS_HAVE_VECTORCALL) == 0 {
return None;
}
assert!(PyCallable_Check(callable) > 0);
let offset = (*tp).tp_vectorcall_offset;
assert!(offset > 0);
let ptr = (callable as *const c_char).offset(offset) as *const Option<vectorcallfunc>;
*ptr
}
#[cfg(all(Py_3_8, not(PyPy)))]
#[inline(always)]
pub unsafe fn _PyObject_VectorcallTstate(
tstate: *mut PyThreadState,
callable: *mut PyObject,
args: *const *mut PyObject,
nargsf: size_t,
kwnames: *mut PyObject,
) -> *mut PyObject {
assert!(kwnames.is_null() || PyTuple_Check(kwnames) > 0);
assert!(!args.is_null() || PyVectorcall_NARGS(nargsf) == 0);
match PyVectorcall_Function(callable) {
None => {
let nargs = PyVectorcall_NARGS(nargsf);
_PyObject_MakeTpCall(tstate, callable, args, nargs, kwnames)
}
Some(func) => {
let res = func(callable, args, nargsf, kwnames);
_Py_CheckFunctionResult(tstate, callable, res, std::ptr::null_mut())
}
}
}
#[cfg(all(Py_3_8, not(PyPy)))]
#[inline(always)]
pub unsafe fn PyObject_Vectorcall(
callable: *mut PyObject,
args: *const *mut PyObject,
nargsf: size_t,
kwnames: *mut PyObject,
) -> *mut PyObject {
_PyObject_VectorcallTstate(PyThreadState_GET(), callable, args, nargsf, kwnames)
}
extern "C" {
#[cfg(all(Py_3_8, not(PyPy)))]
#[cfg_attr(not(Py_3_9), link_name = "_PyObject_VectorcallDict")]
pub fn PyObject_VectorcallDict(
callable: *mut PyObject,
args: *const *mut PyObject,
nargsf: size_t,
kwargs: *mut PyObject,
) -> *mut PyObject;
#[cfg(all(Py_3_8, not(PyPy)))]
#[cfg_attr(not(Py_3_9), link_name = "_PyVectorcall_Call")]
pub fn PyVectorcall_Call(
callable: *mut PyObject,
tuple: *mut PyObject,
dict: *mut PyObject,
) -> *mut PyObject;
}
#[cfg(all(Py_3_8, not(PyPy)))]
#[inline(always)]
pub unsafe fn _PyObject_FastCallTstate(
tstate: *mut PyThreadState,
func: *mut PyObject,
args: *const *mut PyObject,
nargs: Py_ssize_t,
) -> *mut PyObject {
_PyObject_VectorcallTstate(tstate, func, args, nargs as size_t, std::ptr::null_mut())
}
#[cfg(all(Py_3_8, not(PyPy)))]
#[inline(always)]
pub unsafe fn _PyObject_FastCall(
func: *mut PyObject,
args: *const *mut PyObject,
nargs: Py_ssize_t,
) -> *mut PyObject {
_PyObject_FastCallTstate(PyThreadState_GET(), func, args, nargs)
}
#[cfg(all(Py_3_8, not(PyPy)))]
#[inline(always)]
pub unsafe fn _PyObject_CallNoArg(func: *mut PyObject) -> *mut PyObject {
_PyObject_VectorcallTstate(
PyThreadState_GET(),
func,
std::ptr::null_mut(),
0,
std::ptr::null_mut(),
)
}
#[cfg(all(Py_3_8, not(PyPy)))]
#[inline(always)]
pub unsafe fn PyObject_CallOneArg(func: *mut PyObject, arg: *mut PyObject) -> *mut PyObject {
assert!(!arg.is_null());
let _args = [std::ptr::null_mut(), arg];
let args = _args.as_ptr().offset(1); // For PY_VECTORCALL_ARGUMENTS_OFFSET
let tstate = PyThreadState_GET();
let nargsf = 1 | PY_VECTORCALL_ARGUMENTS_OFFSET;
_PyObject_VectorcallTstate(tstate, func, args, nargsf as size_t, std::ptr::null_mut())
}
extern "C" {
#[cfg(all(Py_3_9, not(PyPy)))]
pub fn PyObject_VectorcallMethod(
name: *mut PyObject,
args: *const *mut PyObject,
nargsf: size_t,
kwnames: *mut PyObject,
) -> *mut PyObject;
}
#[cfg(all(Py_3_9, not(PyPy)))]
#[inline(always)]
pub unsafe fn PyObject_CallMethodNoArgs(
self_: *mut PyObject,
name: *mut PyObject,
) -> *mut PyObject {
PyObject_VectorcallMethod(
name,
&self_,
1 | PY_VECTORCALL_ARGUMENTS_OFFSET as size_t,
std::ptr::null_mut(),
)
}
#[cfg(all(Py_3_9, not(PyPy)))]
#[inline(always)]
pub unsafe fn PyObject_CallMethodOneArg(
self_: *mut PyObject,
name: *mut PyObject,
arg: *mut PyObject,
) -> *mut PyObject {
let args = [self_, arg];
assert!(!arg.is_null());
PyObject_VectorcallMethod(
name,
args.as_ptr(),
2 | PY_VECTORCALL_ARGUMENTS_OFFSET as size_t,
std::ptr::null_mut(),
)
}
// skipped _PyObject_VectorcallMethodId
// skipped _PyObject_CallMethodIdNoArgs
// skipped _PyObject_CallMethodIdOneArg
// skipped _PyObject_HasLen
extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyObject_LengthHint")]
pub fn PyObject_LengthHint(o: *mut PyObject, arg1: Py_ssize_t) -> Py_ssize_t;
#[cfg(all(Py_3_9, not(PyPy)))]
pub fn PyObject_CheckBuffer(obj: *mut PyObject) -> c_int;
}
#[cfg(not(any(Py_3_9, PyPy)))]
#[inline]
pub unsafe fn PyObject_CheckBuffer(o: *mut PyObject) -> c_int {
let tp_as_buffer = (*Py_TYPE(o)).tp_as_buffer;
(!tp_as_buffer.is_null() && (*tp_as_buffer).bf_getbuffer.is_some()) as c_int
}
extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyObject_GetBuffer")]
pub fn PyObject_GetBuffer(obj: *mut PyObject, view: *mut Py_buffer, flags: c_int) -> c_int;
#[cfg_attr(PyPy, link_name = "PyPyBuffer_GetPointer")]
pub fn PyBuffer_GetPointer(view: *mut Py_buffer, indices: *mut Py_ssize_t) -> *mut c_void;
#[cfg_attr(PyPy, link_name = "PyPyBuffer_SizeFromFormat")]
pub fn PyBuffer_SizeFromFormat(format: *const c_char) -> Py_ssize_t;
#[cfg_attr(PyPy, link_name = "PyPyBuffer_ToContiguous")]
pub fn PyBuffer_ToContiguous(
buf: *mut c_void,
view: *mut Py_buffer,
len: Py_ssize_t,
order: c_char,
) -> c_int;
#[cfg_attr(PyPy, link_name = "PyPyBuffer_FromContiguous")]
pub fn PyBuffer_FromContiguous(
view: *mut Py_buffer,
buf: *mut c_void,
len: Py_ssize_t,
order: c_char,
) -> c_int;
pub fn PyObject_CopyData(dest: *mut PyObject, src: *mut PyObject) -> c_int;
#[cfg_attr(PyPy, link_name = "PyPyBuffer_IsContiguous")]
pub fn PyBuffer_IsContiguous(view: *const Py_buffer, fort: c_char) -> c_int;
pub fn PyBuffer_FillContiguousStrides(
ndims: c_int,
shape: *mut Py_ssize_t,
strides: *mut Py_ssize_t,
itemsize: c_int,
fort: c_char,
);
#[cfg_attr(PyPy, link_name = "PyPyBuffer_FillInfo")]
pub fn PyBuffer_FillInfo(
view: *mut Py_buffer,
o: *mut PyObject,
buf: *mut c_void,
len: Py_ssize_t,
readonly: c_int,
flags: c_int,
) -> c_int;
#[cfg_attr(PyPy, link_name = "PyPyBuffer_Release")]
pub fn PyBuffer_Release(view: *mut Py_buffer);
}
#[inline]
pub unsafe fn PyIter_Check(o: *mut PyObject) -> c_int {
(match (*Py_TYPE(o)).tp_iternext {
Some(tp_iternext) => {
tp_iternext as *const c_void != crate::ffi::object::_PyObject_NextNotImplemented as _
}
None => false,
}) as c_int
}
// skipped PySequence_ITEM
pub const PY_ITERSEARCH_COUNT: c_int = 1;
pub const PY_ITERSEARCH_INDEX: c_int = 2;
pub const PY_ITERSEARCH_CONTAINS: c_int = 3;
extern "C" {
#[cfg(not(PyPy))]
pub fn _PySequence_IterSearch(
seq: *mut PyObject,
obj: *mut PyObject,
operation: c_int,
) -> Py_ssize_t;
}
// skipped _PyObject_RealIsInstance
// skipped _PyObject_RealIsSubclass
// skipped _PySequence_BytesToCharpArray
// skipped _Py_FreeCharPArray
// skipped _Py_add_one_to_index_F
// skipped _Py_add_one_to_index_C
// skipped _Py_convert_optional_to_ssize_t
// skipped _PyNumber_Index(*mut PyObject o)

3
src/ffi/cpython/mod.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod abstract_;
pub use self::abstract_::*;

View file

@ -16,40 +16,6 @@ pub unsafe fn PyCFunction_Check(op: *mut PyObject) -> c_int {
pub type PyCFunction =
unsafe extern "C" fn(slf: *mut PyObject, args: *mut PyObject) -> *mut PyObject;
// TODO(davidhewitt)[1283] - Fix this definition
// #[cfg(all(Py_3_8, not(Py_LIMITED_API)))]
// #[cfg_attr(Py_3_8, link_name = "_PyObject_Vectorcall")]
// pub type PyObject_Vectorcall = unsafe extern "C" fn(
// slf: *mut PyObject,
// // positional and keyword arguments
// args: *const *mut PyObject,
// // number of position arguments in args, after which values are kwargs
// nargs: crate::ffi::pyport::Py_ssize_t,
// // tuple of kwargs, if given, or null
// kwnames: *mut PyObject,
// ) -> *mut PyObject;
// TODO(davidhewitt)[1283] - Fix this definition
// #[cfg(all(Py_3_8, not(Py_LIMITED_API)))]
// #[cfg_attr(Py_3_8, link_name = "PyVectorcall_Call")]
// pub type PyVectorcall_Call = unsafe extern "C" fn(
// obj: *mut PyObject,
// tuple: *mut PyObject,
// dict: *mut PyObject,
// ) -> *mut PyObject;
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
const PY_VECTORCALL_ARGUMENTS_OFFSET: crate::ffi::pyport::Py_ssize_t =
1 << (8 * std::mem::size_of::<usize>() - 1);
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
#[inline(always)]
pub unsafe fn PyVectorcall_NARGS(
n: crate::ffi::pyport::Py_ssize_t,
) -> crate::ffi::pyport::Py_ssize_t {
n & !PY_VECTORCALL_ARGUMENTS_OFFSET
}
#[cfg(all(Py_3_7, not(Py_LIMITED_API)))]
pub type _PyCFunctionFast = unsafe extern "C" fn(
slf: *mut PyObject,

View file

@ -61,6 +61,9 @@ pub use self::unicodeobject::*;
pub use self::warnings::*;
pub use self::weakrefobject::*;
#[cfg(not(Py_LIMITED_API))]
pub use self::cpython::*;
mod pyport;
// mod pymacro; contains nothing of interest for Rust
// mod pyatomic; contains nothing of interest for Rust
@ -169,3 +172,6 @@ pub(crate) mod datetime;
pub(crate) mod marshal;
pub(crate) mod funcobject;
#[cfg(not(Py_LIMITED_API))]
mod cpython;

View file

@ -758,7 +758,7 @@ pub const Py_TPFLAGS_BASETYPE: c_ulong = 1 << 10;
/// Set if the type implements the vectorcall protocol (PEP 590)
#[cfg(all(Py_3_8, not(Py_LIMITED_API)))]
pub const _Py_TPFLAGS_HAVE_VECTORCALL: c_ulong = 1 << 11;
pub const Py_TPFLAGS_HAVE_VECTORCALL: c_ulong = 1 << 11;
/// Set if the type is 'ready' -- fully initialized
pub const Py_TPFLAGS_READY: c_ulong = 1 << 12;

View file

@ -1,6 +1,6 @@
use crate::ffi::object::*;
use crate::ffi::pyport::Py_ssize_t;
use std::os::raw::{c_char, c_int, c_void};
use std::os::raw::{c_char, c_int};
use std::ptr;
#[inline]
@ -16,6 +16,11 @@ pub unsafe fn PyObject_DelAttr(o: *mut PyObject, attr_name: *mut PyObject) -> c_
}
extern "C" {
#[cfg(all(
not(PyPy),
any(not(Py_LIMITED_API), Py_3_9) // Added to limited API in 3.9
))]
pub fn PyObject_CallNoArgs(func: *mut PyObject) -> *mut PyObject;
#[cfg_attr(PyPy, link_name = "PyPyObject_Call")]
pub fn PyObject_Call(
callable_object: *mut PyObject,
@ -61,10 +66,6 @@ pub unsafe fn PyObject_Length(o: *mut PyObject) -> Py_ssize_t {
}
extern "C" {
#[cfg(not(Py_LIMITED_API))]
#[cfg_attr(PyPy, link_name = "PyPyObject_LengthHint")]
pub fn PyObject_LengthHint(o: *mut PyObject, arg1: Py_ssize_t) -> Py_ssize_t;
#[cfg_attr(PyPy, link_name = "PyPyObject_GetItem")]
pub fn PyObject_GetItem(o: *mut PyObject, key: *mut PyObject) -> *mut PyObject;
#[cfg_attr(PyPy, link_name = "PyPyObject_SetItem")]
@ -73,78 +74,15 @@ extern "C" {
pub fn PyObject_DelItem(o: *mut PyObject, key: *mut PyObject) -> c_int;
}
#[cfg(not(Py_LIMITED_API))]
#[inline]
pub unsafe fn PyObject_CheckBuffer(o: *mut PyObject) -> c_int {
let tp_as_buffer = (*Py_TYPE(o)).tp_as_buffer;
(!tp_as_buffer.is_null() && (*tp_as_buffer).bf_getbuffer.is_some()) as c_int
}
#[cfg(not(Py_LIMITED_API))]
extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyObject_GetBuffer")]
pub fn PyObject_GetBuffer(obj: *mut PyObject, view: *mut Py_buffer, flags: c_int) -> c_int;
#[cfg_attr(PyPy, link_name = "PyPyBuffer_GetPointer")]
pub fn PyBuffer_GetPointer(view: *mut Py_buffer, indices: *mut Py_ssize_t) -> *mut c_void;
#[cfg_attr(PyPy, link_name = "PyPyBuffer_ToContiguous")]
pub fn PyBuffer_ToContiguous(
buf: *mut c_void,
view: *mut Py_buffer,
len: Py_ssize_t,
order: c_char,
) -> c_int;
#[cfg_attr(PyPy, link_name = "PyPyBuffer_FromContiguous")]
pub fn PyBuffer_FromContiguous(
view: *mut Py_buffer,
buf: *mut c_void,
len: Py_ssize_t,
order: c_char,
) -> c_int;
pub fn PyObject_CopyData(dest: *mut PyObject, src: *mut PyObject) -> c_int;
#[cfg_attr(PyPy, link_name = "PyPyBuffer_IsContiguous")]
pub fn PyBuffer_IsContiguous(view: *const Py_buffer, fort: c_char) -> c_int;
pub fn PyBuffer_FillContiguousStrides(
ndims: c_int,
shape: *mut Py_ssize_t,
strides: *mut Py_ssize_t,
itemsize: c_int,
fort: c_char,
);
#[cfg_attr(PyPy, link_name = "PyPyBuffer_FillInfo")]
pub fn PyBuffer_FillInfo(
view: *mut Py_buffer,
o: *mut PyObject,
buf: *mut c_void,
len: Py_ssize_t,
readonly: c_int,
flags: c_int,
) -> c_int;
#[cfg_attr(PyPy, link_name = "PyPyBuffer_Release")]
pub fn PyBuffer_Release(view: *mut Py_buffer);
}
extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyObject_Format")]
pub fn PyObject_Format(obj: *mut PyObject, format_spec: *mut PyObject) -> *mut PyObject;
#[cfg_attr(PyPy, link_name = "PyPyObject_GetIter")]
pub fn PyObject_GetIter(arg1: *mut PyObject) -> *mut PyObject;
}
#[cfg(not(Py_LIMITED_API))]
#[inline]
#[cfg_attr(PyPy, link_name = "PyPyIter_Check")]
pub unsafe fn PyIter_Check(o: *mut PyObject) -> c_int {
(match (*Py_TYPE(o)).tp_iternext {
Some(tp_iternext) => {
tp_iternext as *const c_void
!= crate::ffi::object::_PyObject_NextNotImplemented as *const c_void
}
None => false,
}) as c_int
}
#[cfg(all(Py_LIMITED_API, Py_3_8))]
extern "C" {
// PyIter_Check for unlimited API is in cpython/abstract_.rs
#[cfg(all(Py_LIMITED_API, Py_3_8))]
#[cfg_attr(PyPy, link_name = "PyPyIter_Check")]
pub fn PyIter_Check(obj: *mut PyObject) -> c_int;
}

View file

@ -272,7 +272,18 @@ impl<T> Py<T> {
///
/// This is equivalent to the Python expression `self()`.
pub fn call0(&self, py: Python) -> PyResult<PyObject> {
self.call(py, (), None)
cfg_if::cfg_if! {
// TODO: Use PyObject_CallNoArgs instead after https://bugs.python.org/issue42415.
// Once the issue is resolved, we can enable this optimization for limited API.
if #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] {
// Optimized path on python 3.9+
unsafe {
PyObject::from_owned_ptr_or_err(py, ffi::_PyObject_CallNoArg(self.as_ptr()))
}
} else {
self.call(py, (), None)
}
}
}
/// Calls a method on the object.
@ -316,7 +327,17 @@ impl<T> Py<T> {
///
/// This is equivalent to the Python expression `self.name()`.
pub fn call_method0(&self, py: Python, name: &str) -> PyResult<PyObject> {
self.call_method(py, name, (), None)
cfg_if::cfg_if! {
if #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] {
// Optimized path on python 3.9+
unsafe {
let name = name.into_py(py);
PyObject::from_owned_ptr_or_err(py, ffi::PyObject_CallMethodNoArgs(self.as_ptr(), name.as_ptr()))
}
} else {
self.call_method(py, name, (), None)
}
}
}
/// Create a `Py<T>` instance by taking ownership of the given FFI pointer.

View file

@ -226,7 +226,18 @@ impl PyAny {
///
/// This is equivalent to the Python expression `self()`.
pub fn call0(&self) -> PyResult<&PyAny> {
self.call((), None)
cfg_if::cfg_if! {
// TODO: Use PyObject_CallNoArgs instead after https://bugs.python.org/issue42415.
// Once the issue is resolved, we can enable this optimization for limited API.
if #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] {
// Optimized path on python 3.9+
unsafe {
self.py().from_owned_ptr_or_err(ffi::_PyObject_CallNoArg(self.as_ptr()))
}
} else {
self.call((), None)
}
}
}
/// Calls the object with only positional arguments.
@ -283,7 +294,17 @@ impl PyAny {
///
/// This is equivalent to the Python expression `self.name()`.
pub fn call_method0(&self, name: &str) -> PyResult<&PyAny> {
self.call_method(name, (), None)
cfg_if::cfg_if! {
if #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] {
// Optimized path on python 3.9+
unsafe {
let name = name.into_py(self.py());
self.py().from_owned_ptr_or_err(ffi::PyObject_CallMethodNoArgs(self.as_ptr(), name.as_ptr()))
}
} else {
self.call_method(name, (), None)
}
}
}
/// Calls a method on the object with only positional arguments.
@ -464,8 +485,17 @@ impl PyAny {
#[cfg(test)]
mod test {
use crate::types::{IntoPyDict, PyList, PyLong};
use crate::{Python, ToPyObject};
use crate::{
types::{IntoPyDict, PyList, PyLong, PyModule},
Python, ToPyObject,
};
macro_rules! test_module {
($py:ident, $code:literal) => {
PyModule::from_code($py, indoc::indoc!($code), file!(), "test_module")
.expect("module creation failed")
};
}
#[test]
fn test_call_for_non_existing_method() {
@ -488,6 +518,30 @@ mod test {
assert_eq!(list.extract::<Vec<i32>>(py).unwrap(), vec![7, 6, 5, 4, 3]);
}
#[test]
fn test_call_method0() {
Python::with_gil(|py| {
let module = test_module!(
py,
r#"
class SimpleClass:
def foo(self):
return 42
"#
);
let simple_class = module.getattr("SimpleClass").unwrap().call0().unwrap();
assert_eq!(
simple_class
.call_method0("foo")
.unwrap()
.extract::<u32>()
.unwrap(),
42
);
})
}
#[test]
fn test_type() {
let gil = Python::acquire_gil();