diff --git a/CHANGELOG.md b/CHANGELOG.md index 823a7f60..ddbae8e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +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`. [#1285](https://github.com/PyO3/pyo3/pull/1285) +- 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` to `PyResult<&str>`. [#1152](https://github.com/PyO3/pyo3/pull/1152) @@ -30,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,8 +40,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed - 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 FFI definitions for `PyObject_Vectorcall` and `PyVectorcall_Call`. [#1285](https://github.com/PyO3/pyo3/pull/1285) - 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 diff --git a/Cargo.toml b/Cargo.toml index 29a26741..9850271f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } diff --git a/src/instance.rs b/src/instance.rs index 9cfc4002..45d3a169 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -272,7 +272,18 @@ impl Py { /// /// This is equivalent to the Python expression `self()`. pub fn call0(&self, py: Python) -> PyResult { - 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 Py { /// /// This is equivalent to the Python expression `self.name()`. pub fn call_method0(&self, py: Python, name: &str) -> PyResult { - 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` instance by taking ownership of the given FFI pointer. diff --git a/src/types/any.rs b/src/types/any.rs index 1a79aa58..5e8c2ff1 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -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::>(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::() + .unwrap(), + 42 + ); + }) + } + #[test] fn test_type() { let gil = Python::acquire_gil();