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
- 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)
- 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)
## [0.16.4] - 2022-04-14
### 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
### 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
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::{FromPyObject, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject};
use std::borrow::Cow;
@ -18,10 +19,10 @@ impl FromPyObject<'_> for PathBuf {
Ok(s) => s,
Err(err) => {
let py = ob.py();
let pathlib = py.import("pathlib")?;
let pathlib_path: &PyType = pathlib.getattr("Path")?.downcast()?;
let pathlib = py.import(intern!(py, "pathlib"))?;
let pathlib_path: &PyType = pathlib.getattr(intern!(py, "Path"))?.downcast()?;
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)?
} else {
return Err(err);

View File

@ -135,7 +135,10 @@ impl PyErr {
///
/// # Examples
/// ```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| {
/// // Case #1: Exception object
/// 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: ");
///
/// // 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!(
/// err.to_string(),
/// "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::gil;
use crate::pycell::{PyBorrowError, PyBorrowMutError, PyCell};
use crate::types::{PyDict, PyTuple};
use crate::types::{PyDict, PyString, PyTuple};
use crate::{
ffi, pyclass::MutablePyClass, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyAny, PyClass,
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>
where
N: ToPyObject,
N: IntoPy<Py<PyString>>,
{
let attr_name = attr_name.into_py(py);
unsafe {
PyObject::from_owned_ptr_or_err(
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`.
///
/// If calling this method becomes performance-critical, the [`intern!`] macro can be used
/// to intern `attr_name`, thereby avoiding repeated temporary allocations of Python strings.
/// To avoid repeated temporary allocations of Python strings, the [`intern!`] macro can be used
/// to intern `attr_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<()>
where
N: ToPyObject,
V: ToPyObject,
N: IntoPy<Py<PyString>>,
V: IntoPy<Py<PyAny>>,
{
let attr_name = attr_name.into_py(py);
let value = value.into_py(py);
unsafe {
err::error_on_minusone(
py,
ffi::PyObject_SetAttr(
self.as_ptr(),
attr_name.to_object(py).as_ptr(),
value.to_object(py).as_ptr(),
),
ffi::PyObject_SetAttr(self.as_ptr(), attr_name.as_ptr(), value.as_ptr()),
)
}
}
@ -619,16 +620,17 @@ impl<T> Py<T> {
args: impl IntoPy<Py<PyTuple>>,
kwargs: Option<&PyDict>,
) -> PyResult<PyObject> {
let args = args.into_py(py).into_ptr();
let args = args.into_py(py);
let kwargs = kwargs.into_ptr();
let result = unsafe {
PyObject::from_owned_ptr_or_err(py, ffi::PyObject_Call(self.as_ptr(), args, kwargs))
};
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);
ret
}
result
}
/// Calls the object with only positional arguments.
@ -657,23 +659,29 @@ impl<T> Py<T> {
/// Calls a method on the object.
///
/// 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,
py: Python<'_>,
name: &str,
args: impl IntoPy<Py<PyTuple>>,
name: N,
args: A,
kwargs: Option<&PyDict>,
) -> PyResult<PyObject> {
unsafe {
let args = args.into_py(py).into_ptr();
) -> 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();
let ptr = ffi::PyObject_GetAttr(self.as_ptr(), name.to_object(py).as_ptr());
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);
unsafe {
let result = PyObject::from_owned_ptr_or_err(
py,
ffi::PyObject_Call(callee.as_ptr(), args.as_ptr(), kwargs),
);
ffi::Py_XDECREF(kwargs);
result
}
@ -682,24 +690,32 @@ impl<T> Py<T> {
/// Calls a method on the object with only positional arguments.
///
/// This is equivalent to the Python expression `self.name(*args)`.
pub fn call_method1(
&self,
py: Python<'_>,
name: &str,
args: impl IntoPy<Py<PyTuple>>,
) -> PyResult<PyObject> {
///
/// To avoid repeated temporary allocations of Python strings, the [`intern!`] macro can be used
/// to intern `name`.
pub fn call_method1<N, A>(&self, py: Python<'_>, name: N, args: A) -> PyResult<PyObject>
where
N: IntoPy<Py<PyString>>,
A: IntoPy<Py<PyTuple>>,
{
self.call_method(py, name, args, None)
}
/// Calls a method on the object with no arguments.
///
/// 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! {
if #[cfg(all(Py_3_9, not(any(Py_LIMITED_API, PyPy))))] {
// Optimized path on python 3.9+
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()))
}
} else {
@ -728,7 +744,7 @@ impl<T> Py<T> {
/// 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
/// If non-null, `ptr` must be a pointer to a Python object of type T.
@ -985,8 +1001,8 @@ impl PyObject {
#[cfg(test)]
mod tests {
use super::{Py, PyObject};
use crate::types::PyDict;
use crate::{Python, ToPyObject};
use crate::types::{PyDict, PyString};
use crate::{PyAny, PyResult, Python, ToPyObject};
#[test]
fn test_call0() {
@ -1036,4 +1052,67 @@ mod tests {
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::gil::{self, GILGuard, GILPool};
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::{
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::marker::PhantomData;
@ -590,7 +591,10 @@ impl<'py> Python<'py> {
}
/// 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)
}

View File

@ -97,21 +97,25 @@ impl PyAny {
/// Determines whether this object has the given attribute.
///
/// 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>
where
N: ToPyObject,
N: IntoPy<Py<PyString>>,
{
unsafe {
Ok(ffi::PyObject_HasAttr(self.as_ptr(), attr_name.to_object(self.py()).as_ptr()) != 0)
}
let py = self.py();
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.
///
/// This is equivalent to the Python expression `self.attr_name`.
///
/// If calling this method becomes performance-critical, the [`intern!`] macro can be used
/// to intern `attr_name`, thereby avoiding repeated temporary allocations of Python strings.
/// To avoid repeated temporary allocations of Python strings, the [`intern!`] macro can be used
/// to intern `attr_name`.
///
/// # Example: `intern!`ing the attribute name
///
@ -130,13 +134,14 @@ impl PyAny {
/// ```
pub fn getattr<N>(&self, attr_name: N) -> PyResult<&PyAny>
where
N: ToPyObject,
N: IntoPy<Py<PyString>>,
{
let py = self.py();
let attr_name = attr_name.into_py(py);
unsafe {
self.py().from_owned_ptr_or_err(ffi::PyObject_GetAttr(
self.as_ptr(),
attr_name.to_object(self.py()).as_ptr(),
))
let ret = ffi::PyObject_GetAttr(self.as_ptr(), attr_name.as_ptr());
py.from_owned_ptr_or_err(ret)
}
}
@ -144,8 +149,8 @@ impl PyAny {
///
/// 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 intern `attr_name`, thereby avoiding repeated temporary allocations of Python strings.
/// To avoid repeated temporary allocations of Python strings, the [`intern!`] macro can be used
/// to intern `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<()>
where
N: ToPyObject,
N: IntoPy<Py<PyString>>,
V: ToPyObject,
{
let py = self.py();
let attr_name = attr_name.into_py(py);
let value = value.to_object(py);
unsafe {
err::error_on_minusone(
py,
ffi::PyObject_SetAttr(
self.as_ptr(),
attr_name.to_object(py).as_ptr(),
value.to_object(py).as_ptr(),
),
)
let ret = ffi::PyObject_SetAttr(self.as_ptr(), attr_name.as_ptr(), value.as_ptr());
err::error_on_minusone(py, ret)
}
}
/// Deletes an attribute.
///
/// 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<()>
where
N: ToPyObject,
N: IntoPy<Py<PyString>>,
{
let py = self.py();
let attr_name = attr_name.into_py(py);
unsafe {
err::error_on_minusone(
self.py(),
ffi::PyObject_DelAttr(self.as_ptr(), attr_name.to_object(py).as_ptr()),
)
let ret = ffi::PyObject_DelAttr(self.as_ptr(), attr_name.as_ptr());
err::error_on_minusone(py, ret)
}
}
@ -273,6 +278,10 @@ impl PyAny {
/// 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
/// following Python expressions:
///
@ -408,22 +417,49 @@ impl PyAny {
/// Calls the object.
///
/// 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(
&self,
args: impl IntoPy<Py<PyTuple>>,
kwargs: Option<&PyDict>,
) -> 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 result = unsafe {
let return_value = ffi::PyObject_Call(self.as_ptr(), args, kwargs);
self.py().from_owned_ptr_or_err(return_value)
};
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);
ret
}
result
}
/// Calls the object without arguments.
@ -468,25 +504,23 @@ impl PyAny {
/// ```rust
/// use pyo3::prelude::*;
///
/// const CODE: &str = r#"
/// def function(*args, **kwargs):
/// assert args == ("hello",)
/// assert kwargs == {}
/// return "called with args"
/// "#;
///
/// # fn main() -> PyResult<()> {
/// Python::with_gil(|py| -> PyResult<()> {
/// let module = PyModule::import(py, "operator")?;
/// let add = module.getattr("add")?;
/// let args = (1, 2);
/// let value = add.call1(args)?;
/// assert_eq!(value.extract::<i32>()?, 3);
/// Python::with_gil(|py| {
/// let module = PyModule::from_code(py, CODE, "", "")?;
/// let fun = module.getattr("function")?;
/// let args = ("hello",);
/// let result = fun.call1(args)?;
/// assert_eq!(result.extract::<&str>()?, "called with args");
/// 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> {
self.call(args, None)
@ -496,49 +530,51 @@ impl PyAny {
///
/// 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
///
/// ```rust
/// 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<()> {
/// Python::with_gil(|py| -> PyResult<()> {
/// let list = PyList::new(py, vec![3, 6, 5, 4, 7]);
/// let kwargs = vec![("reverse", true)].into_py_dict(py);
///
/// list.call_method("sort", (), Some(kwargs))?;
/// assert_eq!(list.extract::<Vec<i32>>()?, vec![7, 6, 5, 4, 3]);
/// Python::with_gil(|py| {
/// let module = PyModule::from_code(py, CODE, "", "")?;
/// let instance = module.getattr("a")?;
/// let args = ("hello",);
/// let kwargs = PyDict::new(py);
/// 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(())}
/// })
/// # }
/// ```
///
/// This is equivalent to the following Python code:
///
/// ```python
/// my_list = [3, 6, 5, 4, 7]
/// my_list.sort(reverse = True)
/// assert my_list == [7, 6, 5, 4, 3]
/// ```
pub fn call_method(
&self,
name: &str,
args: impl IntoPy<Py<PyTuple>>,
kwargs: Option<&PyDict>,
) -> PyResult<&PyAny> {
unsafe {
pub fn call_method<N, A>(&self, name: N, args: A, kwargs: Option<&PyDict>) -> PyResult<&PyAny>
where
N: IntoPy<Py<PyString>>,
A: IntoPy<Py<PyTuple>>,
{
let py = self.py();
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 callee = self.getattr(name)?;
let args: Py<PyTuple> = args.into_py(py);
let kwargs = kwargs.into_ptr();
let result_ptr = ffi::PyObject_Call(ptr, args, kwargs);
unsafe {
let result_ptr = ffi::PyObject_Call(callee.as_ptr(), args.as_ptr(), kwargs);
let result = py.from_owned_ptr_or_err(result_ptr);
ffi::Py_DECREF(ptr);
ffi::Py_XDECREF(args);
ffi::Py_XDECREF(kwargs);
result
}
@ -548,39 +584,46 @@ impl PyAny {
///
/// 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
///
/// ```rust
/// 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<()> {
/// Python::with_gil(|py| -> PyResult<()> {
/// let pi = PyFloat::new(py, PI);
/// let ratio = pi.call_method0("as_integer_ratio")?;
/// let (a, b) = ratio.extract::<(u64, u64)>()?;
/// assert_eq!(a, 884_279_719_003_555);
/// assert_eq!(b, 281_474_976_710_656);
/// Python::with_gil(|py| {
/// let module = PyModule::from_code(py, CODE, "", "")?;
/// let instance = module.getattr("a")?;
/// let result = instance.call_method0("method")?;
/// assert_eq!(result.extract::<&str>()?, "called with no arguments");
/// Ok(())
/// })?;
/// # Ok(())}
/// })
/// # }
/// ```
///
/// This is equivalent to the following Python code:
///
/// ```python
/// import math
///
/// a, b = math.pi.as_integer_ratio()
/// ```
pub fn call_method0(&self, name: &str) -> PyResult<&PyAny> {
pub fn call_method0<N>(&self, name: N) -> PyResult<&PyAny>
where
N: IntoPy<Py<PyString>>,
{
cfg_if::cfg_if! {
if #[cfg(all(Py_3_9, not(any(Py_LIMITED_API, PyPy))))] {
let py = self.py();
// 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()))
let name: Py<PyString> = name.into_py(py);
let ptr = ffi::PyObject_CallMethodNoArgs(self.as_ptr(), name.as_ptr());
py.from_owned_ptr_or_err(ptr)
}
} else {
self.call_method(name, (), None)
@ -592,30 +635,39 @@ impl PyAny {
///
/// 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
///
/// ```rust
/// 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<()> {
/// Python::with_gil(|py| -> PyResult<()> {
/// let list = PyList::new(py, vec![1, 3, 4]);
/// list.call_method1("insert", (1, 2))?;
/// assert_eq!(list.extract::<Vec<u8>>()?, [1, 2, 3, 4]);
/// Python::with_gil(|py| {
/// let module = PyModule::from_code(py, CODE, "", "")?;
/// let instance = module.getattr("a")?;
/// let args = ("hello",);
/// let result = instance.call_method1("method", args)?;
/// assert_eq!(result.extract::<&str>()?, "called with args");
/// Ok(())
/// })?;
/// # Ok(()) }
/// })
/// # }
/// ```
///
/// This is equivalent to the following Python code:
///
/// ```python
/// 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> {
pub fn call_method1<N, A>(&self, name: N, args: A) -> PyResult<&PyAny>
where
N: IntoPy<Py<PyString>>,
A: IntoPy<Py<PyTuple>>,
{
self.call_method(name, args, None)
}
@ -712,7 +764,7 @@ impl PyAny {
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.
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.
///
/// 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>
where
D: FromPyObject<'a>,
@ -788,11 +840,11 @@ impl PyAny {
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)`.
pub fn is_instance(&self, typ: &PyType) -> PyResult<bool> {
let result = unsafe { ffi::PyObject_IsInstance(self.as_ptr(), typ.as_ptr()) };
/// This is equivalent to the Python expression `isinstance(self, ty)`.
pub fn is_instance(&self, ty: &PyType) -> PyResult<bool> {
let result = unsafe { ffi::PyObject_IsInstance(self.as_ptr(), ty.as_ptr()) };
err::error_on_minusone(self.py(), result)?;
Ok(result == 1)
}

View File

@ -155,7 +155,7 @@ impl PyCFunction {
let (py, module) = py_or_module.into_py_and_maybe_module();
let (mod_ptr, module_name) = if let Some(m) = module {
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())
} else {
(std::ptr::null_mut(), std::ptr::null_mut())

View File

@ -8,7 +8,7 @@ use crate::exceptions;
use crate::ffi;
use crate::pyclass::PyClass;
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::str;
@ -65,8 +65,11 @@ impl PyModule {
/// ```python
/// import antigravity
/// ```
pub fn import<'p>(py: Python<'p>, name: &str) -> PyResult<&'p PyModule> {
let name: PyObject = name.into_py(py);
pub fn import<N>(py: Python<'_>, name: N) -> PyResult<&PyModule>
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())) }
}

View File

@ -4,7 +4,7 @@
use crate::exceptions::PyUnicodeDecodeError;
use crate::types::PyBytes;
use crate::{
ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyObject, PyResult, PyTryFrom, Python,
ffi, AsPyPointer, FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult, PyTryFrom, Python,
ToPyObject,
};
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.
/// See `PyString::new` for details on the conversion.
impl<'a> ToPyObject for Cow<'a, str> {

View File

@ -49,11 +49,14 @@ impl PyTraceback {
/// ```
pub fn format(&self) -> PyResult<String> {
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()) };
error_on_minusone(py, result)?;
let formatted = string_io
.getattr("getvalue")?
.getattr(intern!(py, "getvalue"))?
.call0()?
.downcast::<PyString>()?
.to_str()?

View File

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