Allow more methods to take interned arguments (#2312)

* Allow more methods to take interned arguments

* Changelog

* Unify name bounds

* Resolve merge conflict

* reduce use of py_decref

* Add some attr tests

* Update migration
This commit is contained in:
Bruno Kolenbrander 2022-05-02 11:13:15 +02:00 committed by GitHub
parent 97db563253
commit dce4377eb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 370 additions and 187 deletions

View File

@ -14,10 +14,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Several methods of `Py` and `PyAny` now accept `impl IntoPy<Py<PyString>>` rather than just `&str` to allow use of the `intern!` macro. [#2312](https://github.com/PyO3/pyo3/pull/2312)
- Move `PyTypeObject::type_object` method to `PyTypeInfo` trait, and deprecate `PyTypeObject` trait. [#2287](https://github.com/PyO3/pyo3/pull/2287) - Move `PyTypeObject::type_object` method to `PyTypeInfo` trait, and deprecate `PyTypeObject` trait. [#2287](https://github.com/PyO3/pyo3/pull/2287)
- The deprecated `pyproto` feature is now disabled by default. [#2322](https://github.com/PyO3/pyo3/pull/2322) - The deprecated `pyproto` feature is now disabled by default. [#2322](https://github.com/PyO3/pyo3/pull/2322)
- Deprecate `ToBorrowedObject` trait (it is only used as a wrapper for `ToPyObject`). [#2333](https://github.com/PyO3/pyo3/pull/2333) - Deprecate `ToBorrowedObject` trait (it is only used as a wrapper for `ToPyObject`). [#2333](https://github.com/PyO3/pyo3/pull/2333)
## [0.16.4] - 2022-04-14 ## [0.16.4] - 2022-04-14
### Added ### Added

View File

@ -5,6 +5,35 @@ For a detailed list of all changes, see the [CHANGELOG](changelog.md).
## from 0.16.* to 0.17 ## from 0.16.* to 0.17
### Added `impl IntoPy<Py<PyString>> for &str`
This may cause inference errors.
Before:
```rust,compile_fail
# use pyo3::prelude::*;
#
# fn main() {
Python::with_gil(|py| {
// Cannot infer either `Py<PyAny>` or `Py<PyString>`
let _test = "test".into_py(py);
});
# }
```
After, some type annotations may be necessary:
```rust
# use pyo3::prelude::*;
#
# fn main() {
Python::with_gil(|py| {
let _test: Py<PyAny> = "test".into_py(py);
});
# }
```
### The `pyproto` feature is now disabled by default ### The `pyproto` feature is now disabled by default
In preparation for removing the deprecated `#[pyproto]` attribute macro in a future PyO3 version, it is now gated behind an opt-in feature flag. This also gives a slight saving to compile times for code which does not use the deprecated macro. In preparation for removing the deprecated `#[pyproto]` attribute macro in a future PyO3 version, it is now gated behind an opt-in feature flag. This also gives a slight saving to compile times for code which does not use the deprecated macro.

View File

@ -1,3 +1,4 @@
use crate::intern;
use crate::types::PyType; use crate::types::PyType;
use crate::{FromPyObject, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject}; use crate::{FromPyObject, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject};
use std::borrow::Cow; use std::borrow::Cow;
@ -18,10 +19,10 @@ impl FromPyObject<'_> for PathBuf {
Ok(s) => s, Ok(s) => s,
Err(err) => { Err(err) => {
let py = ob.py(); let py = ob.py();
let pathlib = py.import("pathlib")?; let pathlib = py.import(intern!(py, "pathlib"))?;
let pathlib_path: &PyType = pathlib.getattr("Path")?.downcast()?; let pathlib_path: &PyType = pathlib.getattr(intern!(py, "Path"))?.downcast()?;
if ob.is_instance(pathlib_path)? { if ob.is_instance(pathlib_path)? {
let path_str = ob.call_method0("__str__")?; let path_str = ob.call_method0(intern!(py, "__str__"))?;
OsString::extract(path_str)? OsString::extract(path_str)?
} else { } else {
return Err(err); return Err(err);

View File

@ -135,7 +135,10 @@ impl PyErr {
/// ///
/// # Examples /// # Examples
/// ```rust /// ```rust
/// use pyo3::{exceptions::PyTypeError, types::PyType, IntoPy, PyErr, Python}; /// use pyo3::prelude::*;
/// use pyo3::exceptions::PyTypeError;
/// use pyo3::types::{PyType, PyString};
///
/// Python::with_gil(|py| { /// Python::with_gil(|py| {
/// // Case #1: Exception object /// // Case #1: Exception object
/// let err = PyErr::from_value(PyTypeError::new_err("some type error").value(py)); /// let err = PyErr::from_value(PyTypeError::new_err("some type error").value(py));
@ -146,7 +149,7 @@ impl PyErr {
/// assert_eq!(err.to_string(), "TypeError: "); /// assert_eq!(err.to_string(), "TypeError: ");
/// ///
/// // Case #3: Invalid exception value /// // Case #3: Invalid exception value
/// let err = PyErr::from_value("foo".into_py(py).as_ref(py)); /// let err = PyErr::from_value(PyString::new(py, "foo").into());
/// assert_eq!( /// assert_eq!(
/// err.to_string(), /// err.to_string(),
/// "TypeError: exceptions must derive from BaseException" /// "TypeError: exceptions must derive from BaseException"

View File

@ -3,7 +3,7 @@ use crate::conversion::PyTryFrom;
use crate::err::{self, PyDowncastError, PyErr, PyResult}; use crate::err::{self, PyDowncastError, PyErr, PyResult};
use crate::gil; use crate::gil;
use crate::pycell::{PyBorrowError, PyBorrowMutError, PyCell}; use crate::pycell::{PyBorrowError, PyBorrowMutError, PyCell};
use crate::types::{PyDict, PyTuple}; use crate::types::{PyDict, PyString, PyTuple};
use crate::{ use crate::{
ffi, pyclass::MutablePyClass, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyAny, PyClass, ffi, pyclass::MutablePyClass, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyAny, PyClass,
PyClassInitializer, PyRef, PyRefMut, PyTypeInfo, Python, ToPyObject, PyClassInitializer, PyRef, PyRefMut, PyTypeInfo, Python, ToPyObject,
@ -561,12 +561,14 @@ impl<T> Py<T> {
/// ``` /// ```
pub fn getattr<N>(&self, py: Python<'_>, attr_name: N) -> PyResult<PyObject> pub fn getattr<N>(&self, py: Python<'_>, attr_name: N) -> PyResult<PyObject>
where where
N: ToPyObject, N: IntoPy<Py<PyString>>,
{ {
let attr_name = attr_name.into_py(py);
unsafe { unsafe {
PyObject::from_owned_ptr_or_err( PyObject::from_owned_ptr_or_err(
py, py,
ffi::PyObject_GetAttr(self.as_ptr(), attr_name.to_object(py).as_ptr()), ffi::PyObject_GetAttr(self.as_ptr(), attr_name.as_ptr()),
) )
} }
} }
@ -575,8 +577,8 @@ impl<T> Py<T> {
/// ///
/// This is equivalent to the Python expression `self.attr_name = value`. /// This is equivalent to the Python expression `self.attr_name = value`.
/// ///
/// If calling this method becomes performance-critical, the [`intern!`] macro can be used /// To avoid repeated temporary allocations of Python strings, the [`intern!`] macro can be used
/// to intern `attr_name`, thereby avoiding repeated temporary allocations of Python strings. /// to intern `attr_name`.
/// ///
/// # Example: `intern!`ing the attribute name /// # Example: `intern!`ing the attribute name
/// ///
@ -595,17 +597,16 @@ impl<T> Py<T> {
/// ``` /// ```
pub fn setattr<N, V>(&self, py: Python<'_>, attr_name: N, value: V) -> PyResult<()> pub fn setattr<N, V>(&self, py: Python<'_>, attr_name: N, value: V) -> PyResult<()>
where where
N: ToPyObject, N: IntoPy<Py<PyString>>,
V: ToPyObject, V: IntoPy<Py<PyAny>>,
{ {
let attr_name = attr_name.into_py(py);
let value = value.into_py(py);
unsafe { unsafe {
err::error_on_minusone( err::error_on_minusone(
py, py,
ffi::PyObject_SetAttr( ffi::PyObject_SetAttr(self.as_ptr(), attr_name.as_ptr(), value.as_ptr()),
self.as_ptr(),
attr_name.to_object(py).as_ptr(),
value.to_object(py).as_ptr(),
),
) )
} }
} }
@ -619,16 +620,17 @@ impl<T> Py<T> {
args: impl IntoPy<Py<PyTuple>>, args: impl IntoPy<Py<PyTuple>>,
kwargs: Option<&PyDict>, kwargs: Option<&PyDict>,
) -> PyResult<PyObject> { ) -> PyResult<PyObject> {
let args = args.into_py(py).into_ptr(); let args = args.into_py(py);
let kwargs = kwargs.into_ptr(); let kwargs = kwargs.into_ptr();
let result = unsafe {
PyObject::from_owned_ptr_or_err(py, ffi::PyObject_Call(self.as_ptr(), args, kwargs))
};
unsafe { unsafe {
ffi::Py_XDECREF(args); let ret = PyObject::from_owned_ptr_or_err(
py,
ffi::PyObject_Call(self.as_ptr(), args.as_ptr(), kwargs),
);
ffi::Py_XDECREF(kwargs); ffi::Py_XDECREF(kwargs);
ret
} }
result
} }
/// Calls the object with only positional arguments. /// Calls the object with only positional arguments.
@ -657,23 +659,29 @@ impl<T> Py<T> {
/// Calls a method on the object. /// Calls a method on the object.
/// ///
/// This is equivalent to the Python expression `self.name(*args, **kwargs)`. /// This is equivalent to the Python expression `self.name(*args, **kwargs)`.
pub fn call_method( ///
/// To avoid repeated temporary allocations of Python strings, the [`intern!`] macro can be used
/// to intern `name`.
pub fn call_method<N, A>(
&self, &self,
py: Python<'_>, py: Python<'_>,
name: &str, name: N,
args: impl IntoPy<Py<PyTuple>>, args: A,
kwargs: Option<&PyDict>, kwargs: Option<&PyDict>,
) -> PyResult<PyObject> { ) -> PyResult<PyObject>
where
N: IntoPy<Py<PyString>>,
A: IntoPy<Py<PyTuple>>,
{
let callee = self.getattr(py, name)?;
let args: Py<PyTuple> = args.into_py(py);
let kwargs = kwargs.into_ptr();
unsafe { unsafe {
let args = args.into_py(py).into_ptr(); let result = PyObject::from_owned_ptr_or_err(
let kwargs = kwargs.into_ptr(); py,
let ptr = ffi::PyObject_GetAttr(self.as_ptr(), name.to_object(py).as_ptr()); ffi::PyObject_Call(callee.as_ptr(), args.as_ptr(), kwargs),
if ptr.is_null() { );
return Err(PyErr::fetch(py));
}
let result = PyObject::from_owned_ptr_or_err(py, ffi::PyObject_Call(ptr, args, kwargs));
ffi::Py_DECREF(ptr);
ffi::Py_XDECREF(args);
ffi::Py_XDECREF(kwargs); ffi::Py_XDECREF(kwargs);
result result
} }
@ -682,24 +690,32 @@ impl<T> Py<T> {
/// Calls a method on the object with only positional arguments. /// Calls a method on the object with only positional arguments.
/// ///
/// This is equivalent to the Python expression `self.name(*args)`. /// This is equivalent to the Python expression `self.name(*args)`.
pub fn call_method1( ///
&self, /// To avoid repeated temporary allocations of Python strings, the [`intern!`] macro can be used
py: Python<'_>, /// to intern `name`.
name: &str, pub fn call_method1<N, A>(&self, py: Python<'_>, name: N, args: A) -> PyResult<PyObject>
args: impl IntoPy<Py<PyTuple>>, where
) -> PyResult<PyObject> { N: IntoPy<Py<PyString>>,
A: IntoPy<Py<PyTuple>>,
{
self.call_method(py, name, args, None) self.call_method(py, name, args, None)
} }
/// Calls a method on the object with no arguments. /// Calls a method on the object with no arguments.
/// ///
/// This is equivalent to the Python expression `self.name()`. /// This is equivalent to the Python expression `self.name()`.
pub fn call_method0(&self, py: Python<'_>, name: &str) -> PyResult<PyObject> { ///
/// To avoid repeated temporary allocations of Python strings, the [`intern!`] macro can be used
/// to intern `name`.
pub fn call_method0<N>(&self, py: Python<'_>, name: N) -> PyResult<PyObject>
where
N: IntoPy<Py<PyString>>,
{
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(all(Py_3_9, not(any(Py_LIMITED_API, PyPy))))] { if #[cfg(all(Py_3_9, not(any(Py_LIMITED_API, PyPy))))] {
// Optimized path on python 3.9+ // Optimized path on python 3.9+
unsafe { unsafe {
let name = name.into_py(py); let name: Py<PyString> = name.into_py(py);
PyObject::from_owned_ptr_or_err(py, ffi::PyObject_CallMethodNoArgs(self.as_ptr(), name.as_ptr())) PyObject::from_owned_ptr_or_err(py, ffi::PyObject_CallMethodNoArgs(self.as_ptr(), name.as_ptr()))
} }
} else { } else {
@ -728,7 +744,7 @@ impl<T> Py<T> {
/// Create a `Py<T>` instance by taking ownership of the given FFI pointer. /// Create a `Py<T>` instance by taking ownership of the given FFI pointer.
/// ///
/// If `ptr` is null then the current Python exception is fetched as a `PyErr`. /// If `ptr` is null then the current Python exception is fetched as a [`PyErr`].
/// ///
/// # Safety /// # Safety
/// If non-null, `ptr` must be a pointer to a Python object of type T. /// If non-null, `ptr` must be a pointer to a Python object of type T.
@ -985,8 +1001,8 @@ impl PyObject {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Py, PyObject}; use super::{Py, PyObject};
use crate::types::PyDict; use crate::types::{PyDict, PyString};
use crate::{Python, ToPyObject}; use crate::{PyAny, PyResult, Python, ToPyObject};
#[test] #[test]
fn test_call0() { fn test_call0() {
@ -1036,4 +1052,67 @@ mod tests {
assert_eq!(p.get_refcnt(py), cnt); assert_eq!(p.get_refcnt(py), cnt);
}); });
} }
#[test]
fn attr() -> PyResult<()> {
use crate::types::PyModule;
Python::with_gil(|py| {
const CODE: &str = r#"
class A:
pass
a = A()
"#;
let module = PyModule::from_code(py, CODE, "", "")?;
let instance: Py<PyAny> = module.getattr("a")?.into();
instance.getattr(py, "foo").unwrap_err();
instance.setattr(py, "foo", "bar")?;
assert!(instance
.getattr(py, "foo")?
.as_ref(py)
.eq(PyString::new(py, "bar"))?);
instance.getattr(py, "foo")?;
Ok(())
})
}
#[test]
fn pystring_attr() -> PyResult<()> {
use crate::types::PyModule;
Python::with_gil(|py| {
const CODE: &str = r#"
class A:
pass
a = A()
"#;
let module = PyModule::from_code(py, CODE, "", "")?;
let instance: Py<PyAny> = module.getattr("a")?.into();
let foo = crate::intern!(py, "foo");
let bar = crate::intern!(py, "bar");
instance.getattr(py, foo).unwrap_err();
instance.setattr(py, foo, bar)?;
assert!(instance.getattr(py, foo)?.as_ref(py).eq(bar)?);
Ok(())
})
}
#[test]
fn invalid_attr() -> PyResult<()> {
Python::with_gil(|py| {
let instance: Py<PyAny> = py.eval("object()", None, None)?.into();
instance.getattr(py, "foo").unwrap_err();
// Cannot assign arbitrary attributes to `object`
instance.setattr(py, "foo", "bar").unwrap_err();
Ok(())
})
}
} }

View File

@ -122,10 +122,11 @@
use crate::err::{self, PyDowncastError, PyErr, PyResult}; use crate::err::{self, PyDowncastError, PyErr, PyResult};
use crate::gil::{self, GILGuard, GILPool}; use crate::gil::{self, GILGuard, GILPool};
use crate::impl_::not_send::NotSend; use crate::impl_::not_send::NotSend;
use crate::types::{PyAny, PyDict, PyModule, PyType}; use crate::types::{PyAny, PyDict, PyModule, PyString, PyType};
use crate::version::PythonVersionInfo; use crate::version::PythonVersionInfo;
use crate::{ use crate::{
ffi, AsPyPointer, FromPyPointer, IntoPyPointer, PyNativeType, PyObject, PyTryFrom, PyTypeInfo, ffi, AsPyPointer, FromPyPointer, IntoPy, IntoPyPointer, Py, PyNativeType, PyObject, PyTryFrom,
PyTypeInfo,
}; };
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
use std::marker::PhantomData; use std::marker::PhantomData;
@ -590,7 +591,10 @@ impl<'py> Python<'py> {
} }
/// Imports the Python module with the specified name. /// Imports the Python module with the specified name.
pub fn import(self, name: &str) -> PyResult<&'py PyModule> { pub fn import<N>(self, name: N) -> PyResult<&'py PyModule>
where
N: IntoPy<Py<PyString>>,
{
PyModule::import(self, name) PyModule::import(self, name)
} }

View File

@ -97,21 +97,25 @@ impl PyAny {
/// Determines whether this object has the given attribute. /// Determines whether this object has the given attribute.
/// ///
/// This is equivalent to the Python expression `hasattr(self, attr_name)`. /// This is equivalent to the Python expression `hasattr(self, attr_name)`.
///
/// To avoid repeated temporary allocations of Python strings, the [`intern!`] macro can be used
/// to intern `attr_name`.
pub fn hasattr<N>(&self, attr_name: N) -> PyResult<bool> pub fn hasattr<N>(&self, attr_name: N) -> PyResult<bool>
where where
N: ToPyObject, N: IntoPy<Py<PyString>>,
{ {
unsafe { let py = self.py();
Ok(ffi::PyObject_HasAttr(self.as_ptr(), attr_name.to_object(self.py()).as_ptr()) != 0) let attr_name = attr_name.into_py(py);
}
unsafe { Ok(ffi::PyObject_HasAttr(self.as_ptr(), attr_name.as_ptr()) != 0) }
} }
/// Retrieves an attribute value. /// Retrieves an attribute value.
/// ///
/// This is equivalent to the Python expression `self.attr_name`. /// This is equivalent to the Python expression `self.attr_name`.
/// ///
/// If calling this method becomes performance-critical, the [`intern!`] macro can be used /// To avoid repeated temporary allocations of Python strings, the [`intern!`] macro can be used
/// to intern `attr_name`, thereby avoiding repeated temporary allocations of Python strings. /// to intern `attr_name`.
/// ///
/// # Example: `intern!`ing the attribute name /// # Example: `intern!`ing the attribute name
/// ///
@ -130,13 +134,14 @@ impl PyAny {
/// ``` /// ```
pub fn getattr<N>(&self, attr_name: N) -> PyResult<&PyAny> pub fn getattr<N>(&self, attr_name: N) -> PyResult<&PyAny>
where where
N: ToPyObject, N: IntoPy<Py<PyString>>,
{ {
let py = self.py();
let attr_name = attr_name.into_py(py);
unsafe { unsafe {
self.py().from_owned_ptr_or_err(ffi::PyObject_GetAttr( let ret = ffi::PyObject_GetAttr(self.as_ptr(), attr_name.as_ptr());
self.as_ptr(), py.from_owned_ptr_or_err(ret)
attr_name.to_object(self.py()).as_ptr(),
))
} }
} }
@ -144,8 +149,8 @@ impl PyAny {
/// ///
/// This is equivalent to the Python expression `self.attr_name = value`. /// This is equivalent to the Python expression `self.attr_name = value`.
/// ///
/// If calling this method becomes performance-critical, the [`intern!`] macro can be used /// To avoid repeated temporary allocations of Python strings, the [`intern!`] macro can be used
/// to intern `attr_name`, thereby avoiding repeated temporary allocations of Python strings. /// to intern `name`.
/// ///
/// # Example: `intern!`ing the attribute name /// # Example: `intern!`ing the attribute name
/// ///
@ -164,35 +169,35 @@ impl PyAny {
/// ``` /// ```
pub fn setattr<N, V>(&self, attr_name: N, value: V) -> PyResult<()> pub fn setattr<N, V>(&self, attr_name: N, value: V) -> PyResult<()>
where where
N: ToPyObject, N: IntoPy<Py<PyString>>,
V: ToPyObject, V: ToPyObject,
{ {
let py = self.py(); let py = self.py();
let attr_name = attr_name.into_py(py);
let value = value.to_object(py);
unsafe { unsafe {
err::error_on_minusone( let ret = ffi::PyObject_SetAttr(self.as_ptr(), attr_name.as_ptr(), value.as_ptr());
py, err::error_on_minusone(py, ret)
ffi::PyObject_SetAttr(
self.as_ptr(),
attr_name.to_object(py).as_ptr(),
value.to_object(py).as_ptr(),
),
)
} }
} }
/// Deletes an attribute. /// Deletes an attribute.
/// ///
/// This is equivalent to the Python statement `del self.attr_name`. /// This is equivalent to the Python statement `del self.attr_name`.
///
/// To avoid repeated temporary allocations of Python strings, the [`intern!`] macro can be used
/// to intern `attr_name`.
pub fn delattr<N>(&self, attr_name: N) -> PyResult<()> pub fn delattr<N>(&self, attr_name: N) -> PyResult<()>
where where
N: ToPyObject, N: IntoPy<Py<PyString>>,
{ {
let py = self.py(); let py = self.py();
let attr_name = attr_name.into_py(py);
unsafe { unsafe {
err::error_on_minusone( let ret = ffi::PyObject_DelAttr(self.as_ptr(), attr_name.as_ptr());
self.py(), err::error_on_minusone(py, ret)
ffi::PyObject_DelAttr(self.as_ptr(), attr_name.to_object(py).as_ptr()),
)
} }
} }
@ -273,6 +278,10 @@ impl PyAny {
/// Tests whether two Python objects obey a given [`CompareOp`]. /// Tests whether two Python objects obey a given [`CompareOp`].
/// ///
/// [`lt`](Self::lt), [`le`](Self::le), [`eq`](Self::eq), [`ne`](Self::ne),
/// [`gt`](Self::gt) and [`ge`](Self::ge) are the specialized versions
/// of this function.
///
/// Depending on the value of `compare_op`, this is equivalent to one of the /// Depending on the value of `compare_op`, this is equivalent to one of the
/// following Python expressions: /// following Python expressions:
/// ///
@ -408,22 +417,49 @@ impl PyAny {
/// Calls the object. /// Calls the object.
/// ///
/// This is equivalent to the Python expression `self(*args, **kwargs)`. /// This is equivalent to the Python expression `self(*args, **kwargs)`.
///
/// # Examples
///
/// ```rust
/// use pyo3::prelude::*;
/// use pyo3::types::PyDict;
///
/// const CODE: &str = r#"
/// def function(*args, **kwargs):
/// assert args == ("hello",)
/// assert kwargs == {"cruel": "world"}
/// return "called with args and kwargs"
/// "#;
///
/// # fn main() -> PyResult<()> {
/// Python::with_gil(|py| {
/// let module = PyModule::from_code(py, CODE, "", "")?;
/// let fun = module.getattr("function")?;
/// let args = ("hello",);
/// let kwargs = PyDict::new(py);
/// kwargs.set_item("cruel", "world")?;
/// let result = fun.call(args, Some(kwargs))?;
/// assert_eq!(result.extract::<&str>()?, "called with args and kwargs");
/// Ok(())
/// })
/// # }
/// ```
pub fn call( pub fn call(
&self, &self,
args: impl IntoPy<Py<PyTuple>>, args: impl IntoPy<Py<PyTuple>>,
kwargs: Option<&PyDict>, kwargs: Option<&PyDict>,
) -> PyResult<&PyAny> { ) -> PyResult<&PyAny> {
let args = args.into_py(self.py()).into_ptr(); let py = self.py();
let args = args.into_py(py);
let kwargs = kwargs.into_ptr(); let kwargs = kwargs.into_ptr();
let result = unsafe {
let return_value = ffi::PyObject_Call(self.as_ptr(), args, kwargs);
self.py().from_owned_ptr_or_err(return_value)
};
unsafe { unsafe {
ffi::Py_XDECREF(args); let return_value = ffi::PyObject_Call(self.as_ptr(), args.as_ptr(), kwargs);
let ret = py.from_owned_ptr_or_err(return_value);
ffi::Py_XDECREF(kwargs); ffi::Py_XDECREF(kwargs);
ret
} }
result
} }
/// Calls the object without arguments. /// Calls the object without arguments.
@ -468,25 +504,23 @@ impl PyAny {
/// ```rust /// ```rust
/// use pyo3::prelude::*; /// use pyo3::prelude::*;
/// ///
/// const CODE: &str = r#"
/// def function(*args, **kwargs):
/// assert args == ("hello",)
/// assert kwargs == {}
/// return "called with args"
/// "#;
///
/// # fn main() -> PyResult<()> { /// # fn main() -> PyResult<()> {
/// Python::with_gil(|py| -> PyResult<()> { /// Python::with_gil(|py| {
/// let module = PyModule::import(py, "operator")?; /// let module = PyModule::from_code(py, CODE, "", "")?;
/// let add = module.getattr("add")?; /// let fun = module.getattr("function")?;
/// let args = (1, 2); /// let args = ("hello",);
/// let value = add.call1(args)?; /// let result = fun.call1(args)?;
/// assert_eq!(value.extract::<i32>()?, 3); /// assert_eq!(result.extract::<&str>()?, "called with args");
/// Ok(()) /// Ok(())
/// })?; /// })
/// # Ok(())} /// # }
/// ```
///
/// This is equivalent to the following Python code:
///
/// ```python
/// from operator import add
///
/// value = add(1,2)
/// assert value == 3
/// ``` /// ```
pub fn call1(&self, args: impl IntoPy<Py<PyTuple>>) -> PyResult<&PyAny> { pub fn call1(&self, args: impl IntoPy<Py<PyTuple>>) -> PyResult<&PyAny> {
self.call(args, None) self.call(args, None)
@ -496,49 +530,51 @@ impl PyAny {
/// ///
/// This is equivalent to the Python expression `self.name(*args, **kwargs)`. /// This is equivalent to the Python expression `self.name(*args, **kwargs)`.
/// ///
/// To avoid repeated temporary allocations of Python strings, the [`intern!`] macro can be used
/// to intern `name`.
///
/// # Examples /// # Examples
/// ///
/// ```rust /// ```rust
/// use pyo3::prelude::*; /// use pyo3::prelude::*;
/// use pyo3::types::{IntoPyDict, PyList}; /// use pyo3::types::PyDict;
///
/// const CODE: &str = r#"
/// class A:
/// def method(self, *args, **kwargs):
/// assert args == ("hello",)
/// assert kwargs == {"cruel": "world"}
/// return "called with args and kwargs"
/// a = A()
/// "#;
/// ///
/// # fn main() -> PyResult<()> { /// # fn main() -> PyResult<()> {
/// Python::with_gil(|py| -> PyResult<()> { /// Python::with_gil(|py| {
/// let list = PyList::new(py, vec![3, 6, 5, 4, 7]); /// let module = PyModule::from_code(py, CODE, "", "")?;
/// let kwargs = vec![("reverse", true)].into_py_dict(py); /// let instance = module.getattr("a")?;
/// /// let args = ("hello",);
/// list.call_method("sort", (), Some(kwargs))?; /// let kwargs = PyDict::new(py);
/// assert_eq!(list.extract::<Vec<i32>>()?, vec![7, 6, 5, 4, 3]); /// kwargs.set_item("cruel", "world")?;
/// let result = instance.call_method("method", args, Some(kwargs))?;
/// assert_eq!(result.extract::<&str>()?, "called with args and kwargs");
/// Ok(()) /// Ok(())
/// })?; /// })
/// # Ok(())} /// # }
/// ``` /// ```
/// pub fn call_method<N, A>(&self, name: N, args: A, kwargs: Option<&PyDict>) -> PyResult<&PyAny>
/// This is equivalent to the following Python code: where
/// N: IntoPy<Py<PyString>>,
/// ```python A: IntoPy<Py<PyTuple>>,
/// my_list = [3, 6, 5, 4, 7] {
/// my_list.sort(reverse = True) let py = self.py();
/// assert my_list == [7, 6, 5, 4, 3]
/// ``` let callee = self.getattr(name)?;
pub fn call_method( let args: Py<PyTuple> = args.into_py(py);
&self, let kwargs = kwargs.into_ptr();
name: &str,
args: impl IntoPy<Py<PyTuple>>,
kwargs: Option<&PyDict>,
) -> PyResult<&PyAny> {
unsafe { unsafe {
let py = self.py(); let result_ptr = ffi::PyObject_Call(callee.as_ptr(), args.as_ptr(), kwargs);
let ptr = ffi::PyObject_GetAttr(self.as_ptr(), name.to_object(py).as_ptr());
if ptr.is_null() {
return Err(PyErr::fetch(py));
}
let args = args.into_py(py).into_ptr();
let kwargs = kwargs.into_ptr();
let result_ptr = ffi::PyObject_Call(ptr, args, kwargs);
let result = py.from_owned_ptr_or_err(result_ptr); let result = py.from_owned_ptr_or_err(result_ptr);
ffi::Py_DECREF(ptr);
ffi::Py_XDECREF(args);
ffi::Py_XDECREF(kwargs); ffi::Py_XDECREF(kwargs);
result result
} }
@ -548,39 +584,46 @@ impl PyAny {
/// ///
/// This is equivalent to the Python expression `self.name()`. /// This is equivalent to the Python expression `self.name()`.
/// ///
/// To avoid repeated temporary allocations of Python strings, the [`intern!`] macro can be used
/// to intern `name`.
///
/// # Examples /// # Examples
/// ///
/// ```rust /// ```rust
/// use pyo3::prelude::*; /// use pyo3::prelude::*;
/// use pyo3::types::PyFloat; ///
/// use std::f64::consts::PI; /// const CODE: &str = r#"
/// class A:
/// def method(self, *args, **kwargs):
/// assert args == ()
/// assert kwargs == {}
/// return "called with no arguments"
/// a = A()
/// "#;
/// ///
/// # fn main() -> PyResult<()> { /// # fn main() -> PyResult<()> {
/// Python::with_gil(|py| -> PyResult<()> { /// Python::with_gil(|py| {
/// let pi = PyFloat::new(py, PI); /// let module = PyModule::from_code(py, CODE, "", "")?;
/// let ratio = pi.call_method0("as_integer_ratio")?; /// let instance = module.getattr("a")?;
/// let (a, b) = ratio.extract::<(u64, u64)>()?; /// let result = instance.call_method0("method")?;
/// assert_eq!(a, 884_279_719_003_555); /// assert_eq!(result.extract::<&str>()?, "called with no arguments");
/// assert_eq!(b, 281_474_976_710_656);
/// Ok(()) /// Ok(())
/// })?; /// })
/// # Ok(())} /// # }
/// ``` /// ```
/// pub fn call_method0<N>(&self, name: N) -> PyResult<&PyAny>
/// This is equivalent to the following Python code: where
/// N: IntoPy<Py<PyString>>,
/// ```python {
/// import math
///
/// a, b = math.pi.as_integer_ratio()
/// ```
pub fn call_method0(&self, name: &str) -> PyResult<&PyAny> {
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(all(Py_3_9, not(any(Py_LIMITED_API, PyPy))))] { if #[cfg(all(Py_3_9, not(any(Py_LIMITED_API, PyPy))))] {
let py = self.py();
// Optimized path on python 3.9+ // Optimized path on python 3.9+
unsafe { unsafe {
let name = name.into_py(self.py()); let name: Py<PyString> = name.into_py(py);
self.py().from_owned_ptr_or_err(ffi::PyObject_CallMethodNoArgs(self.as_ptr(), name.as_ptr())) let ptr = ffi::PyObject_CallMethodNoArgs(self.as_ptr(), name.as_ptr());
py.from_owned_ptr_or_err(ptr)
} }
} else { } else {
self.call_method(name, (), None) self.call_method(name, (), None)
@ -592,30 +635,39 @@ impl PyAny {
/// ///
/// This is equivalent to the Python expression `self.name(*args)`. /// This is equivalent to the Python expression `self.name(*args)`.
/// ///
/// To avoid repeated temporary allocations of Python strings, the [`intern!`] macro can be used
/// to intern `name`.
///
/// # Examples /// # Examples
/// ///
/// ```rust /// ```rust
/// use pyo3::prelude::*; /// use pyo3::prelude::*;
/// use pyo3::types::PyList; ///
/// const CODE: &str = r#"
/// class A:
/// def method(self, *args, **kwargs):
/// assert args == ("hello",)
/// assert kwargs == {}
/// return "called with args"
/// a = A()
/// "#;
/// ///
/// # fn main() -> PyResult<()> { /// # fn main() -> PyResult<()> {
/// Python::with_gil(|py| -> PyResult<()> { /// Python::with_gil(|py| {
/// let list = PyList::new(py, vec![1, 3, 4]); /// let module = PyModule::from_code(py, CODE, "", "")?;
/// list.call_method1("insert", (1, 2))?; /// let instance = module.getattr("a")?;
/// assert_eq!(list.extract::<Vec<u8>>()?, [1, 2, 3, 4]); /// let args = ("hello",);
/// let result = instance.call_method1("method", args)?;
/// assert_eq!(result.extract::<&str>()?, "called with args");
/// Ok(()) /// Ok(())
/// })?; /// })
/// # Ok(()) } /// # }
/// ``` /// ```
/// pub fn call_method1<N, A>(&self, name: N, args: A) -> PyResult<&PyAny>
/// This is equivalent to the following Python code: where
/// N: IntoPy<Py<PyString>>,
/// ```python A: IntoPy<Py<PyTuple>>,
/// list_ = [1,3,4] {
/// list_.insert(1,2)
/// assert list_ == [1,2,3,4]
/// ```
pub fn call_method1(&self, name: &str, args: impl IntoPy<Py<PyTuple>>) -> PyResult<&PyAny> {
self.call_method(name, args, None) self.call_method(name, args, None)
} }
@ -712,7 +764,7 @@ impl PyAny {
unsafe { ffi::Py_TYPE(self.as_ptr()) } unsafe { ffi::Py_TYPE(self.as_ptr()) }
} }
/// Casts the PyObject to a concrete Python object type. /// Casts `self` to a concrete Python object type.
/// ///
/// This can cast only to native Python types, not types implemented in Rust. /// This can cast only to native Python types, not types implemented in Rust.
pub fn cast_as<'a, D>(&'a self) -> Result<&'a D, PyDowncastError<'_>> pub fn cast_as<'a, D>(&'a self) -> Result<&'a D, PyDowncastError<'_>>
@ -724,7 +776,7 @@ impl PyAny {
/// Extracts some type from the Python object. /// Extracts some type from the Python object.
/// ///
/// This is a wrapper function around `FromPyObject::extract()`. /// This is a wrapper function around [`FromPyObject::extract()`].
pub fn extract<'a, D>(&'a self) -> PyResult<D> pub fn extract<'a, D>(&'a self) -> PyResult<D>
where where
D: FromPyObject<'a>, D: FromPyObject<'a>,
@ -788,11 +840,11 @@ impl PyAny {
unsafe { self.py().from_owned_ptr(ffi::PyObject_Dir(self.as_ptr())) } unsafe { self.py().from_owned_ptr(ffi::PyObject_Dir(self.as_ptr())) }
} }
/// Checks whether this object is an instance of type `typ`. /// Checks whether this object is an instance of type `ty`.
/// ///
/// This is equivalent to the Python expression `isinstance(self, typ)`. /// This is equivalent to the Python expression `isinstance(self, ty)`.
pub fn is_instance(&self, typ: &PyType) -> PyResult<bool> { pub fn is_instance(&self, ty: &PyType) -> PyResult<bool> {
let result = unsafe { ffi::PyObject_IsInstance(self.as_ptr(), typ.as_ptr()) }; let result = unsafe { ffi::PyObject_IsInstance(self.as_ptr(), ty.as_ptr()) };
err::error_on_minusone(self.py(), result)?; err::error_on_minusone(self.py(), result)?;
Ok(result == 1) Ok(result == 1)
} }

View File

@ -155,7 +155,7 @@ impl PyCFunction {
let (py, module) = py_or_module.into_py_and_maybe_module(); let (py, module) = py_or_module.into_py_and_maybe_module();
let (mod_ptr, module_name) = if let Some(m) = module { let (mod_ptr, module_name) = if let Some(m) = module {
let mod_ptr = m.as_ptr(); let mod_ptr = m.as_ptr();
let name = m.name()?.into_py(py); let name: Py<PyAny> = m.name()?.into_py(py);
(mod_ptr, name.as_ptr()) (mod_ptr, name.as_ptr())
} else { } else {
(std::ptr::null_mut(), std::ptr::null_mut()) (std::ptr::null_mut(), std::ptr::null_mut())

View File

@ -8,7 +8,7 @@ use crate::exceptions;
use crate::ffi; use crate::ffi;
use crate::pyclass::PyClass; use crate::pyclass::PyClass;
use crate::types::{PyAny, PyCFunction, PyDict, PyList, PyString}; use crate::types::{PyAny, PyCFunction, PyDict, PyList, PyString};
use crate::{AsPyPointer, IntoPy, PyObject, Python}; use crate::{AsPyPointer, IntoPy, Py, PyObject, Python};
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
use std::str; use std::str;
@ -65,8 +65,11 @@ impl PyModule {
/// ```python /// ```python
/// import antigravity /// import antigravity
/// ``` /// ```
pub fn import<'p>(py: Python<'p>, name: &str) -> PyResult<&'p PyModule> { pub fn import<N>(py: Python<'_>, name: N) -> PyResult<&PyModule>
let name: PyObject = name.into_py(py); where
N: IntoPy<Py<PyString>>,
{
let name: Py<PyString> = name.into_py(py);
unsafe { py.from_owned_ptr_or_err(ffi::PyImport_Import(name.as_ptr())) } unsafe { py.from_owned_ptr_or_err(ffi::PyImport_Import(name.as_ptr())) }
} }

View File

@ -4,7 +4,7 @@
use crate::exceptions::PyUnicodeDecodeError; use crate::exceptions::PyUnicodeDecodeError;
use crate::types::PyBytes; use crate::types::PyBytes;
use crate::{ use crate::{
ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyObject, PyResult, PyTryFrom, Python, ffi, AsPyPointer, FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult, PyTryFrom, Python,
ToPyObject, ToPyObject,
}; };
use std::borrow::Cow; use std::borrow::Cow;
@ -298,6 +298,13 @@ impl<'a> IntoPy<PyObject> for &'a str {
} }
} }
impl<'a> IntoPy<Py<PyString>> for &'a str {
#[inline]
fn into_py(self, py: Python<'_>) -> Py<PyString> {
PyString::new(py, self).into()
}
}
/// Converts a Rust `Cow<'_, str>` to a Python object. /// Converts a Rust `Cow<'_, str>` to a Python object.
/// See `PyString::new` for details on the conversion. /// See `PyString::new` for details on the conversion.
impl<'a> ToPyObject for Cow<'a, str> { impl<'a> ToPyObject for Cow<'a, str> {

View File

@ -49,11 +49,14 @@ impl PyTraceback {
/// ``` /// ```
pub fn format(&self) -> PyResult<String> { pub fn format(&self) -> PyResult<String> {
let py = self.py(); let py = self.py();
let string_io = py.import("io")?.getattr("StringIO")?.call0()?; let string_io = py
.import(intern!(py, "io"))?
.getattr(intern!(py, "StringIO"))?
.call0()?;
let result = unsafe { ffi::PyTraceBack_Print(self.as_ptr(), string_io.as_ptr()) }; let result = unsafe { ffi::PyTraceBack_Print(self.as_ptr(), string_io.as_ptr()) };
error_on_minusone(py, result)?; error_on_minusone(py, result)?;
let formatted = string_io let formatted = string_io
.getattr("getvalue")? .getattr(intern!(py, "getvalue"))?
.call0()? .call0()?
.downcast::<PyString>()? .downcast::<PyString>()?
.to_str()? .to_str()?

View File

@ -74,7 +74,7 @@ pub struct B {
#[test] #[test]
fn test_transparent_named_field_struct() { fn test_transparent_named_field_struct() {
Python::with_gil(|py| { Python::with_gil(|py| {
let test = "test".into_py(py); let test: PyObject = "test".into_py(py);
let b: B = FromPyObject::extract(test.as_ref(py)).expect("Failed to extract B from String"); let b: B = FromPyObject::extract(test.as_ref(py)).expect("Failed to extract B from String");
assert_eq!(b.test, "test"); assert_eq!(b.test, "test");
let test: PyObject = 1.into_py(py); let test: PyObject = 1.into_py(py);
@ -92,7 +92,7 @@ pub struct D<T> {
#[test] #[test]
fn test_generic_transparent_named_field_struct() { fn test_generic_transparent_named_field_struct() {
Python::with_gil(|py| { Python::with_gil(|py| {
let test = "test".into_py(py); let test: PyObject = "test".into_py(py);
let d: D<String> = let d: D<String> =
D::extract(test.as_ref(py)).expect("Failed to extract D<String> from String"); D::extract(test.as_ref(py)).expect("Failed to extract D<String> from String");
assert_eq!(d.test, "test"); assert_eq!(d.test, "test");
@ -180,7 +180,7 @@ fn test_transparent_tuple_struct() {
let tup: PyObject = 1.into_py(py); let tup: PyObject = 1.into_py(py);
let tup = TransparentTuple::extract(tup.as_ref(py)); let tup = TransparentTuple::extract(tup.as_ref(py));
assert!(tup.is_err()); assert!(tup.is_err());
let test = "test".into_py(py); let test: PyObject = "test".into_py(py);
let tup = TransparentTuple::extract(test.as_ref(py)) let tup = TransparentTuple::extract(test.as_ref(py))
.expect("Failed to extract TransparentTuple from PyTuple"); .expect("Failed to extract TransparentTuple from PyTuple");
assert_eq!(tup.0, "test"); assert_eq!(tup.0, "test");