From f7b1f5d1f146eddb0ab532f28c2bc1e75e1a6a79 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Thu, 26 Nov 2020 22:02:07 +0000 Subject: [PATCH] experimental: owned objects API --- Cargo.toml | 1 + benches/bench_call.rs | 2 + benches/bench_dict.rs | 17 +- benches/bench_experimental.rs | 214 +++++++ benches/bench_list.rs | 2 + benches/bench_set.rs | 16 +- benches/bench_tuple.rs | 2 + guide/src/experimental_objects.md | 14 + pyo3-derive-backend/src/pymethod.rs | 2 +- src/derive_utils.rs | 8 +- src/experimental/mod.rs | 50 ++ src/experimental/objects/any.rs | 560 +++++++++++++++++ src/experimental/objects/boolobject.rs | 83 +++ src/experimental/objects/bytearray.rs | 313 ++++++++++ src/experimental/objects/bytes.rs | 177 ++++++ src/experimental/objects/complex.rs | 362 +++++++++++ src/experimental/objects/datetime.rs | 329 ++++++++++ src/experimental/objects/dict.rs | 826 +++++++++++++++++++++++++ src/experimental/objects/floatob.rs | 116 ++++ src/experimental/objects/function.rs | 103 +++ src/experimental/objects/iterator.rs | 229 +++++++ src/experimental/objects/list.rs | 452 ++++++++++++++ src/experimental/objects/mod.rs | 336 ++++++++++ src/experimental/objects/module.rs | 278 +++++++++ src/experimental/objects/num.rs | 618 ++++++++++++++++++ src/experimental/objects/sequence.rs | 756 ++++++++++++++++++++++ src/experimental/objects/set.rs | 593 ++++++++++++++++++ src/experimental/objects/slice.rs | 88 +++ src/experimental/objects/str.rs | 295 +++++++++ src/experimental/objects/tuple.rs | 322 ++++++++++ src/experimental/objects/typeobject.rs | 68 ++ src/instance.rs | 40 +- src/lib.rs | 3 + src/types/boolobject.rs | 29 +- src/types/bytes.rs | 13 +- src/types/dict.rs | 73 +-- src/types/floatob.rs | 29 +- src/types/function.rs | 6 +- src/types/list.rs | 64 +- src/types/mod.rs | 37 ++ src/types/set.rs | 67 +- src/types/string.rs | 83 +-- src/types/tuple.rs | 33 +- tests/test_module.rs | 1 + 44 files changed, 7330 insertions(+), 380 deletions(-) create mode 100644 benches/bench_experimental.rs create mode 100644 guide/src/experimental_objects.md create mode 100644 src/experimental/mod.rs create mode 100644 src/experimental/objects/any.rs create mode 100644 src/experimental/objects/boolobject.rs create mode 100644 src/experimental/objects/bytearray.rs create mode 100644 src/experimental/objects/bytes.rs create mode 100644 src/experimental/objects/complex.rs create mode 100644 src/experimental/objects/datetime.rs create mode 100644 src/experimental/objects/dict.rs create mode 100644 src/experimental/objects/floatob.rs create mode 100644 src/experimental/objects/function.rs create mode 100644 src/experimental/objects/iterator.rs create mode 100644 src/experimental/objects/list.rs create mode 100644 src/experimental/objects/mod.rs create mode 100644 src/experimental/objects/module.rs create mode 100644 src/experimental/objects/num.rs create mode 100644 src/experimental/objects/sequence.rs create mode 100644 src/experimental/objects/set.rs create mode 100644 src/experimental/objects/slice.rs create mode 100644 src/experimental/objects/str.rs create mode 100644 src/experimental/objects/tuple.rs create mode 100644 src/experimental/objects/typeobject.rs diff --git a/Cargo.toml b/Cargo.toml index 16b99de07e3..0f542ad0e3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ hashbrown = { version = "0.9", optional = true } assert_approx_eq = "1.1.0" trybuild = "1.0.23" rustversion = "1.0" +maplit = "1.0" [features] default = ["macros"] diff --git a/benches/bench_call.rs b/benches/bench_call.rs index 45b4ffd3d11..505217033ed 100644 --- a/benches/bench_call.rs +++ b/benches/bench_call.rs @@ -24,6 +24,7 @@ fn bench_call_0(b: &mut Bencher) { let foo = module.getattr("foo").unwrap(); b.iter(|| { + let _pool = unsafe { py.new_pool() }; for _ in 0..1000 { foo.call0().unwrap(); } @@ -45,6 +46,7 @@ fn bench_call_method_0(b: &mut Bencher) { let foo = module.getattr("Foo").unwrap().call0().unwrap(); b.iter(|| { + let _pool = unsafe { py.new_pool() }; for _ in 0..1000 { foo.call_method0("foo").unwrap(); } diff --git a/benches/bench_dict.rs b/benches/bench_dict.rs index c1a1121b7ae..1bd04c7b681 100644 --- a/benches/bench_dict.rs +++ b/benches/bench_dict.rs @@ -14,6 +14,7 @@ fn iter_dict(b: &mut Bencher) { let dict = (0..LEN as u64).map(|i| (i, i * 2)).into_py_dict(py); let mut sum = 0; b.iter(|| { + let _pool = unsafe { py.new_pool() }; for (k, _v) in dict.iter() { let i: u64 = k.extract().unwrap(); sum += i; @@ -29,6 +30,7 @@ fn dict_get_item(b: &mut Bencher) { let dict = (0..LEN as u64).map(|i| (i, i * 2)).into_py_dict(py); let mut sum = 0; b.iter(|| { + let _pool = unsafe { py.new_pool() }; for i in 0..LEN { sum += dict.get_item(i).unwrap().extract::().unwrap(); } @@ -41,7 +43,10 @@ fn extract_hashmap(b: &mut Bencher) { let py = gil.python(); const LEN: usize = 100_000; let dict = (0..LEN as u64).map(|i| (i, i * 2)).into_py_dict(py); - b.iter(|| HashMap::::extract(dict)); + b.iter(|| { + let _pool = unsafe { py.new_pool() }; + HashMap::::extract(dict) + }); } #[bench] @@ -50,7 +55,10 @@ fn extract_btreemap(b: &mut Bencher) { let py = gil.python(); const LEN: usize = 100_000; let dict = (0..LEN as u64).map(|i| (i, i * 2)).into_py_dict(py); - b.iter(|| BTreeMap::::extract(dict)); + b.iter(|| { + let _pool = unsafe { py.new_pool() }; + BTreeMap::::extract(dict) + }); } #[bench] @@ -60,5 +68,8 @@ fn extract_hashbrown_map(b: &mut Bencher) { let py = gil.python(); const LEN: usize = 100_000; let dict = (0..LEN as u64).map(|i| (i, i * 2)).into_py_dict(py); - b.iter(|| hashbrown::HashMap::::extract(dict)); + b.iter(|| { + let _pool = unsafe { py.new_pool() }; + hashbrown::HashMap::::extract(dict) + }); } diff --git a/benches/bench_experimental.rs b/benches/bench_experimental.rs new file mode 100644 index 00000000000..08fed36e4d8 --- /dev/null +++ b/benches/bench_experimental.rs @@ -0,0 +1,214 @@ +#![feature(test)] + +extern crate test; +use pyo3::experimental::objects::{IntoPyDict, PyList, PySet, PyTuple}; +use pyo3::experimental::prelude::*; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +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 iter_dict(b: &mut Bencher) { + let gil = Python::acquire_gil(); + let py = gil.python(); + const LEN: usize = 100_000; + let dict = (0..LEN as u64).map(|i| (i, i * 2)).into_py_dict(py); + let mut sum = 0; + b.iter(|| { + for (k, _v) in dict.iter() { + let i: u64 = k.extract().unwrap(); + sum += i; + } + }); +} + +#[bench] +fn dict_get_item(b: &mut Bencher) { + let gil = Python::acquire_gil(); + let py = gil.python(); + const LEN: usize = 50_000; + let dict = (0..LEN as u64).map(|i| (i, i * 2)).into_py_dict(py); + let dict_ref = &*dict; + let mut sum = 0; + b.iter(|| { + for i in 0..LEN { + sum += dict_ref.get_item(i).unwrap().extract::().unwrap(); + } + }); +} + +#[bench] +fn extract_hashmap(b: &mut Bencher) { + let gil = Python::acquire_gil(); + let py = gil.python(); + const LEN: usize = 100_000; + let dict = (0..LEN as u64).map(|i| (i, i * 2)).into_py_dict(py); + b.iter(|| HashMap::::extract(&dict)); +} + +#[bench] +fn extract_btreemap(b: &mut Bencher) { + let gil = Python::acquire_gil(); + let py = gil.python(); + const LEN: usize = 100_000; + let dict = (0..LEN as u64).map(|i| (i, i * 2)).into_py_dict(py); + b.iter(|| BTreeMap::::extract(&dict)); +} + +#[bench] +#[cfg(feature = "hashbrown")] +fn extract_hashbrown_map(b: &mut Bencher) { + let gil = Python::acquire_gil(); + let py = gil.python(); + const LEN: usize = 100_000; + let dict = (0..LEN as u64).map(|i| (i, i * 2)).into_py_dict(py); + b.iter(|| hashbrown::HashMap::::extract(dict)); +} + +#[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(); + } + }); + }) +} + +#[bench] +fn iter_list(b: &mut Bencher) { + let gil = Python::acquire_gil(); + let py = gil.python(); + const LEN: usize = 100_000; + let list = PyList::new(py, 0..LEN); + let mut sum = 0; + b.iter(|| { + for x in list.iter() { + let i: u64 = x.extract().unwrap(); + sum += i; + } + }); +} + +#[bench] +fn list_get_item(b: &mut Bencher) { + let gil = Python::acquire_gil(); + let py = gil.python(); + const LEN: usize = 50_000; + let list = PyList::new(py, 0..LEN); + let mut sum = 0; + b.iter(|| { + for i in 0..LEN { + sum += list.get_item(i as isize).extract::().unwrap(); + } + }); +} + +#[bench] +fn iter_set(b: &mut Bencher) { + let gil = Python::acquire_gil(); + let py = gil.python(); + const LEN: usize = 100_000; + let set = PySet::new(py, &(0..LEN).collect::>()).unwrap(); + let mut sum = 0; + b.iter(|| { + for x in set.iter() { + let i: u64 = x.extract().unwrap(); + sum += i; + } + }); +} + +#[bench] +fn extract_hashset(b: &mut Bencher) { + let gil = Python::acquire_gil(); + let py = gil.python(); + const LEN: usize = 100_000; + let set = PySet::new(py, &(0..LEN).collect::>()).unwrap(); + b.iter(|| HashSet::::extract(&set)); +} + +#[bench] +fn extract_btreeset(b: &mut Bencher) { + let gil = Python::acquire_gil(); + let py = gil.python(); + const LEN: usize = 100_000; + let set = PySet::new(py, &(0..LEN).collect::>()).unwrap(); + b.iter(|| BTreeSet::::extract(&set)); +} + +#[bench] +#[cfg(feature = "hashbrown")] +fn extract_hashbrown_set(b: &mut Bencher) { + let gil = Python::acquire_gil(); + let py = gil.python(); + const LEN: usize = 100_000; + let set = PySet::new(py, &(0..LEN).collect::>()).unwrap(); + b.iter(|| hashbrown::HashSet::::extract(set)); +} + +#[bench] +fn iter_tuple(b: &mut Bencher) { + let gil = Python::acquire_gil(); + let py = gil.python(); + const LEN: usize = 100_000; + let tuple = PyTuple::new(py, 0..LEN); + let mut sum = 0; + b.iter(|| { + for x in tuple.iter() { + let i: u64 = x.extract().unwrap(); + sum += i; + } + }); +} + +#[bench] +fn tuple_get_item(b: &mut Bencher) { + let gil = Python::acquire_gil(); + let py = gil.python(); + const LEN: usize = 50_000; + let tuple = PyTuple::new(py, 0..LEN); + let mut sum = 0; + b.iter(|| { + for i in 0..LEN { + sum += tuple.get_item(i).extract::().unwrap(); + } + }); +} diff --git a/benches/bench_list.rs b/benches/bench_list.rs index 48c5f5b621e..33a1558046b 100644 --- a/benches/bench_list.rs +++ b/benches/bench_list.rs @@ -13,6 +13,7 @@ fn iter_list(b: &mut Bencher) { let list = PyList::new(py, 0..LEN); let mut sum = 0; b.iter(|| { + let _pool = unsafe { py.new_pool() }; for x in list.iter() { let i: u64 = x.extract().unwrap(); sum += i; @@ -28,6 +29,7 @@ fn list_get_item(b: &mut Bencher) { let list = PyList::new(py, 0..LEN); let mut sum = 0; b.iter(|| { + let _pool = unsafe { py.new_pool() }; for i in 0..LEN { sum += list.get_item(i as isize).extract::().unwrap(); } diff --git a/benches/bench_set.rs b/benches/bench_set.rs index ef0143fa35c..2c9f7215858 100644 --- a/benches/bench_set.rs +++ b/benches/bench_set.rs @@ -14,6 +14,7 @@ fn iter_set(b: &mut Bencher) { let set = PySet::new(py, &(0..LEN).collect::>()).unwrap(); let mut sum = 0; b.iter(|| { + let _pool = unsafe { py.new_pool() }; for x in set.iter() { let i: u64 = x.extract().unwrap(); sum += i; @@ -27,7 +28,10 @@ fn extract_hashset(b: &mut Bencher) { let py = gil.python(); const LEN: usize = 100_000; let set = PySet::new(py, &(0..LEN).collect::>()).unwrap(); - b.iter(|| HashSet::::extract(set)); + b.iter(|| { + let _pool = unsafe { py.new_pool() }; + HashSet::::extract(set) + }); } #[bench] @@ -36,7 +40,10 @@ fn extract_btreeset(b: &mut Bencher) { let py = gil.python(); const LEN: usize = 100_000; let set = PySet::new(py, &(0..LEN).collect::>()).unwrap(); - b.iter(|| BTreeSet::::extract(set)); + b.iter(|| { + let _pool = unsafe { py.new_pool() }; + BTreeSet::::extract(set) + }); } #[bench] @@ -46,5 +53,8 @@ fn extract_hashbrown_set(b: &mut Bencher) { let py = gil.python(); const LEN: usize = 100_000; let set = PySet::new(py, &(0..LEN).collect::>()).unwrap(); - b.iter(|| hashbrown::HashSet::::extract(set)); + b.iter(|| { + let _pool = unsafe { py.new_pool() }; + hashbrown::HashSet::::extract(set) + }); } diff --git a/benches/bench_tuple.rs b/benches/bench_tuple.rs index a8db6e3f30c..7ef7daaddfe 100644 --- a/benches/bench_tuple.rs +++ b/benches/bench_tuple.rs @@ -13,6 +13,7 @@ fn iter_tuple(b: &mut Bencher) { let tuple = PyTuple::new(py, 0..LEN); let mut sum = 0; b.iter(|| { + let _pool = unsafe { py.new_pool() }; for x in tuple.iter() { let i: u64 = x.extract().unwrap(); sum += i; @@ -28,6 +29,7 @@ fn tuple_get_item(b: &mut Bencher) { let tuple = PyTuple::new(py, 0..LEN); let mut sum = 0; b.iter(|| { + let _pool = unsafe { py.new_pool() }; for i in 0..LEN { sum += tuple.get_item(i).extract::().unwrap(); } diff --git a/guide/src/experimental_objects.md b/guide/src/experimental_objects.md new file mode 100644 index 00000000000..9e306a884f7 --- /dev/null +++ b/guide/src/experimental_objects.md @@ -0,0 +1,14 @@ +# Experimental: Objects + +Practical differences: + - No longer possible to extract `HashMap<&str, &str>` (e.g.) from a PyDict - these strings cannot be guaranteed to be safe to borrow without pyo3 owned references. Instead you should use `HashMap`. + - Iterators from PyOwned must now be prefixed with &* - e.g. `in set` -> `in &*set` + - return values `&'py PyAny` -> `PyOwned<'py, Any>` + - Distinction between _types_ `Any` and _objects_ `PyAny`. + + - PyString -> PyStr + - PyLong -> PyInt + +TODO: + - Might want to create a new Python type which returns the new signatures from e.g. pyo3::experimental. This might be too painful. + - Probably provide "experimental" forms of all the macros to make the migration possible. diff --git a/pyo3-derive-backend/src/pymethod.rs b/pyo3-derive-backend/src/pymethod.rs index ddfa4a1831b..83940cb84db 100644 --- a/pyo3-derive-backend/src/pymethod.rs +++ b/pyo3-derive-backend/src/pymethod.rs @@ -481,7 +481,7 @@ fn impl_arg_param( if spec.is_args(&name) { return quote! { - let #arg_name = <#ty as pyo3::FromPyObject>::extract(_args.as_ref()) + let #arg_name = <#ty as pyo3::FromPyObject>::extract(_args) .map_err(#transform_error)?; }; } else if spec.is_kwargs(&name) { diff --git a/src/derive_utils.rs b/src/derive_utils.rs index cd0f5bde1ff..ea36f83d64c 100644 --- a/src/derive_utils.rs +++ b/src/derive_utils.rs @@ -188,13 +188,13 @@ impl PyBaseTypeUtils for T { /// Utility trait to enable &PyClass as a pymethod/function argument #[doc(hidden)] -pub trait ExtractExt<'a> { - type Target: crate::FromPyObject<'a>; +pub trait ExtractExt<'py> { + type Target: crate::experimental::FromPyObject<'py, 'py>; } -impl<'a, T> ExtractExt<'a> for T +impl<'py, T> ExtractExt<'py> for T where - T: crate::FromPyObject<'a>, + T: crate::experimental::FromPyObject<'py, 'py>, { type Target = T; } diff --git a/src/experimental/mod.rs b/src/experimental/mod.rs new file mode 100644 index 00000000000..bcb68737cfc --- /dev/null +++ b/src/experimental/mod.rs @@ -0,0 +1,50 @@ +pub mod objects; + +pub use objects::{FromPyObject, PyNativeObject, PyTryFrom}; + +pub mod types { + pub use crate::types::experimental::*; +} + +/// Alternative prelude to use the new experimental types / traits. +pub mod prelude { + pub use super::{FromPyObject, PyTryFrom}; + + pub use crate::err::{PyErr, PyResult}; + pub use crate::gil::GILGuard; + pub use crate::instance::{Py, PyObject}; + pub use crate::pycell::{PyCell, PyRef, PyRefMut}; + pub use crate::pyclass_init::PyClassInitializer; + pub use crate::python::Python; + pub use crate::{IntoPy, IntoPyPointer, PyTryInto, ToPyObject}; + // PyModule is only part of the prelude because we need it for the pymodule function + pub use crate::objects::{PyAny, PyModule}; + #[cfg(feature = "macros")] + pub use pyo3cls::{pyclass, pyfunction, pymethods, pymodule, pyproto, FromPyObject}; +} + +use crate::Python; + +/// Conversion trait that allows various objects to be converted into `PyObject`. +pub trait ToPyObject { + /// Converts self into a Python object. + fn to_object<'py>(&self, py: Python<'py>) -> PyObject<'py>; +} + +impl ToPyObject for T +where + T: crate::ToPyObject, +{ + /// Converts self into a Python object. + fn to_object<'py>(&self, py: Python<'py>) -> PyObject<'py> { + use crate::IntoPyPointer; + unsafe { + PyObject::from_raw_or_panic( + py, + ::to_object(self, py).into_ptr(), + ) + } + } +} + +type PyObject<'py> = objects::PyAny<'py>; diff --git a/src/experimental/objects/any.rs b/src/experimental/objects/any.rs new file mode 100644 index 00000000000..2a1b676c49c --- /dev/null +++ b/src/experimental/objects/any.rs @@ -0,0 +1,560 @@ +use crate::class::basic::CompareOp; +use crate::conversion::{AsPyPointer, IntoPy, ToBorrowedObject, ToPyObject}; +use crate::err::{PyDowncastError, PyErr, PyResult}; +use crate::exceptions::PyTypeError; +use crate::objects::{FromPyObject, PyNativeObject, PyTryFrom}; +use crate::objects::{PyDict, PyIterator, PyList, PyStr, PyType}; +use crate::type_object::PyTypeObject; +use crate::types::{Any, Tuple}; +use crate::{err, ffi, Py, PyObject, Python}; +use libc::c_int; +use std::cmp::Ordering; + +#[repr(transparent)] +pub struct PyAny<'py>(pub(crate) PyObject, pub(crate) Python<'py>); + +pyo3_native_object_base!(PyAny<'py>, Any, 'py); + +impl PartialEq for PyAny<'_> { + #[inline] + fn eq(&self, o: &PyAny) -> bool { + self.as_ptr() == o.as_ptr() + } +} + +impl AsPyPointer for PyAny<'_> { + #[inline] + fn as_ptr(&self) -> *mut ffi::PyObject { + self.0.as_ptr() + } +} + +impl Clone for PyAny<'_> { + fn clone(&self) -> Self { + Self(self.0.clone_ref(self.1), self.1) + } +} + +impl<'py> FromPyObject<'_, 'py> for PyAny<'py> { + fn extract(any: &PyAny<'py>) -> PyResult { + Ok(any.clone()) + } +} + +// unsafe impl crate::PyNativeType for PyAny {} +// unsafe impl crate::type_object::PyLayout for ffi::PyObject {} +// impl crate::type_object::PySizedLayout for ffi::PyObject {} + +// pyobject_native_type_convert!( +// PyAny, +// ffi::PyObject, +// ffi::PyBaseObject_Type, +// Some("builtins"), +// ffi::PyObject_Check +// ); + +// pyobject_native_type_extract!(PyAny); + +// pyobject_native_type_fmt!(PyAny); + +impl<'py> PyAny<'py> { + /// Convert this PyAny to a concrete Python type. + pub fn downcast<'a, T>(&'a self) -> Result<&'a T, PyDowncastError> + where + T: PyTryFrom<'a, 'py>, + { + ::try_from(self) + } + + /// Extracts some type from the Python object. + /// + /// This is a wrapper function around `FromPyObject::extract()`. + pub fn extract<'a, D>(&'a self) -> PyResult + where + D: FromPyObject<'a, 'py>, + { + FromPyObject::extract(self) + } + + /// Determines whether this object has the given attribute. + /// + /// This is equivalent to the Python expression `hasattr(self, attr_name)`. + pub fn hasattr(&self, attr_name: N) -> PyResult + where + N: ToPyObject, + { + attr_name.with_borrowed_ptr(self.py(), |attr_name| unsafe { + Ok(ffi::PyObject_HasAttr(self.as_ptr(), attr_name) != 0) + }) + } + + /// Retrieves an attribute value. + /// + /// This is equivalent to the Python expression `self.attr_name`. + pub fn getattr(&self, attr_name: N) -> PyResult + where + N: ToPyObject, + { + attr_name.with_borrowed_ptr(self.py(), |attr_name| unsafe { + Self::from_raw_or_fetch_err(self.py(), ffi::PyObject_GetAttr(self.as_ptr(), attr_name)) + }) + } + + /// Sets an attribute value. + /// + /// This is equivalent to the Python expression `self.attr_name = value`. + pub fn setattr(&self, attr_name: N, value: V) -> PyResult<()> + where + N: ToBorrowedObject, + V: ToBorrowedObject, + { + attr_name.with_borrowed_ptr(self.py(), move |attr_name| { + value.with_borrowed_ptr(self.py(), |value| unsafe { + err::error_on_minusone( + self.py(), + ffi::PyObject_SetAttr(self.as_ptr(), attr_name, value), + ) + }) + }) + } + + /// Deletes an attribute. + /// + /// This is equivalent to the Python expression `del self.attr_name`. + pub fn delattr(&self, attr_name: N) -> PyResult<()> + where + N: ToPyObject, + { + attr_name.with_borrowed_ptr(self.py(), |attr_name| unsafe { + err::error_on_minusone(self.py(), ffi::PyObject_DelAttr(self.as_ptr(), attr_name)) + }) + } + + /// Compares two Python objects. + /// + /// This is equivalent to: + /// ```python + /// if self == other: + /// return Equal + /// elif a < b: + /// return Less + /// elif a > b: + /// return Greater + /// else: + /// raise TypeError("PyAny::compare(): All comparisons returned false") + /// ``` + pub fn compare(&self, other: O) -> PyResult + where + O: ToPyObject, + { + let py = self.py(); + // Almost the same as ffi::PyObject_RichCompareBool, but this one doesn't try self == other. + // See https://github.com/PyO3/pyo3/issues/985 for more. + let do_compare = |other, op| unsafe { + PyObject::from_owned_ptr_or_err(py, ffi::PyObject_RichCompare(self.as_ptr(), other, op)) + .and_then(|obj| obj.is_true(py)) + }; + other.with_borrowed_ptr(py, |other| { + if do_compare(other, ffi::Py_EQ)? { + Ok(Ordering::Equal) + } else if do_compare(other, ffi::Py_LT)? { + Ok(Ordering::Less) + } else if do_compare(other, ffi::Py_GT)? { + Ok(Ordering::Greater) + } else { + Err(PyTypeError::new_err( + "PyAny::compare(): All comparisons returned false", + )) + } + }) + } + + /// Compares two Python objects. + /// + /// Depending on the value of `compare_op`, this is equivalent to one of the + /// following Python expressions: + /// * CompareOp::Eq: `self == other` + /// * CompareOp::Ne: `self != other` + /// * CompareOp::Lt: `self < other` + /// * CompareOp::Le: `self <= other` + /// * CompareOp::Gt: `self > other` + /// * CompareOp::Ge: `self >= other` + pub fn rich_compare(&self, other: O, compare_op: CompareOp) -> PyResult + where + O: ToPyObject, + { + unsafe { + let result = other.with_borrowed_ptr(self.py(), |other| { + ffi::PyObject_RichCompare(self.as_ptr(), other, compare_op as c_int) + }); + Self::from_raw_or_fetch_err(self.py(), result) + } + } + + /// Determines whether this object is callable. + pub fn is_callable(&self) -> bool { + unsafe { ffi::PyCallable_Check(self.as_ptr()) != 0 } + } + + /// Calls the object. + /// + /// This is equivalent to the Python expression `self(*args, **kwargs)`. + pub fn call(&self, args: impl IntoPy>, kwargs: Option<&PyDict>) -> PyResult { + let args = args.into_py(self.py()); + let kwargs_ptr = kwargs.map_or(std::ptr::null_mut(), |dict| dict.as_ptr()); + unsafe { + let result = ffi::PyObject_Call(self.as_ptr(), args.as_ptr(), kwargs_ptr); + Self::from_raw_or_fetch_err(self.py(), result) + } + } + + /// Calls the object without arguments. + /// + /// This is equivalent to the Python expression `self()`. + pub fn call0(&self) -> PyResult { + self.call((), None) + } + + /// Calls the object with only positional arguments. + /// + /// This is equivalent to the Python expression `self(*args)`. + pub fn call1(&self, args: impl IntoPy>) -> PyResult { + self.call(args, None) + } + + /// Calls a method on the object. + /// + /// This is equivalent to the Python expression `self.name(*args, **kwargs)`. + /// + /// # Example + /// ```rust + /// # use pyo3::experimental::prelude::*; + /// use pyo3::experimental::objects::{IntoPyDict, PyNativeObject}; + /// + /// let gil = Python::acquire_gil(); + /// let py = gil.python(); + /// let list = vec![3, 6, 5, 4, 7].to_object(py); + /// let dict = vec![("reverse", true)].into_py_dict(py); + /// list.call_method(py, "sort", (), Some(dict.as_owned_ref())).unwrap(); + /// assert_eq!(list.extract::>(py).unwrap(), vec![7, 6, 5, 4, 3]); + /// + /// let new_element = 1.to_object(py); + /// list.call_method(py, "append", (new_element,), None).unwrap(); + /// assert_eq!(list.extract::>(py).unwrap(), vec![7, 6, 5, 4, 3, 1]); + /// ``` + pub fn call_method( + &self, + name: &str, + args: impl IntoPy>, + kwargs: Option<&PyDict>, + ) -> PyResult { + name.with_borrowed_ptr(self.py(), |name| unsafe { + let py = self.py(); + let ptr = ffi::PyObject_GetAttr(self.as_ptr(), name); + if ptr.is_null() { + return Err(PyErr::fetch(py)); + } + let args = args.into_py(self.py()); + let kwargs_ptr = kwargs.map_or(std::ptr::null_mut(), |dict| dict.as_ptr()); + let result_ptr = ffi::PyObject_Call(ptr, args.as_ptr(), kwargs_ptr); + let result = Self::from_raw_or_fetch_err(self.py(), result_ptr); + ffi::Py_DECREF(ptr); + result + }) + } + + /// Calls a method on the object without arguments. + /// + /// This is equivalent to the Python expression `self.name()`. + pub fn call_method0(&self, name: &str) -> PyResult { + self.call_method(name, (), None) + } + + /// 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, name: &str, args: impl IntoPy>) -> PyResult { + self.call_method(name, args, None) + } + + /// Returns whether the object is considered to be true. + /// + /// This is equivalent to the Python expression `bool(self)`. + pub fn is_true(&self) -> PyResult { + let v = unsafe { ffi::PyObject_IsTrue(self.as_ptr()) }; + if v == -1 { + Err(PyErr::fetch(self.py())) + } else { + Ok(v != 0) + } + } + + /// Returns whether the object is considered to be None. + /// + /// This is equivalent to the Python expression `self is None`. + pub fn is_none(&self) -> bool { + unsafe { ffi::Py_None() == self.as_ptr() } + } + + /// Returns true if the sequence or mapping has a length of 0. + /// + /// This is equivalent to the Python expression `len(self) == 0`. + pub fn is_empty(&self) -> PyResult { + self.len().map(|l| l == 0) + } + + /// Gets an item from the collection. + /// + /// This is equivalent to the Python expression `self[key]`. + pub fn get_item(&self, key: K) -> PyResult + where + K: ToBorrowedObject, + { + key.with_borrowed_ptr(self.py(), |key| unsafe { + Self::from_raw_or_fetch_err(self.py(), ffi::PyObject_GetItem(self.as_ptr(), key)) + }) + } + + /// Sets a collection item value. + /// + /// This is equivalent to the Python expression `self[key] = value`. + pub fn set_item(&self, key: K, value: V) -> PyResult<()> + where + K: ToBorrowedObject, + V: ToBorrowedObject, + { + key.with_borrowed_ptr(self.py(), move |key| { + value.with_borrowed_ptr(self.py(), |value| unsafe { + err::error_on_minusone(self.py(), ffi::PyObject_SetItem(self.as_ptr(), key, value)) + }) + }) + } + + /// Deletes an item from the collection. + /// + /// This is equivalent to the Python expression `del self[key]`. + pub fn del_item(&self, key: K) -> PyResult<()> + where + K: ToBorrowedObject, + { + key.with_borrowed_ptr(self.py(), |key| unsafe { + err::error_on_minusone(self.py(), ffi::PyObject_DelItem(self.as_ptr(), key)) + }) + } + + /// Takes an object and returns an iterator for it. + /// + /// This is typically a new iterator but if the argument is an iterator, + /// this returns itself. + pub fn iter(&self) -> PyResult> { + PyIterator::from_object(self.py(), self) + } + + /// Returns the Python type object for this object's type. + pub fn get_type(&self) -> PyType<'py> { + unsafe { + PyType(PyAny::from_borrowed_ptr_or_panic( + self.py(), + ffi::Py_TYPE(self.as_ptr()) as _, + )) + } + } + + /// Returns the Python type pointer for this object. + #[inline] + pub fn get_type_ptr(&self) -> *mut ffi::PyTypeObject { + unsafe { ffi::Py_TYPE(self.as_ptr()) } + } + + /// Returns the reference count for the Python object. + pub fn get_refcnt(&self) -> isize { + unsafe { ffi::Py_REFCNT(self.as_ptr()) } + } + + /// Computes the "repr" representation of self. + /// + /// This is equivalent to the Python expression `repr(self)`. + pub fn repr(&self) -> PyResult> { + unsafe { + Self::from_raw_or_fetch_err(self.py(), ffi::PyObject_Repr(self.as_ptr())).map(PyStr) + } + } + + /// Computes the "str" representation of self. + /// + /// This is equivalent to the Python expression `str(self)`. + pub fn str(&self) -> PyResult> { + unsafe { + Self::from_raw_or_fetch_err(self.py(), ffi::PyObject_Str(self.as_ptr())).map(PyStr) + } + } + + /// Retrieves the hash code of self. + /// + /// This is equivalent to the Python expression `hash(self)`. + pub fn hash(&self) -> PyResult { + let v = unsafe { ffi::PyObject_Hash(self.as_ptr()) }; + if v == -1 { + Err(PyErr::fetch(self.py())) + } else { + Ok(v) + } + } + + /// Returns the length of the sequence or mapping. + /// + /// This is equivalent to the Python expression `len(self)`. + pub fn len(&self) -> PyResult { + let v = unsafe { ffi::PyObject_Size(self.as_ptr()) }; + if v == -1 { + Err(PyErr::fetch(self.py())) + } else { + Ok(v as usize) + } + } + + /// Returns the list of attributes of this object. + /// + /// This is equivalent to the Python expression `dir(self)`. + pub fn dir(&self) -> PyList<'py> { + unsafe { + PyList(Self::from_raw_or_panic( + self.py(), + ffi::PyObject_Dir(self.as_ptr()), + )) + } + } + + /// Checks whether this object is an instance of type `T`. + /// + /// This is equivalent to the Python expression `isinstance(self, T)`. + pub fn is_instance(&self) -> PyResult { + T::type_object(self.py()).is_instance(self) + } + + pub(crate) fn from_type_any(any: &&'py Any) -> &'py Self { + + unsafe { &*(any as *const &'py Any as *const Self) } + } + + /// CONVERSION FUNCTIONS + + #[inline] + pub(crate) unsafe fn from_raw(py: Python<'py>, ptr: *mut ffi::PyObject) -> Option { + Py::from_owned_ptr_or_opt(py, ptr).map(|obj| Self(obj, py)) + } + + // Creates a PyOwned without checking the type. + #[inline] + pub(crate) unsafe fn from_raw_or_fetch_err( + py: Python<'py>, + ptr: *mut ffi::PyObject, + ) -> PyResult { + Self::from_raw(py, ptr).ok_or_else(|| PyErr::fetch(py)) + } + + #[inline] + pub(crate) unsafe fn from_raw_or_panic(py: Python<'py>, ptr: *mut ffi::PyObject) -> Self { + Self(Py::from_owned_ptr(py, ptr), py) + } + + #[inline] + pub(crate) unsafe fn from_borrowed_ptr( + py: Python<'py>, + ptr: *mut ffi::PyObject, + ) -> Option { + Py::from_borrowed_ptr_or_opt(py, ptr).map(|obj| Self(obj, py)) + } + + #[inline] + pub(crate) unsafe fn from_borrowed_ptr_or_fetch_err( + py: Python<'py>, + ptr: *mut ffi::PyObject, + ) -> PyResult { + Py::from_borrowed_ptr_or_err(py, ptr).map(|obj| Self(obj, py)) + } + + #[inline] + pub(crate) unsafe fn from_borrowed_ptr_or_panic( + py: Python<'py>, + ptr: *mut ffi::PyObject, + ) -> Self { + Self(Py::from_borrowed_ptr(py, ptr), py) + } +} + +#[cfg(test)] +mod test { + use crate::experimental::ToPyObject; + use crate::objects::IntoPyDict; + use crate::types::{Int, List}; + use crate::Python; + + #[test] + fn test_call_for_non_existing_method() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let a = py.eval("42", None, None).unwrap(); + a.call_method0("__str__").unwrap(); // ok + assert!(a.call_method("nonexistent_method", (1,), None).is_err()); + assert!(a.call_method0("nonexistent_method").is_err()); + assert!(a.call_method1("nonexistent_method", (1,)).is_err()); + } + + #[test] + fn test_call_with_kwargs() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let list = vec![3, 6, 5, 4, 7].to_object(py); + let dict = vec![("reverse", true)].into_py_dict(py); + list.call_method("sort", (), Some(&dict)).unwrap(); + assert_eq!(list.extract::>().unwrap(), vec![7, 6, 5, 4, 3]); + } + + #[test] + fn test_type() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let obj = py.eval("42", None, None).unwrap(); + assert_eq!(obj.get_type().as_type_ptr(), obj.get_type_ptr()) + } + + #[test] + fn test_dir() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let obj = py.eval("42", None, None).unwrap(); + let dir = py + .eval("dir(42)", None, None) + .unwrap() + .downcast::() + .unwrap() + .to_owned(); + let a = obj + .dir() + .into_iter() + .map(|x| x.extract::().unwrap()); + let b = dir.into_iter().map(|x| x.extract::().unwrap()); + assert!(a.eq(b)); + } + + #[test] + fn test_nan_eq() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let nan = py.eval("float('nan')", None, None).unwrap(); + assert!(nan.compare(nan).is_err()); + } + + #[test] + fn test_any_isinstance() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let x = 5.to_object(py); + assert!(x.is_instance::().unwrap()); + + let l = vec![&x, &x].to_object(py); + assert!(l.is_instance::().unwrap()); + } +} diff --git a/src/experimental/objects/boolobject.rs b/src/experimental/objects/boolobject.rs new file mode 100644 index 00000000000..d17eb47f13b --- /dev/null +++ b/src/experimental/objects/boolobject.rs @@ -0,0 +1,83 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors +use crate::{ + ffi, + objects::{FromPyObject, PyAny}, + types::Bool, + AsPyPointer, IntoPy, PyObject, PyResult, Python, ToPyObject, +}; + +/// Represents a Python `bool`. +#[repr(transparent)] +pub struct PyBool<'py>(pub(crate) PyAny<'py>); + +pyo3_native_object!(PyBool<'py>, Bool, 'py); + +impl<'py> PyBool<'py> { + /// Depending on `val`, returns `true` or `false`. + #[inline] + pub fn new(py: Python<'py>, val: bool) -> PyBool<'py> { + unsafe { + Self(PyAny::from_borrowed_ptr_or_panic( + py, + if val { ffi::Py_True() } else { ffi::Py_False() }, + )) + } + } + + /// Gets whether this boolean is `true`. + #[inline] + pub fn is_true(&self) -> bool { + self.as_ptr() == unsafe { crate::ffi::Py_True() } + } +} + +/// Converts a Rust `bool` to a Python `bool`. +impl ToPyObject for bool { + #[inline] + fn to_object(&self, py: Python) -> PyObject { + PyBool::new(py, *self).into() + } +} + +impl IntoPy for bool { + #[inline] + fn into_py(self, py: Python) -> PyObject { + PyBool::new(py, self).into() + } +} + +/// Converts a Python `bool` to a Rust `bool`. +/// +/// Fails with `TypeError` if the input is not a Python `bool`. +impl FromPyObject<'_, '_> for bool { + fn extract(obj: &PyAny) -> PyResult { + Ok(obj.downcast::()?.is_true()) + } +} + +#[cfg(test)] +mod test { + use crate::objects::PyBool; + use crate::Python; + use crate::ToPyObject; + + #[test] + fn test_true() { + let gil = Python::acquire_gil(); + let py = gil.python(); + assert!(PyBool::new(py, true).is_true()); + let t = PyBool::new(py, true); + assert_eq!(true, t.extract().unwrap()); + assert_eq!(true.to_object(py), PyBool::new(py, true).into()); + } + + #[test] + fn test_false() { + let gil = Python::acquire_gil(); + let py = gil.python(); + assert!(!PyBool::new(py, false).is_true()); + let t = PyBool::new(py, false); + assert_eq!(false, t.extract().unwrap()); + assert_eq!(false.to_object(py), PyBool::new(py, false).into()); + } +} diff --git a/src/experimental/objects/bytearray.rs b/src/experimental/objects/bytearray.rs new file mode 100644 index 00000000000..66cd09aa3bf --- /dev/null +++ b/src/experimental/objects/bytearray.rs @@ -0,0 +1,313 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors +use crate::err::{PyErr, PyResult}; +use crate::objects::PyNativeObject; +use crate::{ffi, objects::PyAny, types::ByteArray, AsPyPointer, Python}; +use std::os::raw::c_char; +use std::slice; + +/// Represents a Python `bytearray`. +#[repr(transparent)] +pub struct PyByteArray<'py>(pub(crate) PyAny<'py>); + +pyo3_native_object!(PyByteArray<'py>, ByteArray, 'py); + +impl<'py> PyByteArray<'py> { + /// Creates a new Python bytearray object. + /// + /// The byte string is initialized by copying the data from the `&[u8]`. + pub fn new(py: Python<'py>, src: &[u8]) -> Self { + let ptr = src.as_ptr() as *const c_char; + let len = src.len() as ffi::Py_ssize_t; + unsafe { + Self(PyAny::from_raw_or_panic( + py, + ffi::PyByteArray_FromStringAndSize(ptr, len), + )) + } + } + + /// Creates a new Python `bytearray` object with an `init` closure to write its contents. + /// Before calling `init` the bytearray is zero-initialised. + /// * If Python raises a MemoryError on the allocation, `new_with` will return + /// it inside `Err`. + /// * If `init` returns `Err(e)`, `new_with` will return `Err(e)`. + /// * If `init` returns `Ok(())`, `new_with` will return `Ok(&PyByteArray)`. + /// + /// # Example + /// ``` + /// use pyo3::experimental::{prelude::*, objects::PyByteArray}; + /// Python::with_gil(|py| -> PyResult<()> { + /// let py_bytearray = PyByteArray::new_with(py, 10, |bytes: &mut [u8]| { + /// bytes.copy_from_slice(b"Hello Rust"); + /// Ok(()) + /// })?; + /// let bytearray: &[u8] = unsafe { py_bytearray.as_bytes() }; + /// assert_eq!(bytearray, b"Hello Rust"); + /// Ok(()) + /// }); + /// ``` + pub fn new_with(py: Python<'py>, len: usize, init: F) -> PyResult + where + F: FnOnce(&mut [u8]) -> PyResult<()>, + { + unsafe { + let pyptr = + ffi::PyByteArray_FromStringAndSize(std::ptr::null(), len as ffi::Py_ssize_t); + // Check for an allocation error and return it + let bytearray = Self(PyAny::from_raw_or_fetch_err(py, pyptr)?); + let buffer = ffi::PyByteArray_AsString(pyptr) as *mut u8; + debug_assert!(!buffer.is_null()); + // Zero-initialise the uninitialised bytearray + std::ptr::write_bytes(buffer, 0u8, len); + // (Further) Initialise the bytearray in init + // If init returns an Err, pypybytearray will automatically deallocate the buffer + init(std::slice::from_raw_parts_mut(buffer, len))?; + Ok(bytearray) + } + } + + /// Creates a new Python bytearray object from another PyObject that + /// implements the buffer protocol. + pub fn from(py: Python<'py>, src: &I) -> PyResult + where + I: AsPyPointer, + { + unsafe { + PyAny::from_raw_or_fetch_err(py, ffi::PyByteArray_FromObject(src.as_ptr())).map(Self) + } + } + + /// Gets the length of the bytearray. + #[inline] + pub fn len(&self) -> usize { + // non-negative Py_ssize_t should always fit into Rust usize + unsafe { ffi::PyByteArray_Size(self.as_ptr()) as usize } + } + + /// Checks if the bytearray is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Get the start of the buffer containing the contents of the bytearray. + /// + /// Note that this bytearray object is both shared and mutable, and the backing buffer may be + /// reallocated if the bytearray is resized. This can occur from Python code as well as from + /// Rust via [PyByteArray::resize]. + /// + /// As a result, the returned pointer should be dereferenced only if since calling this method + /// no Python code has executed, [PyByteArray::resize] has not been called. + pub fn data(&self) -> *mut u8 { + unsafe { ffi::PyByteArray_AsString(self.as_ptr()) as *mut u8 } + } + + /// Get the contents of this buffer as a slice. + /// + /// # Safety + /// This bytearray must not be resized or edited while holding the slice. + /// + /// ## Safety Detail + /// This method is equivalent to `std::slice::from_raw_parts(self.data(), self.len())`, and so + /// all the safety notes of `std::slice::from_raw_parts` apply here. + /// + /// In particular, note that this bytearray object is both shared and mutable, and the backing + /// buffer may be reallocated if the bytearray is resized. Mutations can occur from Python + /// code as well as from Rust, via [PyByteArray::as_bytes_mut] and [PyByteArray::resize]. + /// + /// Extreme care should be exercised when using this slice, as the Rust compiler will + /// make optimizations based on the assumption the contents of this slice cannot change. This + /// can easily lead to undefined behavior. + /// + /// As a result, this slice should only be used for short-lived operations to read this + /// bytearray without executing any Python code, such as copying into a Vec. + pub unsafe fn as_bytes(&self) -> &[u8] { + slice::from_raw_parts(self.data(), self.len()) + } + + /// Get the contents of this buffer as a mutable slice. + /// + /// # Safety + /// This slice should only be used for short-lived operations that write to this bytearray + /// without executing any Python code. See the safety note for [PyByteArray::as_bytes]. + #[allow(clippy::mut_from_ref)] + pub unsafe fn as_bytes_mut(&self) -> &mut [u8] { + slice::from_raw_parts_mut(self.data(), self.len()) + } + + /// Copies the contents of the bytearray to a Rust vector. + /// + /// # Example + /// + /// ``` + /// # use pyo3::experimental::prelude::*; + /// # use pyo3::experimental::objects::{PyByteArray, IntoPyDict, PyNativeObject}; + /// # let gil = Python::acquire_gil(); + /// # let py = gil.python(); + /// # + /// let bytearray = PyByteArray::new(py, b"Hello World."); + /// let mut copied_message = bytearray.to_vec(); + /// assert_eq!(b"Hello World.", copied_message.as_slice()); + /// + /// copied_message[11] = b'!'; + /// assert_eq!(b"Hello World!", copied_message.as_slice()); + /// + /// let locals = [("bytearray", bytearray)].into_py_dict(py); + /// py.run("assert bytearray == b'Hello World.'", None, Some(locals.as_owned_ref())).unwrap(); + /// ``` + pub fn to_vec(&self) -> Vec { + unsafe { self.as_bytes() }.to_vec() + } + + /// Resizes the bytearray object to the new length `len`. + /// + /// Note that this will invalidate any pointers obtained by [PyByteArray::data], as well as + /// any (unsafe) slices obtained from [PyByteArray::as_bytes] and [PyByteArray::as_bytes_mut]. + pub fn resize(&self, len: usize) -> PyResult<()> { + unsafe { + let result = ffi::PyByteArray_Resize(self.as_ptr(), len as ffi::Py_ssize_t); + if result == 0 { + Ok(()) + } else { + Err(PyErr::fetch(self.py())) + } + } + } +} + +#[cfg(test)] +mod test { + use crate::exceptions; + use crate::objects::PyByteArray; + use crate::{PyObject, Python}; + + #[test] + fn test_len() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let src = b"Hello Python"; + let bytearray = PyByteArray::new(py, src); + assert_eq!(src.len(), bytearray.len()); + } + + #[test] + fn test_as_bytes() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let src = b"Hello Python"; + let bytearray = PyByteArray::new(py, src); + + let slice = unsafe { bytearray.as_bytes() }; + assert_eq!(src, slice); + assert_eq!(bytearray.data() as *const _, slice.as_ptr()); + } + + #[test] + fn test_as_bytes_mut() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let src = b"Hello Python"; + let bytearray = PyByteArray::new(py, src); + + let slice = unsafe { bytearray.as_bytes_mut() }; + assert_eq!(src, slice); + assert_eq!(bytearray.data(), slice.as_mut_ptr()); + + slice[0..5].copy_from_slice(b"Hi..."); + + assert_eq!( + bytearray.str().unwrap().to_str().unwrap(), + "bytearray(b'Hi... Python')" + ); + } + + #[test] + fn test_to_vec() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let src = b"Hello Python"; + let bytearray = PyByteArray::new(py, src); + + let vec = bytearray.to_vec(); + assert_eq!(src, vec.as_slice()); + } + + #[test] + fn test_from() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let src = b"Hello Python"; + let bytearray = PyByteArray::new(py, src); + + let ba: PyObject = bytearray.into(); + let bytearray = PyByteArray::from(py, &ba).unwrap(); + + assert_eq!(src, unsafe { bytearray.as_bytes() }); + } + + #[test] + fn test_from_err() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + if let Err(err) = PyByteArray::from(py, &py.None()) { + assert!(err.is_instance::(py)); + } else { + panic!("error"); + } + } + + #[test] + fn test_resize() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let src = b"Hello Python"; + let bytearray = PyByteArray::new(py, src); + + bytearray.resize(20).unwrap(); + assert_eq!(20, bytearray.len()); + } + + #[test] + fn test_byte_array_new_with() -> super::PyResult<()> { + let gil = Python::acquire_gil(); + let py = gil.python(); + let py_bytearray = PyByteArray::new_with(py, 10, |b: &mut [u8]| { + b.copy_from_slice(b"Hello Rust"); + Ok(()) + })?; + let bytearray: &[u8] = unsafe { py_bytearray.as_bytes() }; + assert_eq!(bytearray, b"Hello Rust"); + Ok(()) + } + + #[test] + fn test_byte_array_new_with_zero_initialised() -> super::PyResult<()> { + let gil = Python::acquire_gil(); + let py = gil.python(); + let py_bytearray = PyByteArray::new_with(py, 10, |_b: &mut [u8]| Ok(()))?; + let bytearray: &[u8] = unsafe { py_bytearray.as_bytes() }; + assert_eq!(bytearray, &[0; 10]); + Ok(()) + } + + #[test] + fn test_byte_array_new_with_error() { + use crate::exceptions::PyValueError; + let gil = Python::acquire_gil(); + let py = gil.python(); + let py_bytearray_result = PyByteArray::new_with(py, 10, |_b: &mut [u8]| { + Err(PyValueError::new_err("Hello Crustaceans!")) + }); + assert!(py_bytearray_result.is_err()); + assert!(py_bytearray_result + .err() + .unwrap() + .is_instance::(py)); + } +} diff --git a/src/experimental/objects/bytes.rs b/src/experimental/objects/bytes.rs new file mode 100644 index 00000000000..974f258599e --- /dev/null +++ b/src/experimental/objects/bytes.rs @@ -0,0 +1,177 @@ +use crate::{ + ffi, + objects::{FromPyObject, PyAny}, + types::Bytes, + AsPyPointer, IntoPy, PyObject, PyResult, Python, +}; +use std::ops::Index; +use std::os::raw::c_char; +use std::slice::SliceIndex; + +/// Represents a Python `bytes` object. +/// +/// This type is immutable. +#[repr(transparent)] +pub struct PyBytes<'py>(pub(crate) PyAny<'py>); + +pyo3_native_object!(PyBytes<'py>, Bytes, 'py); + +impl<'py> PyBytes<'py> { + /// Creates a new Python bytestring object. + /// The bytestring is initialized by copying the data from the `&[u8]`. + /// + /// Panics if out of memory. + pub fn new(py: Python<'py>, s: &[u8]) -> Self { + let ptr = s.as_ptr() as *const c_char; + let len = s.len() as ffi::Py_ssize_t; + unsafe { + Self(PyAny::from_raw_or_panic( + py, + ffi::PyBytes_FromStringAndSize(ptr, len), + )) + } + } + + /// Creates a new Python `bytes` object with an `init` closure to write its contents. + /// Before calling `init` the bytes' contents are zero-initialised. + /// * If Python raises a MemoryError on the allocation, `new_with` will return + /// it inside `Err`. + /// * If `init` returns `Err(e)`, `new_with` will return `Err(e)`. + /// * If `init` returns `Ok(())`, `new_with` will return `Ok(&PyBytes)`. + /// + /// # Example + /// ``` + /// use pyo3::experimental::{prelude::*, objects::PyBytes}; + /// Python::with_gil(|py| -> PyResult<()> { + /// let py_bytes = PyBytes::new_with(py, 10, |bytes: &mut [u8]| { + /// bytes.copy_from_slice(b"Hello Rust"); + /// Ok(()) + /// })?; + /// let bytes: &[u8] = FromPyObject::extract(&py_bytes)?; + /// assert_eq!(bytes, b"Hello Rust"); + /// Ok(()) + /// }); + /// ``` + pub fn new_with(py: Python<'py>, len: usize, init: F) -> PyResult + where + F: FnOnce(&mut [u8]) -> PyResult<()>, + { + unsafe { + let pyptr = ffi::PyBytes_FromStringAndSize(std::ptr::null(), len as ffi::Py_ssize_t); + // Check for an allocation error and return it + let bytes = Self(PyAny::from_raw_or_fetch_err(py, pyptr)?); + let buffer = ffi::PyBytes_AsString(pyptr) as *mut u8; + debug_assert!(!buffer.is_null()); + // Zero-initialise the uninitialised bytestring + std::ptr::write_bytes(buffer, 0u8, len); + // (Further) Initialise the bytestring in init + // If init returns an Err, bytes will automatically deallocate the buffer + init(std::slice::from_raw_parts_mut(buffer, len))?; + Ok(bytes) + } + } + + /// Creates a new Python byte string object from a raw pointer and length. + /// + /// Panics if out of memory. + pub unsafe fn from_ptr(py: Python<'py>, ptr: *const u8, len: usize) -> Self { + Self(PyAny::from_raw_or_panic( + py, + ffi::PyBytes_FromStringAndSize(ptr as *const _, len as isize), + )) + } + + /// Gets the Python string as a byte slice. + #[inline] + pub fn as_bytes(&self) -> &[u8] { + unsafe { + let buffer = ffi::PyBytes_AsString(self.as_ptr()) as *const u8; + let length = ffi::PyBytes_Size(self.as_ptr()) as usize; + debug_assert!(!buffer.is_null()); + std::slice::from_raw_parts(buffer, length) + } + } +} + +/// This is the same way [Vec] is indexed. +impl> Index for PyBytes<'_> { + type Output = I::Output; + + fn index(&self, index: I) -> &Self::Output { + &self.as_bytes()[index] + } +} + +impl<'a> IntoPy for &'a [u8] { + fn into_py(self, py: Python) -> PyObject { + PyBytes::new(py, self).into() + } +} + +impl<'a> FromPyObject<'a, '_> for &'a [u8] { + fn extract(obj: &'a PyAny) -> PyResult { + Ok(obj.downcast::()?.as_bytes()) + } +} +#[cfg(test)] +mod test { + use super::PyBytes; + use crate::FromPyObject; + use crate::Python; + + #[test] + fn test_extract_bytes() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let py_bytes = py.eval("b'Hello Python'", None, None).unwrap(); + let bytes: &[u8] = FromPyObject::extract(py_bytes).unwrap(); + assert_eq!(bytes, b"Hello Python"); + } + + #[test] + fn test_bytes_index() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let bytes = PyBytes::new(py, b"Hello World"); + assert_eq!(bytes[1], b'e'); + } + + #[test] + fn test_bytes_new_with() -> super::PyResult<()> { + let gil = Python::acquire_gil(); + let py = gil.python(); + let py_bytes = PyBytes::new_with(py, 10, |b: &mut [u8]| { + b.copy_from_slice(b"Hello Rust"); + Ok(()) + })?; + let bytes: &[u8] = py_bytes.extract()?; + assert_eq!(bytes, b"Hello Rust"); + Ok(()) + } + + #[test] + fn test_bytes_new_with_zero_initialised() -> super::PyResult<()> { + let gil = Python::acquire_gil(); + let py = gil.python(); + let py_bytes = PyBytes::new_with(py, 10, |_b: &mut [u8]| Ok(()))?; + let bytes: &[u8] = py_bytes.extract()?; + assert_eq!(bytes, &[0; 10]); + Ok(()) + } + + #[test] + fn test_bytes_new_with_error() { + use crate::exceptions::PyValueError; + let gil = Python::acquire_gil(); + let py = gil.python(); + let py_bytes_result = PyBytes::new_with(py, 10, |_b: &mut [u8]| { + Err(PyValueError::new_err("Hello Crustaceans!")) + }); + assert!(py_bytes_result.is_err()); + assert!(py_bytes_result + .err() + .unwrap() + .is_instance::(py)); + } +} diff --git a/src/experimental/objects/complex.rs b/src/experimental/objects/complex.rs new file mode 100644 index 00000000000..66a089d9b13 --- /dev/null +++ b/src/experimental/objects/complex.rs @@ -0,0 +1,362 @@ +#[cfg(all(not(PyPy), not(Py_LIMITED_API)))] +use crate::objects::PyNativeObject; +use crate::{ffi, objects::PyAny, types::Complex, AsPyPointer, Python}; +#[cfg(all(not(PyPy), not(Py_LIMITED_API)))] +use std::ops::*; +use std::os::raw::c_double; + +/// Represents a Python `complex`. +#[repr(transparent)] +pub struct PyComplex<'py>(pub(crate) PyAny<'py>); + +pyo3_native_object!(PyComplex<'py>, Complex, 'py); + +impl<'py> PyComplex<'py> { + /// Creates a new Python `complex` object, from its real and imaginary values. + pub fn from_doubles(py: Python<'py>, real: c_double, imag: c_double) -> Self { + unsafe { + Self(PyAny::from_raw_or_panic( + py, + ffi::PyComplex_FromDoubles(real, imag), + )) + } + } + /// Returns the real part of the complex number. + pub fn real(&self) -> c_double { + unsafe { ffi::PyComplex_RealAsDouble(self.as_ptr()) } + } + /// Returns the imaginary part the complex number. + pub fn imag(&self) -> c_double { + unsafe { ffi::PyComplex_ImagAsDouble(self.as_ptr()) } + } + /// Returns `|self|`. + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + pub fn abs(&self) -> c_double { + unsafe { + let val = (*(self.as_ptr() as *mut ffi::PyComplexObject)).cval; + ffi::_Py_c_abs(val) + } + } + /// Returns `self ** other` + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + pub fn pow(&self, other: &PyComplex) -> Self { + unsafe { + Self(PyAny::from_raw_or_panic( + self.py(), + complex_operation(self, other, ffi::_Py_c_pow), + )) + } + } +} + +#[cfg(not(any(Py_LIMITED_API, PyPy)))] +#[inline(always)] +unsafe fn complex_operation( + l: &PyComplex, + r: &PyComplex, + operation: unsafe extern "C" fn(ffi::Py_complex, ffi::Py_complex) -> ffi::Py_complex, +) -> *mut ffi::PyObject { + let l_val = (*(l.as_ptr() as *mut ffi::PyComplexObject)).cval; + let r_val = (*(r.as_ptr() as *mut ffi::PyComplexObject)).cval; + ffi::PyComplex_FromCComplex(operation(l_val, r_val)) +} + +#[cfg(not(any(Py_LIMITED_API, PyPy)))] +impl<'py> Add<&PyComplex<'_>> for &'_ PyComplex<'py> { + type Output = PyComplex<'py>; + fn add(self, other: &PyComplex) -> PyComplex<'py> { + unsafe { + PyComplex(PyAny::from_raw_or_panic( + self.py(), + complex_operation(self, other, ffi::_Py_c_sum), + )) + } + } +} + +#[cfg(not(any(Py_LIMITED_API, PyPy)))] +impl<'py> Sub<&PyComplex<'_>> for &'_ PyComplex<'py> { + type Output = PyComplex<'py>; + fn sub(self, other: &PyComplex) -> PyComplex<'py> { + unsafe { + PyComplex(PyAny::from_raw_or_panic( + self.py(), + complex_operation(self, other, ffi::_Py_c_diff), + )) + } + } +} + +#[cfg(not(any(Py_LIMITED_API, PyPy)))] +impl<'py> Mul<&PyComplex<'_>> for &'_ PyComplex<'py> { + type Output = PyComplex<'py>; + fn mul(self, other: &PyComplex) -> PyComplex<'py> { + unsafe { + PyComplex(PyAny::from_raw_or_panic( + self.py(), + complex_operation(self, other, ffi::_Py_c_prod), + )) + } + } +} +#[cfg(not(any(Py_LIMITED_API, PyPy)))] +impl<'py> Div<&PyComplex<'_>> for &'_ PyComplex<'py> { + type Output = PyComplex<'py>; + fn div(self, other: &PyComplex) -> PyComplex<'py> { + unsafe { + PyComplex(PyAny::from_raw_or_panic( + self.py(), + complex_operation(self, other, ffi::_Py_c_quot), + )) + } + } +} + +#[cfg(not(any(Py_LIMITED_API, PyPy)))] +impl<'py> Neg for &'_ PyComplex<'py> { + type Output = PyComplex<'py>; + fn neg(self) -> PyComplex<'py> { + unsafe { + let val = (*(self.as_ptr() as *mut ffi::PyComplexObject)).cval; + PyComplex(PyAny::from_raw_or_panic( + self.py(), + ffi::PyComplex_FromCComplex(ffi::_Py_c_neg(val)), + )) + } + } +} + +macro_rules! owned_traits { + ($trait:ident, $fn_name:ident, $op:tt) => { + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + impl<'py> $trait> for &'_ PyComplex<'py> { + type Output = PyComplex<'py>; + fn $fn_name(self, other: PyComplex<'_>) -> PyComplex<'py> { + self $op &other + } + } + + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + impl<'py> $trait<&'_ PyComplex<'_>> for PyComplex<'py> { + type Output = PyComplex<'py>; + fn $fn_name(self, other: &PyComplex) -> PyComplex<'py> { + &self $op other + } + } + + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + impl<'py> $trait> for PyComplex<'py> { + type Output = PyComplex<'py>; + fn $fn_name(self, other: PyComplex<'_>) -> PyComplex<'py> { + &self $op &other + } + } + }; +} + +owned_traits!(Add, add, +); +owned_traits!(Sub, sub, -); +owned_traits!(Mul, mul, *); +owned_traits!(Div, div, /); + +#[cfg(not(any(Py_LIMITED_API, PyPy)))] +impl<'py> Neg for PyComplex<'py> { + type Output = PyComplex<'py>; + fn neg(self) -> PyComplex<'py> { + -&self + } +} + +#[cfg(feature = "num-complex")] +mod complex_conversion { + use super::*; + use crate::{FromPyObject, PyErr, PyNativeType, PyObject, PyResult, ToPyObject}; + use num_complex::Complex; + + impl PyComplex { + /// Creates a new Python `PyComplex` object from num_complex::Complex. + pub fn from_complex<'py, F: Into>( + py: Python<'py>, + complex: Complex, + ) -> PyComplex<'py> { + unsafe { + let ptr = ffi::PyComplex_FromDoubles(complex.re.into(), complex.im.into()); + py.from_owned_ptr(ptr) + } + } + } + macro_rules! complex_conversion { + ($float: ty) => { + impl ToPyObject for Complex<$float> { + #[inline] + fn to_object(&self, py: Python) -> PyObject { + crate::IntoPy::::into_py(self.to_owned(), py) + } + } + impl crate::IntoPy for Complex<$float> { + fn into_py(self, py: Python) -> PyObject { + unsafe { + let raw_obj = + ffi::PyComplex_FromDoubles(self.re as c_double, self.im as c_double); + PyObject::from_owned_ptr(py, raw_obj) + } + } + } + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + #[allow(clippy::float_cmp)] // The comparison is for an error value + impl<'source> FromPyObject<'source> for Complex<$float> { + fn extract(obj: &'source PyAny) -> PyResult> { + unsafe { + let val = ffi::PyComplex_AsCComplex(obj.as_ptr()); + if val.real == -1.0 && PyErr::occurred(obj.py()) { + Err(PyErr::fetch(obj.py())) + } else { + Ok(Complex::new(val.real as $float, val.imag as $float)) + } + } + } + } + #[cfg(any(Py_LIMITED_API, PyPy))] + #[allow(clippy::float_cmp)] // The comparison is for an error value + impl<'source> FromPyObject<'source> for Complex<$float> { + fn extract(obj: &'source PyAny) -> PyResult> { + unsafe { + let ptr = obj.as_ptr(); + let real = ffi::PyComplex_RealAsDouble(ptr); + if real == -1.0 && PyErr::occurred(obj.py()) { + return Err(PyErr::fetch(obj.py())); + } + let imag = ffi::PyComplex_ImagAsDouble(ptr); + Ok(Complex::new(real as $float, imag as $float)) + } + } + } + }; + } + complex_conversion!(f32); + complex_conversion!(f64); + + #[allow(clippy::float_cmp)] // The test wants to ensure that no precision was lost on the Python round-trip + #[test] + fn from_complex() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let complex = Complex::new(3.0, 1.2); + let py_c = PyComplex::from_complex(py, complex); + assert_eq!(py_c.real(), 3.0); + assert_eq!(py_c.imag(), 1.2); + } + #[test] + fn to_from_complex() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let val = Complex::new(3.0, 1.2); + let obj = val.to_object(py); + assert_eq!(obj.extract::>(py).unwrap(), val); + } + #[test] + fn from_complex_err() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let obj = vec![1].to_object(py); + assert!(obj.extract::>(py).is_err()); + } +} + +#[cfg(test)] +mod test { + use super::PyComplex; + use crate::Python; + use assert_approx_eq::assert_approx_eq; + + #[test] + fn test_from_double() { + use assert_approx_eq::assert_approx_eq; + + let gil = Python::acquire_gil(); + let py = gil.python(); + let complex = PyComplex::from_doubles(py, 3.0, 1.2); + assert_approx_eq!(complex.real(), 3.0); + assert_approx_eq!(complex.imag(), 1.2); + } + + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + #[test] + fn test_add() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let l = PyComplex::from_doubles(py, 3.0, 1.2); + let r = PyComplex::from_doubles(py, 1.0, 2.6); + let res = l + r; + assert_approx_eq!(res.real(), 4.0); + assert_approx_eq!(res.imag(), 3.8); + } + + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + #[test] + fn test_sub() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let l = PyComplex::from_doubles(py, 3.0, 1.2); + let r = PyComplex::from_doubles(py, 1.0, 2.6); + let res = l - r; + assert_approx_eq!(res.real(), 2.0); + assert_approx_eq!(res.imag(), -1.4); + } + + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + #[test] + fn test_mul() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let l = PyComplex::from_doubles(py, 3.0, 1.2); + let r = PyComplex::from_doubles(py, 1.0, 2.6); + let res = l * r; + assert_approx_eq!(res.real(), -0.12); + assert_approx_eq!(res.imag(), 9.0); + } + + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + #[test] + fn test_div() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let l = PyComplex::from_doubles(py, 3.0, 1.2); + let r = PyComplex::from_doubles(py, 1.0, 2.6); + let res = l / r; + assert_approx_eq!(res.real(), 0.788_659_793_814_432_9); + assert_approx_eq!(res.imag(), -0.850_515_463_917_525_7); + } + + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + #[test] + fn test_neg() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let val = PyComplex::from_doubles(py, 3.0, 1.2); + let res = -val; + assert_approx_eq!(res.real(), -3.0); + assert_approx_eq!(res.imag(), -1.2); + } + + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + #[test] + fn test_abs() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let val = PyComplex::from_doubles(py, 3.0, 1.2); + assert_approx_eq!(val.abs(), 3.231_098_884_280_702_2); + } + + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + #[test] + fn test_pow() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let l = PyComplex::from_doubles(py, 3.0, 1.2); + let r = PyComplex::from_doubles(py, 1.2, 2.6); + let val = l.pow(&r); + assert_approx_eq!(val.real(), -1.419_309_997_016_603_7); + assert_approx_eq!(val.imag(), -0.541_297_466_033_544_6); + } +} diff --git a/src/experimental/objects/datetime.rs b/src/experimental/objects/datetime.rs new file mode 100644 index 00000000000..d90c7071956 --- /dev/null +++ b/src/experimental/objects/datetime.rs @@ -0,0 +1,329 @@ +//! Safe Rust wrappers for types defined in the Python `datetime` library +//! +//! For more details about these types, see the [Python +//! documentation](https://docs.python.org/3/library/datetime.html) + +#![allow(clippy::too_many_arguments)] + +use crate::{ + ffi, + objects::{PyAny, PyTuple}, + types::{Date, DateTime, Time, TimeDelta, TzInfo}, + AsPyPointer, PyObject, PyResult, Python, ToPyObject, +}; +use std::os::raw::c_int; +#[cfg(not(PyPy))] +use std::ptr; + +/// Access traits + +/// Trait for accessing the date components of a struct containing a date. +pub trait PyDateAccess { + fn get_year(&self) -> i32; + fn get_month(&self) -> u8; + fn get_day(&self) -> u8; +} + +/// Trait for accessing the components of a struct containing a timedelta. +/// +/// Note: These access the individual components of a (day, second, +/// microsecond) representation of the delta, they are *not* intended as +/// aliases for calculating the total duration in each of these units. +pub trait PyDeltaAccess { + fn get_days(&self) -> i32; + fn get_seconds(&self) -> i32; + fn get_microseconds(&self) -> i32; +} + +/// Trait for accessing the time components of a struct containing a time. +pub trait PyTimeAccess { + fn get_hour(&self) -> u8; + fn get_minute(&self) -> u8; + fn get_second(&self) -> u8; + fn get_microsecond(&self) -> u32; + #[cfg(not(PyPy))] + fn get_fold(&self) -> u8; +} + +/// Bindings around `datetime.date` +#[repr(transparent)] +pub struct PyDate<'py>(pub(crate) PyAny<'py>); + +pyo3_native_object!(PyDate<'py>, Date, 'py); + +impl<'py> PyDate<'py> { + pub fn new(py: Python<'py>, year: i32, month: u8, day: u8) -> PyResult> { + unsafe { + let ptr = (ffi::PyDateTimeAPI.Date_FromDate)( + year, + c_int::from(month), + c_int::from(day), + ffi::PyDateTimeAPI.DateType, + ); + PyAny::from_raw_or_fetch_err(py, ptr).map(Self) + } + } + + /// Construct a `datetime.date` from a POSIX timestamp + /// + /// This is equivalent to `datetime.date.fromtimestamp` + pub fn from_timestamp(py: Python<'py>, timestamp: i64) -> PyResult> { + let time_tuple = PyTuple::new(py, &[timestamp]); + + unsafe { + #[cfg(PyPy)] + let ptr = PyDate_FromTimestamp(time_tuple.as_ptr()); + + #[cfg(not(PyPy))] + let ptr = (ffi::PyDateTimeAPI.Date_FromTimestamp)( + ffi::PyDateTimeAPI.DateType, + time_tuple.as_ptr(), + ); + + PyAny::from_raw_or_fetch_err(py, ptr).map(Self) + } + } +} + +impl PyDateAccess for PyDate<'_> { + fn get_year(&self) -> i32 { + unsafe { ffi::PyDateTime_GET_YEAR(self.as_ptr()) as i32 } + } + + fn get_month(&self) -> u8 { + unsafe { ffi::PyDateTime_GET_MONTH(self.as_ptr()) as u8 } + } + + fn get_day(&self) -> u8 { + unsafe { ffi::PyDateTime_GET_DAY(self.as_ptr()) as u8 } + } +} + +/// Bindings for `datetime.datetime` +#[repr(transparent)] +pub struct PyDateTime<'py>(pub(crate) PyAny<'py>); +pyo3_native_object!(PyDateTime<'py>, DateTime, 'py); + +impl<'py> PyDateTime<'py> { + pub fn new( + py: Python<'py>, + year: i32, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, + microsecond: u32, + tzinfo: Option<&PyAny>, + ) -> PyResult> { + unsafe { + let ptr = (ffi::PyDateTimeAPI.DateTime_FromDateAndTime)( + year, + c_int::from(month), + c_int::from(day), + c_int::from(hour), + c_int::from(minute), + c_int::from(second), + microsecond as c_int, + tzinfo.map_or(std::ptr::null_mut(), |any| any.as_ptr()), + ffi::PyDateTimeAPI.DateTimeType, + ); + PyAny::from_raw_or_fetch_err(py, ptr).map(Self) + } + } + + /// Construct a `datetime` object from a POSIX timestamp + /// + /// This is equivalent to `datetime.datetime.from_timestamp` + pub fn from_timestamp( + py: Python<'py>, + timestamp: f64, + time_zone_info: Option<&PyTzInfo>, + ) -> PyResult> { + let timestamp: PyObject = timestamp.to_object(py); + + let time_zone_info: PyObject = match time_zone_info { + Some(time_zone_info) => time_zone_info.to_object(py), + None => py.None(), + }; + + let args = PyTuple::new(py, &[timestamp, time_zone_info]); + + unsafe { + #[cfg(PyPy)] + let ptr = PyDateTime_FromTimestamp(args.as_ptr()); + + #[cfg(not(PyPy))] + let ptr = { + (ffi::PyDateTimeAPI.DateTime_FromTimestamp)( + ffi::PyDateTimeAPI.DateTimeType, + args.as_ptr(), + ptr::null_mut(), + ) + }; + + PyAny::from_raw_or_fetch_err(py, ptr).map(Self) + } + } +} + +impl PyDateAccess for PyDateTime<'_> { + fn get_year(&self) -> i32 { + unsafe { ffi::PyDateTime_GET_YEAR(self.as_ptr()) as i32 } + } + + fn get_month(&self) -> u8 { + unsafe { ffi::PyDateTime_GET_MONTH(self.as_ptr()) as u8 } + } + + fn get_day(&self) -> u8 { + unsafe { ffi::PyDateTime_GET_DAY(self.as_ptr()) as u8 } + } +} + +impl PyTimeAccess for PyDateTime<'_> { + fn get_hour(&self) -> u8 { + unsafe { ffi::PyDateTime_DATE_GET_HOUR(self.as_ptr()) as u8 } + } + + fn get_minute(&self) -> u8 { + unsafe { ffi::PyDateTime_DATE_GET_MINUTE(self.as_ptr()) as u8 } + } + + fn get_second(&self) -> u8 { + unsafe { ffi::PyDateTime_DATE_GET_SECOND(self.as_ptr()) as u8 } + } + + fn get_microsecond(&self) -> u32 { + unsafe { ffi::PyDateTime_DATE_GET_MICROSECOND(self.as_ptr()) as u32 } + } + + #[cfg(not(PyPy))] + fn get_fold(&self) -> u8 { + unsafe { ffi::PyDateTime_DATE_GET_FOLD(self.as_ptr()) as u8 } + } +} + +/// Bindings for `datetime.time` +#[repr(transparent)] +pub struct PyTime<'py>(pub(crate) PyAny<'py>); + +pyo3_native_object!(PyTime<'py>, Time, 'py); + +impl<'py> PyTime<'py> { + pub fn new( + py: Python<'py>, + hour: u8, + minute: u8, + second: u8, + microsecond: u32, + tzinfo: Option<&PyAny>, + ) -> PyResult> { + unsafe { + let ptr = (ffi::PyDateTimeAPI.Time_FromTime)( + c_int::from(hour), + c_int::from(minute), + c_int::from(second), + microsecond as c_int, + tzinfo.map_or(std::ptr::null_mut(), |any| any.as_ptr()), + ffi::PyDateTimeAPI.TimeType, + ); + PyAny::from_raw_or_fetch_err(py, ptr).map(Self) + } + } + + #[cfg(not(PyPy))] + /// Alternate constructor that takes a `fold` argument + pub fn new_with_fold( + py: Python<'py>, + hour: u8, + minute: u8, + second: u8, + microsecond: u32, + tzinfo: Option<&PyAny>, + fold: bool, + ) -> PyResult> { + unsafe { + let ptr = (ffi::PyDateTimeAPI.Time_FromTimeAndFold)( + c_int::from(hour), + c_int::from(minute), + c_int::from(second), + microsecond as c_int, + tzinfo.map_or(std::ptr::null_mut(), |any| any.as_ptr()), + fold as c_int, + ffi::PyDateTimeAPI.TimeType, + ); + PyAny::from_raw_or_fetch_err(py, ptr).map(Self) + } + } +} + +impl PyTimeAccess for PyTime<'_> { + fn get_hour(&self) -> u8 { + unsafe { ffi::PyDateTime_TIME_GET_HOUR(self.as_ptr()) as u8 } + } + + fn get_minute(&self) -> u8 { + unsafe { ffi::PyDateTime_TIME_GET_MINUTE(self.as_ptr()) as u8 } + } + + fn get_second(&self) -> u8 { + unsafe { ffi::PyDateTime_TIME_GET_SECOND(self.as_ptr()) as u8 } + } + + fn get_microsecond(&self) -> u32 { + unsafe { ffi::PyDateTime_TIME_GET_MICROSECOND(self.as_ptr()) as u32 } + } + + #[cfg(not(PyPy))] + fn get_fold(&self) -> u8 { + unsafe { ffi::PyDateTime_TIME_GET_FOLD(self.as_ptr()) as u8 } + } +} + +/// Bindings for `datetime.tzinfo` +/// +/// This is an abstract base class and should not be constructed directly. +#[repr(transparent)] +pub struct PyTzInfo<'py>(pub(crate) PyAny<'py>); +pyo3_native_object!(PyTzInfo<'py>, TzInfo, 'py); + +/// Bindings for `datetime.timedelta` +#[repr(transparent)] +pub struct PyTimeDelta<'py>(pub(crate) PyAny<'py>); +pyo3_native_object!(PyTimeDelta<'py>, TimeDelta, 'py); + +impl<'py> PyTimeDelta<'py> { + pub fn new( + py: Python<'py>, + days: i32, + seconds: i32, + microseconds: i32, + normalize: bool, + ) -> PyResult> { + unsafe { + let ptr = (ffi::PyDateTimeAPI.Delta_FromDelta)( + days as c_int, + seconds as c_int, + microseconds as c_int, + normalize as c_int, + ffi::PyDateTimeAPI.DeltaType, + ); + PyAny::from_raw_or_fetch_err(py, ptr).map(Self) + } + } +} + +impl PyDeltaAccess for PyTimeDelta<'_> { + fn get_days(&self) -> i32 { + unsafe { ffi::PyDateTime_DELTA_GET_DAYS(self.as_ptr()) as i32 } + } + + fn get_seconds(&self) -> i32 { + unsafe { ffi::PyDateTime_DELTA_GET_SECONDS(self.as_ptr()) as i32 } + } + + fn get_microseconds(&self) -> i32 { + unsafe { ffi::PyDateTime_DELTA_GET_MICROSECONDS(self.as_ptr()) as i32 } + } +} diff --git a/src/experimental/objects/dict.rs b/src/experimental/objects/dict.rs new file mode 100644 index 00000000000..13b51e9f93c --- /dev/null +++ b/src/experimental/objects/dict.rs @@ -0,0 +1,826 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors + +use crate::err::{self, PyErr, PyResult}; +use crate::objects::PyAny; +use crate::objects::{FromPyObject, PyList, PyNativeObject}; +use crate::types::Dict; +use crate::{ffi, AsPyPointer, IntoPy, PyObject, Python, ToBorrowedObject, ToPyObject}; +use std::collections::{BTreeMap, HashMap}; +use std::{cmp, collections, hash}; + +/// Represents a Python `dict`. +#[repr(transparent)] +pub struct PyDict<'py>(pub(crate) PyAny<'py>); + +pyo3_native_object!(PyDict<'py>, Dict, 'py); + +impl<'py> PyDict<'py> { + /// Creates a new empty dictionary. + pub fn new(py: Python<'py>) -> Self { + unsafe { Self(PyAny::from_raw_or_panic(py, ffi::PyDict_New())) } + } + + /// Creates a new dictionary from the sequence given. + /// + /// The sequence must consist of `(PyObject, PyObject)`. This is + /// equivalent to `dict([("a", 1), ("b", 2)])`. + /// + /// Returns an error on invalid input. In the case of key collisions, + /// this keeps the last entry seen. + #[cfg(not(PyPy))] + pub fn from_sequence(py: Python<'py>, seq: &PyAny) -> PyResult { + unsafe { + let dict = PyDict::new(py); + match ffi::PyDict_MergeFromSeq2(dict.as_ptr(), seq.as_ptr(), 1i32) { + 0 => Ok(dict), + -1 => Err(PyErr::fetch(py)), + _ => unreachable!(), + } + } + } + + /// Returns a new dictionary that contains the same key-value pairs as self. + /// + /// This is equivalent to the Python expression `dict(self)`. + pub fn copy(&self) -> PyResult { + unsafe { + PyAny::from_raw_or_fetch_err(self.py(), ffi::PyDict_Copy(self.as_ptr())).map(Self) + } + } + + /// Empties an existing dictionary of all key-value pairs. + pub fn clear(&self) { + unsafe { ffi::PyDict_Clear(self.as_ptr()) } + } + + /// Return the number of items in the dictionary. + /// + /// This is equivalent to the Python expression `len(self)`. + pub fn len(&self) -> usize { + unsafe { ffi::PyDict_Size(self.as_ptr()) as usize } + } + + /// Checks if the dict is empty, i.e. `len(self) == 0`. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Determines if the dictionary contains the specified key. + /// + /// This is equivalent to the Python expression `key in self`. + pub fn contains(&self, key: K) -> PyResult + where + K: ToBorrowedObject, + { + key.with_borrowed_ptr(self.py(), |key| unsafe { + match ffi::PyDict_Contains(self.as_ptr(), key) { + 1 => Ok(true), + 0 => Ok(false), + _ => Err(PyErr::fetch(self.py())), + } + }) + } + + /// Gets an item from the dictionary. + /// + /// Returns `None` if the item is not present, or if an error occurs. + /// + /// To get a `KeyError` for non-existing keys, use `PyAny::get_item`. + pub fn get_item(&self, key: K) -> Option> + where + K: ToBorrowedObject, + { + key.with_borrowed_ptr(self.py(), |key| unsafe { + let ptr = ffi::PyDict_GetItem(self.as_ptr(), key); + // PyDict_GetItem returns borrowed ptr, must make it owned for safety (see #890). + PyAny::from_borrowed_ptr(self.py(), ptr) + }) + } + + /// Sets an item value. + /// + /// This is equivalent to the Python statement `self[key] = value`. + pub fn set_item(&self, key: K, value: V) -> PyResult<()> + where + K: ToPyObject, + V: ToPyObject, + { + key.with_borrowed_ptr(self.py(), move |key| { + value.with_borrowed_ptr(self.py(), |value| unsafe { + err::error_on_minusone(self.py(), ffi::PyDict_SetItem(self.as_ptr(), key, value)) + }) + }) + } + + /// Deletes an item. + /// + /// This is equivalent to the Python statement `del self[key]`. + pub fn del_item(&self, key: K) -> PyResult<()> + where + K: ToBorrowedObject, + { + key.with_borrowed_ptr(self.py(), |key| unsafe { + err::error_on_minusone(self.py(), ffi::PyDict_DelItem(self.as_ptr(), key)) + }) + } + + /// Returns a list of dict keys. + /// + /// This is equivalent to the Python expression `list(dict.keys())`. + pub fn keys(&self) -> PyList<'py> { + unsafe { + PyList(PyAny::from_raw_or_panic( + self.py(), + ffi::PyDict_Keys(self.as_ptr()), + )) + } + } + + /// Returns a list of dict values. + /// + /// This is equivalent to the Python expression `list(dict.values())`. + pub fn values(&self) -> PyList<'py> { + unsafe { + PyList(PyAny::from_raw_or_panic( + self.py(), + ffi::PyDict_Values(self.as_ptr()), + )) + } + } + + /// Returns a list of dict items. + /// + /// This is equivalent to the Python expression `list(dict.items())`. + pub fn items(&self) -> PyList<'py> { + unsafe { + PyList(PyAny::from_raw_or_panic( + self.py(), + ffi::PyDict_Items(self.as_ptr()), + )) + } + } + + /// Returns an iterator of `(key, value)` pairs in this dictionary. + /// + /// Note that it's unsafe to use when the dictionary might be changed by + /// other code. + pub fn iter(&self) -> PyDictIterator<'_, 'py> { + PyDictIterator { dict: self, pos: 0 } + } +} + +pub struct PyDictIterator<'a, 'py> { + dict: &'a PyDict<'py>, + pos: isize, +} + +impl<'py> Iterator for PyDictIterator<'_, 'py> { + type Item = (PyAny<'py>, PyAny<'py>); + + #[inline] + fn next(&mut self) -> Option { + unsafe { + let mut key: *mut ffi::PyObject = std::ptr::null_mut(); + let mut value: *mut ffi::PyObject = std::ptr::null_mut(); + if ffi::PyDict_Next(self.dict.as_ptr(), &mut self.pos, &mut key, &mut value) != 0 { + let py = self.dict.py(); + // PyDict_Next returns borrowed values; for safety must make them owned (see #890) + Some(( + PyAny::from_borrowed_ptr_or_panic(py, key), + PyAny::from_borrowed_ptr_or_panic(py, value), + )) + } else { + None + } + } + } +} + +impl<'a, 'py> std::iter::IntoIterator for &'a PyDict<'py> { + type Item = (PyAny<'py>, PyAny<'py>); + type IntoIter = PyDictIterator<'a, 'py>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl ToPyObject for collections::HashMap +where + K: hash::Hash + cmp::Eq + ToPyObject, + V: ToPyObject, + H: hash::BuildHasher, +{ + fn to_object(&self, py: Python) -> PyObject { + IntoPyDict::into_py_dict(self, py).into() + } +} + +impl ToPyObject for collections::BTreeMap +where + K: cmp::Eq + ToPyObject, + V: ToPyObject, +{ + fn to_object(&self, py: Python) -> PyObject { + IntoPyDict::into_py_dict(self, py).into() + } +} + +impl IntoPy for collections::HashMap +where + K: hash::Hash + cmp::Eq + IntoPy, + V: IntoPy, + H: hash::BuildHasher, +{ + fn into_py(self, py: Python) -> PyObject { + let iter = self + .into_iter() + .map(|(k, v)| (k.into_py(py), v.into_py(py))); + IntoPyDict::into_py_dict(iter, py).into() + } +} + +impl IntoPy for collections::BTreeMap +where + K: cmp::Eq + IntoPy, + V: IntoPy, +{ + fn into_py(self, py: Python) -> PyObject { + let iter = self + .into_iter() + .map(|(k, v)| (k.into_py(py), v.into_py(py))); + IntoPyDict::into_py_dict(iter, py).into() + } +} + +/// Conversion trait that allows a sequence of tuples to be converted into `PyDict` +/// Primary use case for this trait is `call` and `call_method` methods as keywords argument. +pub trait IntoPyDict { + /// Converts self into a `PyDict` object pointer. Whether pointer owned or borrowed + /// depends on implementation. + fn into_py_dict(self, py: Python) -> PyDict; +} + +impl IntoPyDict for I +where + T: PyDictItem, + I: IntoIterator, +{ + fn into_py_dict(self, py: Python) -> PyDict { + let dict = PyDict::new(py); + for item in self { + dict.set_item(item.key(), item.value()) + .expect("Failed to set_item on dict"); + } + dict + } +} + +/// Represents a tuple which can be used as a PyDict item. +pub trait PyDictItem { + type K: ToPyObject; + type V: ToPyObject; + fn key(&self) -> &Self::K; + fn value(&self) -> &Self::V; +} + +impl PyDictItem for (K, V) +where + K: ToPyObject, + V: ToPyObject, +{ + type K = K; + type V = V; + fn key(&self) -> &Self::K { + &self.0 + } + fn value(&self) -> &Self::V { + &self.1 + } +} + +impl PyDictItem for &(K, V) +where + K: ToPyObject, + V: ToPyObject, +{ + type K = K; + type V = V; + fn key(&self) -> &Self::K { + &self.0 + } + fn value(&self) -> &Self::V { + &self.1 + } +} + +impl<'py, K, V, S> FromPyObject<'_, 'py> for HashMap +where + K: for<'a> FromPyObject<'a, 'py> + cmp::Eq + hash::Hash, + V: for<'a> FromPyObject<'a, 'py>, + S: hash::BuildHasher + Default, +{ + fn extract(ob: &PyAny<'py>) -> Result { + let dict = ob.downcast::()?; + let mut ret = HashMap::with_capacity_and_hasher(dict.len(), S::default()); + for (k, v) in dict.iter() { + ret.insert(K::extract(&k)?, V::extract(&v)?); + } + Ok(ret) + } +} + +impl<'py, K, V> FromPyObject<'_, 'py> for BTreeMap +where + K: for<'a> FromPyObject<'a, 'py> + cmp::Ord, + V: for<'a> FromPyObject<'a, 'py>, +{ + fn extract(ob: &PyAny<'py>) -> Result { + let dict = ob.downcast::()?; + let mut ret = BTreeMap::new(); + for (k, v) in dict.iter() { + ret.insert(K::extract(&k)?, V::extract(&v)?); + } + Ok(ret) + } +} + +#[cfg(feature = "hashbrown")] +mod hashbrown_hashmap_conversion { + use super::*; + use crate::{FromPyObject, PyErr, PyObject, ToPyObject}; + + impl ToPyObject for hashbrown::HashMap + where + K: hash::Hash + cmp::Eq + ToPyObject, + V: ToPyObject, + H: hash::BuildHasher, + { + fn to_object(&self, py: Python) -> PyObject { + IntoPyDict::into_py_dict(self, py).into() + } + } + + impl IntoPy for hashbrown::HashMap + where + K: hash::Hash + cmp::Eq + IntoPy, + V: IntoPy, + H: hash::BuildHasher, + { + fn into_py(self, py: Python) -> PyObject { + let iter = self + .into_iter() + .map(|(k, v)| (k.into_py(py), v.into_py(py))); + IntoPyDict::into_py_dict(iter, py).into() + } + } + + impl<'py, K, V, S> FromPyObject<'py> for hashbrown::HashMap + where + K: FromPyObject<'py> + cmp::Eq + hash::Hash, + V: FromPyObject<'py>, + S: hash::BuildHasher + Default, + { + fn extract(ob: &PyAny<'py>) -> Result { + let dict = ::try_from(ob)?; + let mut ret = hashbrown::HashMap::with_capacity_and_hasher(dict.len(), S::default()); + for (k, v) in dict.iter() { + ret.insert(K::extract(k)?, V::extract(v)?); + } + Ok(ret) + } + } + + #[test] + fn test_hashbrown_hashmap_to_python() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let mut map = hashbrown::HashMap::::new(); + map.insert(1, 1); + + let m = map.to_object(py); + let py_map = ::try_from(m.as_object(py)).unwrap(); + + assert!(py_map.len() == 1); + assert!(py_map.get_item(1).unwrap().extract::().unwrap() == 1); + assert_eq!(map, py_map.extract().unwrap()); + } + #[test] + fn test_hashbrown_hashmap_into_python() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let mut map = hashbrown::HashMap::::new(); + map.insert(1, 1); + + let m: PyObject = map.into_py(py); + let py_map = ::try_from(m.as_object(py)).unwrap(); + + assert!(py_map.len() == 1); + assert!(py_map.get_item(1).unwrap().extract::().unwrap() == 1); + } + + #[test] + fn test_hashbrown_hashmap_into_dict() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let mut map = hashbrown::HashMap::::new(); + map.insert(1, 1); + + let py_map = map.into_py_dict(py); + + assert_eq!(py_map.len(), 1); + assert_eq!(py_map.get_item(1).unwrap().extract::().unwrap(), 1); + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::conversion::IntoPy; + use crate::experimental::PyTryFrom; + #[cfg(not(PyPy))] + use crate::objects::PyList; + use crate::objects::{PyDict, PyTuple}; + use crate::PyObject; + use crate::Python; + use crate::ToPyObject; + use maplit::{btreemap, hashmap}; + use std::collections::{BTreeMap, HashMap}; + + #[test] + fn test_new() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let dict = [(7, 32)].into_py_dict(py); + assert_eq!(32, dict.get_item(7i32).unwrap().extract::().unwrap()); + assert_eq!(None, dict.get_item(8i32)); + let map: HashMap = [(7, 32)].iter().cloned().collect(); + assert_eq!(map, dict.extract().unwrap()); + let map: BTreeMap = [(7, 32)].iter().cloned().collect(); + assert_eq!(map, dict.extract().unwrap()); + } + + #[test] + #[cfg(not(PyPy))] + fn test_from_sequence() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let items = PyList::new(py, &vec![("a", 1), ("b", 2)]); + let dict = PyDict::from_sequence(py, items.to_object(py).as_object(py)).unwrap(); + assert_eq!(1, dict.get_item("a").unwrap().extract::().unwrap()); + assert_eq!(2, dict.get_item("b").unwrap().extract::().unwrap()); + let map: HashMap = hashmap! { "a".into() => 1, "b".into() => 2}; + assert_eq!(map, dict.extract().unwrap()); + let map: BTreeMap = btreemap! { "a".into() => 1, "b".into() => 2}; + assert_eq!(map, dict.extract().unwrap()); + } + + #[test] + #[cfg(not(PyPy))] + fn test_from_sequence_err() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let items = PyList::new(py, &vec!["a", "b"]); + assert!(PyDict::from_sequence(py, items.to_object(py).as_object(py)).is_err()); + } + + #[test] + fn test_copy() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let dict = [(7, 32)].into_py_dict(py); + + let ndict = dict.copy().unwrap(); + assert_eq!(32, ndict.get_item(7i32).unwrap().extract::().unwrap()); + assert_eq!(None, ndict.get_item(8i32)); + } + + #[test] + fn test_len() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let mut v = HashMap::new(); + let ob = v.to_object(py); + let dict = ::try_from(ob.as_object(py)).unwrap(); + assert_eq!(0, dict.len()); + v.insert(7, 32); + let ob = v.to_object(py); + let dict2 = ::try_from(ob.as_object(py)).unwrap(); + assert_eq!(1, dict2.len()); + } + + #[test] + fn test_contains() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let mut v = HashMap::new(); + v.insert(7, 32); + let ob = v.to_object(py); + let dict = ::try_from(ob.as_object(py)).unwrap(); + assert_eq!(true, dict.contains(7i32).unwrap()); + assert_eq!(false, dict.contains(8i32).unwrap()); + } + + #[test] + fn test_get_item() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let mut v = HashMap::new(); + v.insert(7, 32); + let ob = v.to_object(py); + let dict = ::try_from(ob.as_object(py)).unwrap(); + assert_eq!(32, dict.get_item(7i32).unwrap().extract::().unwrap()); + assert_eq!(None, dict.get_item(8i32)); + } + + #[test] + fn test_set_item() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let mut v = HashMap::new(); + v.insert(7, 32); + let ob = v.to_object(py); + let dict = ::try_from(ob.as_object(py)).unwrap(); + assert!(dict.set_item(7i32, 42i32).is_ok()); // change + assert!(dict.set_item(8i32, 123i32).is_ok()); // insert + assert_eq!( + 42i32, + dict.get_item(7i32).unwrap().extract::().unwrap() + ); + assert_eq!( + 123i32, + dict.get_item(8i32).unwrap().extract::().unwrap() + ); + } + + #[test] + fn test_set_item_refcnt() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let cnt; + { + let _pool = unsafe { crate::GILPool::new() }; + let none = py.None(); + cnt = none.get_refcnt(py); + let _dict = [(10, none)].into_py_dict(py); + } + { + assert_eq!(cnt, py.None().get_refcnt(py)); + } + } + + #[test] + fn test_set_item_does_not_update_original_object() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let mut v = HashMap::new(); + v.insert(7, 32); + let ob = v.to_object(py); + let dict = ::try_from(ob.as_object(py)).unwrap(); + assert!(dict.set_item(7i32, 42i32).is_ok()); // change + assert!(dict.set_item(8i32, 123i32).is_ok()); // insert + assert_eq!(32i32, v[&7i32]); // not updated! + assert_eq!(None, v.get(&8i32)); + } + + #[test] + fn test_del_item() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let mut v = HashMap::new(); + v.insert(7, 32); + let ob = v.to_object(py); + let dict = ::try_from(ob.as_object(py)).unwrap(); + assert!(dict.del_item(7i32).is_ok()); + assert_eq!(0, dict.len()); + assert_eq!(None, dict.get_item(7i32)); + } + + #[test] + fn test_del_item_does_not_update_original_object() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let mut v = HashMap::new(); + v.insert(7, 32); + let ob = v.to_object(py); + let dict = ::try_from(ob.as_object(py)).unwrap(); + assert!(dict.del_item(7i32).is_ok()); // change + assert_eq!(32i32, *v.get(&7i32).unwrap()); // not updated! + } + + #[test] + fn test_items() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let mut v = HashMap::new(); + v.insert(7, 32); + v.insert(8, 42); + v.insert(9, 123); + let ob = v.to_object(py); + let dict = ::try_from(ob.as_object(py)).unwrap(); + // Can't just compare against a vector of tuples since we don't have a guaranteed ordering. + let mut key_sum = 0; + let mut value_sum = 0; + for el in dict.items().iter() { + let tuple = el.downcast::().unwrap(); + key_sum += tuple.get_item(0).extract::().unwrap(); + value_sum += tuple.get_item(1).extract::().unwrap(); + } + assert_eq!(7 + 8 + 9, key_sum); + assert_eq!(32 + 42 + 123, value_sum); + } + + #[test] + fn test_keys() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let mut v = HashMap::new(); + v.insert(7, 32); + v.insert(8, 42); + v.insert(9, 123); + let ob = v.to_object(py); + let dict = ::try_from(ob.as_object(py)).unwrap(); + // Can't just compare against a vector of tuples since we don't have a guaranteed ordering. + let mut key_sum = 0; + for el in dict.keys().iter() { + key_sum += el.extract::().unwrap(); + } + assert_eq!(7 + 8 + 9, key_sum); + } + + #[test] + fn test_values() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let mut v = HashMap::new(); + v.insert(7, 32); + v.insert(8, 42); + v.insert(9, 123); + let ob = v.to_object(py); + let dict = ::try_from(ob.as_object(py)).unwrap(); + // Can't just compare against a vector of tuples since we don't have a guaranteed ordering. + let mut values_sum = 0; + for el in dict.values().iter() { + values_sum += el.extract::().unwrap(); + } + assert_eq!(32 + 42 + 123, values_sum); + } + + #[test] + fn test_iter() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let mut v = HashMap::new(); + v.insert(7, 32); + v.insert(8, 42); + v.insert(9, 123); + let ob = v.to_object(py); + let dict = ::try_from(ob.as_object(py)).unwrap(); + let mut key_sum = 0; + let mut value_sum = 0; + for (key, value) in dict.iter() { + key_sum += key.extract::().unwrap(); + value_sum += value.extract::().unwrap(); + } + assert_eq!(7 + 8 + 9, key_sum); + assert_eq!(32 + 42 + 123, value_sum); + } + + #[test] + fn test_into_iter() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let mut v = HashMap::new(); + v.insert(7, 32); + v.insert(8, 42); + v.insert(9, 123); + let ob = v.to_object(py); + let dict = ::try_from(ob.as_object(py)).unwrap(); + let mut key_sum = 0; + let mut value_sum = 0; + for (key, value) in dict { + key_sum += key.extract::().unwrap(); + value_sum += value.extract::().unwrap(); + } + assert_eq!(7 + 8 + 9, key_sum); + assert_eq!(32 + 42 + 123, value_sum); + } + + #[test] + fn test_hashmap_to_python() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let mut map = HashMap::::new(); + map.insert(1, 1); + + let m = map.to_object(py); + let py_map = ::try_from(m.as_object(py)).unwrap(); + + assert!(py_map.len() == 1); + assert!(py_map.get_item(1).unwrap().extract::().unwrap() == 1); + assert_eq!(map, py_map.extract().unwrap()); + } + + #[test] + fn test_btreemap_to_python() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let mut map = BTreeMap::::new(); + map.insert(1, 1); + + let m = map.to_object(py); + let py_map = ::try_from(m.as_object(py)).unwrap(); + + assert!(py_map.len() == 1); + assert!(py_map.get_item(1).unwrap().extract::().unwrap() == 1); + assert_eq!(map, py_map.extract().unwrap()); + } + + #[test] + fn test_hashmap_into_python() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let mut map = HashMap::::new(); + map.insert(1, 1); + + let m: PyObject = map.into_py(py); + let py_map = ::try_from(m.as_object(py)).unwrap(); + + assert!(py_map.len() == 1); + assert!(py_map.get_item(1).unwrap().extract::().unwrap() == 1); + } + + #[test] + fn test_hashmap_into_dict() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let mut map = HashMap::::new(); + map.insert(1, 1); + + let py_map = map.into_py_dict(py); + + assert_eq!(py_map.len(), 1); + assert_eq!(py_map.get_item(1).unwrap().extract::().unwrap(), 1); + } + + #[test] + fn test_btreemap_into_py() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let mut map = BTreeMap::::new(); + map.insert(1, 1); + + let m: PyObject = map.into_py(py); + let py_map = ::try_from(m.as_object(py)).unwrap(); + + assert!(py_map.len() == 1); + assert!(py_map.get_item(1).unwrap().extract::().unwrap() == 1); + } + + #[test] + fn test_btreemap_into_dict() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let mut map = BTreeMap::::new(); + map.insert(1, 1); + + let py_map = map.into_py_dict(py); + + assert_eq!(py_map.len(), 1); + assert_eq!(py_map.get_item(1).unwrap().extract::().unwrap(), 1); + } + + #[test] + fn test_vec_into_dict() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let vec = vec![("a", 1), ("b", 2), ("c", 3)]; + let py_map = vec.into_py_dict(py); + + assert_eq!(py_map.len(), 3); + assert_eq!(py_map.get_item("b").unwrap().extract::().unwrap(), 2); + } + + #[test] + fn test_slice_into_dict() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let arr = [("a", 1), ("b", 2), ("c", 3)]; + let py_map = arr.into_py_dict(py); + + assert_eq!(py_map.len(), 3); + assert_eq!(py_map.get_item("b").unwrap().extract::().unwrap(), 2); + } +} diff --git a/src/experimental/objects/floatob.rs b/src/experimental/objects/floatob.rs new file mode 100644 index 00000000000..9458ced6e3b --- /dev/null +++ b/src/experimental/objects/floatob.rs @@ -0,0 +1,116 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors +// +// based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython +use crate::{ + ffi, + objects::{FromPyObject, PyAny, PyNativeObject}, + types::Float, + AsPyPointer, IntoPy, PyErr, PyObject, PyResult, Python, ToPyObject, +}; +use std::os::raw::c_double; + +/// Represents a Python `float` object. +/// +/// You can usually avoid directly working with this type +/// by using [`ToPyObject`](trait.ToPyObject.html) +/// and [extract](struct.PyAny.html#method.extract) +/// with `f32`/`f64`. +#[repr(transparent)] +pub struct PyFloat<'py>(pub(crate) PyAny<'py>); +pyo3_native_object!(PyFloat<'py>, Float, 'py); + +impl<'py> PyFloat<'py> { + /// Creates a new Python `float` object. + pub fn new(py: Python<'py>, val: c_double) -> Self { + unsafe { Self(PyAny::from_raw_or_panic(py, ffi::PyFloat_FromDouble(val))) } + } + + /// Gets the value of this float. + pub fn value(&self) -> c_double { + unsafe { ffi::PyFloat_AsDouble(self.as_ptr()) } + } +} + +impl ToPyObject for f64 { + fn to_object(&self, py: Python) -> PyObject { + PyFloat::new(py, *self).into() + } +} + +impl IntoPy for f64 { + fn into_py(self, py: Python) -> PyObject { + PyFloat::new(py, self).into() + } +} + +impl FromPyObject<'_, '_> for f64 { + // PyFloat_AsDouble returns -1.0 upon failure + #![allow(clippy::float_cmp)] + fn extract(obj: &PyAny) -> PyResult { + let v = unsafe { ffi::PyFloat_AsDouble(obj.as_ptr()) }; + + if v == -1.0 && PyErr::occurred(obj.py()) { + Err(PyErr::fetch(obj.py())) + } else { + Ok(v) + } + } +} + +impl ToPyObject for f32 { + fn to_object(&self, py: Python) -> PyObject { + PyFloat::new(py, f64::from(*self)).into() + } +} + +impl IntoPy for f32 { + fn into_py(self, py: Python) -> PyObject { + PyFloat::new(py, f64::from(self)).into() + } +} + +impl FromPyObject<'_, '_> for f32 { + fn extract(obj: &PyAny) -> PyResult { + Ok(obj.extract::()? as f32) + } +} + +#[cfg(test)] +mod test { + #[cfg(not(Py_LIMITED_API))] + use crate::ffi::PyFloat_AS_DOUBLE; + #[cfg(not(Py_LIMITED_API))] + use crate::AsPyPointer; + use crate::{Python, ToPyObject}; + + macro_rules! num_to_py_object_and_back ( + ($func_name:ident, $t1:ty, $t2:ty) => ( + #[test] + fn $func_name() { + use assert_approx_eq::assert_approx_eq; + + let gil = Python::acquire_gil(); + let py = gil.python(); + let val = 123 as $t1; + let obj = val.to_object(py); + assert_approx_eq!(obj.extract::<$t2>(py).unwrap(), val as $t2); + } + ) + ); + + num_to_py_object_and_back!(to_from_f64, f64, f64); + num_to_py_object_and_back!(to_from_f32, f32, f32); + num_to_py_object_and_back!(int_to_float, i32, f64); + + #[cfg(not(Py_LIMITED_API))] + #[test] + fn test_as_double_macro() { + use assert_approx_eq::assert_approx_eq; + + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = 1.23f64; + let obj = v.to_object(py); + assert_approx_eq!(v, unsafe { PyFloat_AS_DOUBLE(obj.as_ptr()) }); + } +} diff --git a/src/experimental/objects/function.rs b/src/experimental/objects/function.rs new file mode 100644 index 00000000000..0911a00734e --- /dev/null +++ b/src/experimental/objects/function.rs @@ -0,0 +1,103 @@ +use std::ffi::{CStr, CString}; + +use crate::derive_utils::PyFunctionArguments; +use crate::exceptions::PyValueError; +use crate::{ + ffi, + objects::PyAny, + types::{CFunction, Function}, + AsPyPointer, IntoPy, PyMethodDef, PyMethodType, PyResult, +}; + +/// Represents a builtin Python function object. +#[repr(transparent)] +pub struct PyCFunction<'py>(pub(crate) PyAny<'py>); +pyo3_native_object!(PyCFunction<'py>, CFunction, 'py); + +fn get_name(name: &str) -> PyResult<&'static CStr> { + let cstr = CString::new(name) + .map_err(|_| PyValueError::new_err("Function name cannot contain contain NULL byte."))?; + Ok(Box::leak(cstr.into_boxed_c_str())) +} + +fn get_doc(doc: &str) -> PyResult<&'static CStr> { + let cstr = CString::new(doc) + .map_err(|_| PyValueError::new_err("Document cannot contain contain NULL byte."))?; + Ok(Box::leak(cstr.into_boxed_c_str())) +} + +impl<'py> PyCFunction<'py> { + /// Create a new built-in function with keywords. + /// + /// See [raw_pycfunction] for documentation on how to get the `fun` argument. + pub fn new_with_keywords( + fun: ffi::PyCFunctionWithKeywords, + name: &str, + doc: &str, + py_or_module: PyFunctionArguments<'py>, + ) -> PyResult { + Self::internal_new( + get_name(name)?, + get_doc(doc)?, + PyMethodType::PyCFunctionWithKeywords(fun), + ffi::METH_VARARGS | ffi::METH_KEYWORDS, + py_or_module, + ) + } + + /// Create a new built-in function without keywords. + pub fn new( + fun: ffi::PyCFunction, + name: &str, + doc: &str, + py_or_module: PyFunctionArguments<'py>, + ) -> PyResult { + Self::internal_new( + get_name(name)?, + get_doc(doc)?, + PyMethodType::PyCFunction(fun), + ffi::METH_NOARGS, + py_or_module, + ) + } + + #[doc(hidden)] + pub fn internal_new( + name: &'static CStr, + doc: &'static CStr, + method_type: PyMethodType, + flags: std::os::raw::c_int, + py_or_module: PyFunctionArguments<'py>, + ) -> PyResult { + let (py, module) = py_or_module.into_py_and_maybe_module(); + let method_def = PyMethodDef { + ml_name: name, + ml_meth: method_type, + ml_flags: flags, + ml_doc: doc, + }; + let def = method_def.as_method_def(); + let (mod_ptr, module_name) = if let Some(m) = module { + let mod_ptr = m.as_ptr(); + let name = m.name()?.into_py(py); + (mod_ptr, Some(name)) + } else { + (std::ptr::null_mut(), None) + }; + + unsafe { + PyAny::from_raw_or_fetch_err( + py, + ffi::PyCFunction_NewEx(Box::into_raw(Box::new(def)), mod_ptr, module_name.as_ptr()), + ) + .map(Self) + } + } +} + +/// Represents a Python function object. +#[repr(transparent)] +pub struct PyFunction<'py>(pub(crate) PyAny<'py>); + +#[cfg(not(Py_LIMITED_API))] +pyo3_native_object!(PyFunction<'py>, Function, 'py); diff --git a/src/experimental/objects/iterator.rs b/src/experimental/objects/iterator.rs new file mode 100644 index 00000000000..76b62211bd2 --- /dev/null +++ b/src/experimental/objects/iterator.rs @@ -0,0 +1,229 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors +// +// based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython + +use crate::{ + ffi, objects::PyAny, objects::PyNativeObject, types::Iterator, AsPyPointer, PyErr, PyResult, + Python, +}; +#[cfg(any(not(Py_LIMITED_API), Py_3_8))] +use crate::{objects::PyTryFrom, PyDowncastError}; + +/// A Python iterator object. +/// +/// # Example +/// +/// ```rust +/// # use pyo3::experimental::prelude::*; +/// use pyo3::experimental::objects::PyIterator; +/// +/// # fn main() -> PyResult<()> { +/// let gil = Python::acquire_gil(); +/// let py = gil.python(); +/// let list = py.eval("iter([1, 2, 3, 4])", None, None)?; +/// let numbers: PyResult> = list.iter()?.map(|i| i.and_then(|any| any.extract())).collect(); +/// let sum: usize = numbers?.iter().sum(); +/// assert_eq!(sum, 10); +/// # Ok(()) +/// # } +/// ``` +#[repr(transparent)] +pub struct PyIterator<'py>(pub(crate) PyAny<'py>); +pyo3_native_object!(PyIterator<'py>, Iterator, 'py); + +impl<'py> PyIterator<'py> { + /// Constructs a `PyIterator` from a Python iterable object. + /// + /// Equivalent to Python's built-in `iter` function. + pub fn from_object(py: Python<'py>, obj: &T) -> PyResult + where + T: AsPyPointer, + { + unsafe { PyAny::from_raw_or_fetch_err(py, ffi::PyObject_GetIter(obj.as_ptr())).map(Self) } + } + + fn next(&self) -> Option>> { + let py = self.py(); + + match unsafe { PyAny::from_raw(py, ffi::PyIter_Next(self.0.as_ptr())) } { + Some(obj) => Some(Ok(obj)), + None => { + if PyErr::occurred(py) { + Some(Err(PyErr::fetch(py))) + } else { + None + } + } + } + } +} + +impl<'py> std::iter::Iterator for &'_ PyIterator<'py> { + type Item = PyResult>; + + /// Retrieves the next item from an iterator. + /// + /// Returns `None` when the iterator is exhausted. + /// If an exception occurs, returns `Some(Err(..))`. + /// Further `next()` calls after an exception occurs are likely + /// to repeatedly result in the same exception. + fn next(&mut self) -> Option { + (*self).next() + } +} + +impl<'py> std::iter::Iterator for PyIterator<'py> { + type Item = PyResult>; + fn next(&mut self) -> Option { + (*self).next() + } +} + +// PyIter_Check does not exist in the limited API until 3.8 +#[cfg(any(not(Py_LIMITED_API), Py_3_8))] +impl<'a, 'py> PyTryFrom<'a, 'py> for PyIterator<'py> { + fn try_from(value: &'a PyAny<'py>) -> Result<&'a Self, PyDowncastError<'py>> { + unsafe { + if ffi::PyIter_Check(value.as_ptr()) != 0 { + Ok(::try_from_unchecked(value)) + } else { + Err(PyDowncastError::new(value.into_ty_ref(), "Iterator")) + } + } + } + + fn try_from_exact(value: &'a PyAny<'py>) -> Result<&'a Self, PyDowncastError<'py>> { + ::try_from(value) + } + + #[inline] + unsafe fn try_from_unchecked(value: &'a PyAny<'py>) -> &'a Self { + let ptr = value as *const _ as *const PyIterator; + &*ptr + } +} + +#[cfg(test)] +mod tests { + use super::PyIterator; + use crate::exceptions::PyTypeError; + use crate::gil::GILPool; + use crate::objects::{PyDict, PyList, PyNativeObject}; + #[cfg(any(not(Py_LIMITED_API), Py_3_8))] + use crate::{objects::PyTryFrom, Py, PyAny}; + use crate::{Python, ToPyObject}; + use indoc::indoc; + + #[test] + fn vec_iter() { + let gil_guard = Python::acquire_gil(); + let py = gil_guard.python(); + let obj = vec![10, 20].to_object(py); + let inst = obj.as_ref(py); + let mut it = inst.iter().unwrap(); + assert_eq!(10, it.next().unwrap().unwrap().extract().unwrap()); + assert_eq!(20, it.next().unwrap().unwrap().extract().unwrap()); + assert!(it.next().is_none()); + } + + #[test] + fn iter_refcnt() { + let obj; + let count; + { + let gil_guard = Python::acquire_gil(); + let py = gil_guard.python(); + obj = vec![10, 20].to_object(py); + count = obj.get_refcnt(py); + } + + { + let gil_guard = Python::acquire_gil(); + let py = gil_guard.python(); + let inst = obj.as_ref(py); + let mut it = inst.iter().unwrap(); + + assert_eq!(10, it.next().unwrap().unwrap().extract().unwrap()); + } + assert_eq!(count, obj.get_refcnt(Python::acquire_gil().python())); + } + + #[test] + fn iter_item_refcnt() { + let gil_guard = Python::acquire_gil(); + let py = gil_guard.python(); + + let obj; + let none; + let count; + { + let _pool = unsafe { GILPool::new() }; + let l = PyList::empty(py); + none = py.None(); + l.append(10).unwrap(); + l.append(&none).unwrap(); + count = none.get_refcnt(py); + obj = l.to_object(py); + } + + { + let _pool = unsafe { GILPool::new() }; + let inst = obj.as_ref(py); + let mut it = inst.iter().unwrap(); + + assert_eq!(10, it.next().unwrap().unwrap().extract().unwrap()); + assert!(it.next().unwrap().unwrap().is_none()); + } + assert_eq!(count, none.get_refcnt(py)); + } + + #[test] + fn fibonacci_generator() { + let fibonacci_generator = indoc!( + r#" + def fibonacci(target): + a = 1 + b = 1 + for _ in range(target): + yield a + a, b = b, a + b + "# + ); + + let gil = Python::acquire_gil(); + let py = gil.python(); + + let context = PyDict::new(py); + py.run(fibonacci_generator, None, Some(context.as_owned_ref())) + .unwrap(); + + let generator = py + .eval("fibonacci(5)", None, Some(context.as_owned_ref())) + .unwrap(); + for (actual, expected) in generator.iter().unwrap().zip(&[1, 1, 2, 3, 5]) { + let actual = actual.unwrap().extract::().unwrap(); + assert_eq!(actual, *expected) + } + } + + #[test] + fn int_not_iterable() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let x = 5.to_object(py); + let err = PyIterator::from_object(py, &x).unwrap_err(); + + assert!(err.is_instance::(py)) + } + + #[test] + #[cfg(any(not(Py_LIMITED_API), Py_3_8))] + fn iterator_try_from() { + let gil_guard = Python::acquire_gil(); + let py = gil_guard.python(); + let obj: Py = vec![10, 20].to_object(py).as_ref(py).iter().unwrap().into(); + let iter: &PyIterator = PyIterator::try_from(obj.as_object(py)).unwrap(); + assert_eq!(obj, iter.into()); + } +} diff --git a/src/experimental/objects/list.rs b/src/experimental/objects/list.rs new file mode 100644 index 00000000000..3b6879d0537 --- /dev/null +++ b/src/experimental/objects/list.rs @@ -0,0 +1,452 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors +// +// based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython + +use crate::err::{self, PyResult}; +use crate::ffi::{self, Py_ssize_t}; +use crate::{ + objects::{PyAny, PyNativeObject}, + types::List, + AsPyPointer, IntoPy, IntoPyPointer, PyObject, Python, ToBorrowedObject, ToPyObject, +}; +/// Represents a Python `list`. +#[repr(transparent)] +pub struct PyList<'py>(pub(crate) PyAny<'py>); + +pyo3_native_object!(PyList<'py>, List, 'py); + +impl<'py> PyList<'py> { + /// Constructs a new list with the given elements. + pub fn new(py: Python<'py>, elements: impl IntoIterator) -> Self + where + T: ToPyObject, + U: ExactSizeIterator, + { + let elements_iter = elements.into_iter(); + let len = elements_iter.len(); + unsafe { + let list = PyList::with_length(py, len as isize); + for (i, e) in elements_iter.enumerate() { + list.set_item_unchecked(i as isize, e.to_object(py)); + } + list + } + } + + /// Constructs a new empty list. + pub fn empty(py: Python<'py>) -> Self { + unsafe { Self(PyAny::from_raw_or_panic(py, ffi::PyList_New(0))) } + } + + /// Returns the length of the list. + pub fn len(&self) -> usize { + // non-negative Py_ssize_t should always fit into Rust usize + unsafe { ffi::PyList_Size(self.as_ptr()) as usize } + } + + /// Checks if the list is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Gets the item at the specified index. + /// + /// Panics if the index is out of range. + pub fn get_item(&self, index: isize) -> PyAny<'py> { + assert!((index.abs() as usize) < self.len()); + unsafe { + let ptr = ffi::PyList_GetItem(self.as_ptr(), index as Py_ssize_t); + // PyList_GetItem return borrowed ptr; must make owned for safety (see #890). + PyAny::from_borrowed_ptr_or_panic(self.py(), ptr) + } + } + + /// Sets the item at the specified index. + /// + /// Panics if the index is out of range. + pub fn set_item(&self, index: isize, item: I) -> PyResult<()> + where + I: ToPyObject, + { + unsafe { + err::error_on_minusone( + self.py(), + self.set_item_unchecked(index, item.to_object(self.py())), + ) + } + } + + /// Appends an item to the list. + pub fn append(&self, item: I) -> PyResult<()> + where + I: ToBorrowedObject, + { + item.with_borrowed_ptr(self.py(), |item| unsafe { + err::error_on_minusone(self.py(), ffi::PyList_Append(self.as_ptr(), item)) + }) + } + + /// Inserts an item at the specified index. + /// + /// Panics if the index is out of range. + pub fn insert(&self, index: isize, item: I) -> PyResult<()> + where + I: ToBorrowedObject, + { + item.with_borrowed_ptr(self.py(), |item| unsafe { + err::error_on_minusone(self.py(), ffi::PyList_Insert(self.as_ptr(), index, item)) + }) + } + + /// Returns an iterator over this list's items. + pub fn iter(&self) -> PyListIterator<'_, 'py> { + PyListIterator { + list: self, + index: 0, + } + } + + /// Sorts the list in-place. Equivalent to the Python expression `l.sort()`. + pub fn sort(&self) -> PyResult<()> { + unsafe { err::error_on_minusone(self.py(), ffi::PyList_Sort(self.as_ptr())) } + } + + /// Reverses the list in-place. Equivalent to the Python expression `l.reverse()`. + pub fn reverse(&self) -> PyResult<()> { + unsafe { err::error_on_minusone(self.py(), ffi::PyList_Reverse(self.as_ptr())) } + } + + /// Constructs a list with size NULL elements. All must be set before this list can be + /// safely used. + unsafe fn with_length(py: Python<'py>, size: isize) -> Self { + Self(PyAny::from_raw_or_panic(py, ffi::PyList_New(size))) + } + + /// Set item on self. The caller should check for length error (indicated by -1 return value); + unsafe fn set_item_unchecked( + &self, + index: isize, + item: impl IntoPyPointer, + ) -> std::os::raw::c_int { + ffi::PyList_SetItem(self.as_ptr(), index, item.into_ptr()) + } +} + +/// Used by `PyList::iter()`. +pub struct PyListIterator<'a, 'py> { + list: &'a PyList<'py>, + index: isize, +} + +impl<'py> Iterator for PyListIterator<'_, 'py> { + type Item = PyAny<'py>; + + #[inline] + fn next(&mut self) -> Option> { + if self.index < self.list.len() as isize { + let item = self.list.get_item(self.index); + self.index += 1; + Some(item) + } else { + None + } + } +} + +impl<'a, 'py> std::iter::IntoIterator for &'a PyList<'py> { + type Item = PyAny<'py>; + type IntoIter = PyListIterator<'a, 'py>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl ToPyObject for [T] +where + T: ToPyObject, +{ + fn to_object(&self, py: Python<'_>) -> PyObject { + unsafe { + let list = PyList::with_length(py, self.len() as isize); + for (i, e) in self.iter().enumerate() { + list.set_item_unchecked(i as isize, e.to_object(py)); + } + list.into() + } + } +} + +macro_rules! array_impls { + ($($N:expr),+) => { + $( + impl IntoPy for [T; $N] + where + T: ToPyObject + { + fn into_py(self, py: Python) -> PyObject { + self.as_ref().to_object(py) + } + } + )+ + } +} + +array_impls!( + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32 +); + +impl ToPyObject for Vec +where + T: ToPyObject, +{ + fn to_object(&self, py: Python<'_>) -> PyObject { + self.as_slice().to_object(py) + } +} + +impl IntoPy for Vec +where + T: IntoPy, +{ + fn into_py(self, py: Python) -> PyObject { + unsafe { + let list = PyList::with_length(py, self.len() as isize); + for (i, e) in self.into_iter().enumerate() { + list.set_item_unchecked(i as isize, e.into_py(py)); + } + list.into() + } + } +} + +#[cfg(test)] +mod test { + use crate::types::PyList; + use crate::Python; + use crate::{IntoPy, PyObject, PyTryFrom, ToPyObject}; + + #[test] + fn test_new() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = vec![2, 3, 5, 7]; + let list = PyList::new(py, &v); + assert_eq!(2, list.get_item(0).extract::().unwrap()); + assert_eq!(3, list.get_item(1).extract::().unwrap()); + assert_eq!(5, list.get_item(2).extract::().unwrap()); + assert_eq!(7, list.get_item(3).extract::().unwrap()); + } + + #[test] + fn test_len() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = vec![1, 2, 3, 4]; + let ob = v.to_object(py); + let list = ::try_from(ob.as_ref(py)).unwrap(); + assert_eq!(4, list.len()); + } + + #[test] + fn test_get_item() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = vec![2, 3, 5, 7]; + let ob = v.to_object(py); + let list = ::try_from(ob.as_ref(py)).unwrap(); + assert_eq!(2, list.get_item(0).extract::().unwrap()); + assert_eq!(3, list.get_item(1).extract::().unwrap()); + assert_eq!(5, list.get_item(2).extract::().unwrap()); + assert_eq!(7, list.get_item(3).extract::().unwrap()); + } + + #[test] + fn test_get_parked_item() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = vec![2, 3, 5, 7]; + let ob = v.to_object(py); + let list = ::try_from(ob.as_ref(py)).unwrap(); + assert_eq!(2, list.get_parked_item(0).extract::(py).unwrap()); + assert_eq!(3, list.get_parked_item(1).extract::(py).unwrap()); + assert_eq!(5, list.get_parked_item(2).extract::(py).unwrap()); + assert_eq!(7, list.get_parked_item(3).extract::(py).unwrap()); + } + + #[test] + fn test_set_item() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = vec![2, 3, 5, 7]; + let ob = v.to_object(py); + let list = ::try_from(ob.as_ref(py)).unwrap(); + let val = 42i32.to_object(py); + assert_eq!(2, list.get_item(0).extract::().unwrap()); + list.set_item(0, val).unwrap(); + assert_eq!(42, list.get_item(0).extract::().unwrap()); + } + + #[test] + fn test_set_item_refcnt() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let cnt; + { + let _pool = unsafe { crate::GILPool::new() }; + let v = vec![2]; + let ob = v.to_object(py); + let list = ::try_from(ob.as_ref(py)).unwrap(); + let none = py.None(); + cnt = none.get_refcnt(py); + list.set_item(0, none).unwrap(); + } + + assert_eq!(cnt, py.None().get_refcnt(py)); + } + + #[test] + fn test_insert() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = vec![2, 3, 5, 7]; + let ob = v.to_object(py); + let list = ::try_from(ob.as_ref(py)).unwrap(); + let val = 42i32.to_object(py); + assert_eq!(4, list.len()); + assert_eq!(2, list.get_item(0).extract::().unwrap()); + list.insert(0, val).unwrap(); + assert_eq!(5, list.len()); + assert_eq!(42, list.get_item(0).extract::().unwrap()); + assert_eq!(2, list.get_item(1).extract::().unwrap()); + } + + #[test] + fn test_insert_refcnt() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let cnt; + { + let _pool = unsafe { crate::GILPool::new() }; + let list = PyList::empty(py); + let none = py.None(); + cnt = none.get_refcnt(py); + list.insert(0, none).unwrap(); + } + + assert_eq!(cnt, py.None().get_refcnt(py)); + } + + #[test] + fn test_append() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = vec![2]; + let ob = v.to_object(py); + let list = ::try_from(ob.as_ref(py)).unwrap(); + list.append(3).unwrap(); + assert_eq!(2, list.get_item(0).extract::().unwrap()); + assert_eq!(3, list.get_item(1).extract::().unwrap()); + } + + #[test] + fn test_append_refcnt() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let cnt; + { + let _pool = unsafe { crate::GILPool::new() }; + let list = PyList::empty(py); + let none = py.None(); + cnt = none.get_refcnt(py); + list.append(none).unwrap(); + } + assert_eq!(cnt, py.None().get_refcnt(py)); + } + + #[test] + fn test_iter() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = vec![2, 3, 5, 7]; + let ob = v.to_object(py); + let list = ::try_from(ob.as_ref(py)).unwrap(); + let mut idx = 0; + for el in list.iter() { + assert_eq!(v[idx], el.extract::().unwrap()); + idx += 1; + } + assert_eq!(idx, v.len()); + } + + #[test] + fn test_into_iter() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = vec![1, 2, 3, 4]; + let ob = v.to_object(py); + let list = ::try_from(ob.as_ref(py)).unwrap(); + for (i, item) in list.iter().enumerate() { + assert_eq!((i + 1) as i32, item.extract::().unwrap()); + } + } + + #[test] + fn test_extract() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = vec![2, 3, 5, 7]; + let ob = v.to_object(py); + let list = ::try_from(ob.as_ref(py)).unwrap(); + let v2 = list.as_ref().extract::>().unwrap(); + assert_eq!(v, v2); + } + + #[test] + fn test_sort() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = vec![7, 3, 2, 5]; + let list = PyList::new(py, &v); + assert_eq!(7, list.get_item(0).extract::().unwrap()); + assert_eq!(3, list.get_item(1).extract::().unwrap()); + assert_eq!(2, list.get_item(2).extract::().unwrap()); + assert_eq!(5, list.get_item(3).extract::().unwrap()); + list.sort().unwrap(); + assert_eq!(2, list.get_item(0).extract::().unwrap()); + assert_eq!(3, list.get_item(1).extract::().unwrap()); + assert_eq!(5, list.get_item(2).extract::().unwrap()); + assert_eq!(7, list.get_item(3).extract::().unwrap()); + } + + #[test] + fn test_reverse() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = vec![2, 3, 5, 7]; + let list = PyList::new(py, &v); + assert_eq!(2, list.get_item(0).extract::().unwrap()); + assert_eq!(3, list.get_item(1).extract::().unwrap()); + assert_eq!(5, list.get_item(2).extract::().unwrap()); + assert_eq!(7, list.get_item(3).extract::().unwrap()); + list.reverse().unwrap(); + assert_eq!(7, list.get_item(0).extract::().unwrap()); + assert_eq!(5, list.get_item(1).extract::().unwrap()); + assert_eq!(3, list.get_item(2).extract::().unwrap()); + assert_eq!(2, list.get_item(3).extract::().unwrap()); + } + + #[test] + fn test_array_into_py() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let array: PyObject = [1, 2].into_py(py); + let list = ::try_from(array.as_ref(py)).unwrap(); + assert_eq!(1, list.get_item(0).extract::().unwrap()); + assert_eq!(2, list.get_item(1).extract::().unwrap()); + } +} diff --git a/src/experimental/objects/mod.rs b/src/experimental/objects/mod.rs new file mode 100644 index 00000000000..be4729b3e6b --- /dev/null +++ b/src/experimental/objects/mod.rs @@ -0,0 +1,336 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors + +//! Various types defined by the Python interpreter such as `int`, `str` and `tuple`. + +use crate::{ + ffi, AsPyPointer, PyCell, PyClass, PyDowncastError, PyNativeType, PyRef, PyRefMut, PyResult, + PyTypeInfo, Python, +}; + +pub use self::any::PyAny; +pub use self::boolobject::PyBool; +pub use self::bytearray::PyByteArray; +pub use self::bytes::PyBytes; +pub use self::complex::PyComplex; +pub use self::datetime::PyDeltaAccess; +pub use self::datetime::{ + PyDate, PyDateAccess, PyDateTime, PyTime, PyTimeAccess, PyTimeDelta, PyTzInfo, +}; +pub use self::dict::{IntoPyDict, PyDict}; +pub use self::floatob::PyFloat; +pub use self::function::{PyCFunction, PyFunction}; +pub use self::iterator::PyIterator; +pub use self::list::PyList; +pub use self::module::PyModule; +pub use self::num::PyInt; +pub use self::sequence::PySequence; +pub use self::set::{PyFrozenSet, PySet}; +pub use self::slice::{PySlice, PySliceIndices}; +pub use self::str::PyStr; +pub use self::tuple::PyTuple; +pub use self::typeobject::PyType; + +// For easing the transition +pub use self::num::PyInt as PyLong; +pub use self::str::PyStr as PyString; + +#[macro_export] +#[doc(hidden)] +macro_rules! pyo3_native_object_base { + ($object:ty, $ty:ty, $py:lifetime) => { + impl<$py> $crate::ToPyObject for $object + { + #[inline] + fn to_object(&self, py: $crate::Python) -> $crate::PyObject { + use $crate::AsPyPointer; + unsafe { $crate::PyObject::from_borrowed_ptr(py, self.as_ptr()) } + } + } + + impl<$py> From<&'_ $object> for $crate::PyObject + { + #[inline] + fn from(object: & $object) -> $crate::PyObject { + use $crate::{AsPyPointer, objects::PyNativeObject}; + unsafe { $crate::PyObject::from_borrowed_ptr(object.py(), object.as_ptr()) } + } + } + + impl<$py> From<$object> for $crate::Py<$ty> + { + #[inline] + fn from(object: $object) -> $crate::Py<$ty> { + use $crate::{IntoPyPointer, objects::PyNativeObject}; + unsafe { $crate::Py::from_owned_ptr(object.py(), object.into_ptr()) } + } + } + + impl<'a, $py> $crate::objects::FromPyObject<'a, $py> for &'a $object { + fn extract(any: &'a $crate::objects::PyAny<$py>) -> $crate::PyResult { + any.downcast::<$object>().map_err(Into::into) + } + } + + impl<$py> ::std::fmt::Debug for $object { + fn fmt(&self, f: &mut ::std::fmt::Formatter) + -> Result<(), ::std::fmt::Error> + { + let s = self.repr().map_err(|_| ::std::fmt::Error)?; + f.write_str(&s.to_string_lossy()) + } + } + + impl<$py> ::std::fmt::Display for $object { + fn fmt(&self, f: &mut ::std::fmt::Formatter) + -> Result<(), ::std::fmt::Error> + { + let s = self.str().map_err(|_| ::std::fmt::Error)?; + f.write_str(&s.to_string_lossy()) + } + } + + unsafe impl<$py> $crate::objects::PyNativeObject<$py> for $object { + type NativeType = $ty; + #[inline] + fn py(&self) -> $crate::Python<$py> { + self.1 + } + #[inline] + fn as_owned_ref(&self) -> &Self::NativeType { + unsafe { self.py().from_borrowed_ptr(self.as_ptr()) } + } + #[inline] + fn into_ty_ref(&self) -> &$py Self::NativeType { + use $crate::IntoPyPointer; + unsafe { self.py().from_owned_ptr(self.into_ptr()) } + } + } + + impl $ty { + pub fn to_owned<$py>(&$py self) -> $object { + use $crate::{PyNativeType, IntoPyPointer}; + unsafe { $crate::objects::PyAny::from_raw_or_panic(self.py(), self.into_ptr()).extract().unwrap() } + } + } + } +} + +#[macro_export] +#[doc(hidden)] +macro_rules! pyo3_native_object { + ($object:ty, $ty:ty, $py:lifetime) => { + $crate::pyo3_native_object_base!($object, $ty, $py); + + impl<$py> std::ops::Deref for $object { + type Target = $crate::objects::PyAny<$py>; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl<$py> From<$object> for $crate::PyObject { + #[inline] + fn from(obj: $object) -> $crate::PyObject { + $crate::Py::<$ty>::from(obj).into() + } + } + + impl<$py> $crate::objects::FromPyObject<'_, $py> for $object { + fn extract(any: &$crate::objects::PyAny<$py>) -> $crate::PyResult { + Ok(Self(any.clone())) + } + } + + impl<$py> AsPyPointer for $object { + #[inline] + fn as_ptr(&self) -> *mut $crate::ffi::PyObject { + self.0.as_ptr() + } + } + }; +} + +mod any; +mod boolobject; +mod bytearray; +mod bytes; +mod complex; +mod datetime; +mod dict; +mod floatob; +mod function; +mod iterator; +mod list; +mod module; +mod num; +mod sequence; +mod set; +mod slice; +mod str; +mod tuple; +mod typeobject; + +/// To implement this trait, &Self *must* be equivalent to *mut ffi::PyObject +pub unsafe trait PyNativeObject<'py>: Sized + 'py { + type NativeType: PyNativeType; + fn py(&self) -> Python<'py>; + fn as_owned_ref(&self) -> &Self::NativeType; + fn into_ty_ref(&self) -> &'py Self::NativeType; + #[inline] + unsafe fn unchecked_downcast<'a>(any: &'a PyAny<'py>) -> &'a Self { + &*(any as *const PyAny as *const Self) + } + + #[inline] + unsafe fn from_borrowed_ptr<'a>(_py: Python<'py>, ptr: &*mut ffi::PyObject) -> &'a Self { + &*(ptr as *const *mut ffi::PyObject as *const Self) + } +} + +pub trait PyNativeObjectInfo<'py>: PyNativeObject<'py> +where + Self::NativeType: PyTypeInfo, +{ +} + +impl<'py, T> PyNativeObjectInfo<'py> for T +where + T: PyNativeObject<'py>, + T::NativeType: PyTypeInfo, +{ +} + +/// New variant of conversion::FromPyObject which doesn't create owned references. +pub trait FromPyObject<'a, 'py>: Sized { + /// Extracts `Self` from the source `PyAny`. + fn extract(ob: &'a PyAny<'py>) -> PyResult; +} + +impl<'a, T> FromPyObject<'a, 'a> for &'a PyCell +where + T: PyClass, +{ + fn extract(obj: &'a PyAny<'a>) -> PyResult { + PyTryFrom::try_from(obj).map_err(Into::into) + } +} + +impl FromPyObject<'_, '_> for T +where + T: PyClass + Clone, +{ + fn extract(obj: &PyAny) -> PyResult { + let cell: &PyCell = PyTryFrom::try_from(obj)?; + Ok(unsafe { cell.try_borrow_unguarded()?.clone() }) + } +} + +impl<'py, T> FromPyObject<'py, 'py> for PyRef<'py, T> +where + T: PyClass, +{ + fn extract(obj: &'py PyAny<'py>) -> PyResult { + let cell: &PyCell = PyTryFrom::try_from(obj)?; + cell.try_borrow().map_err(Into::into) + } +} + +impl<'py, T> FromPyObject<'py, 'py> for PyRefMut<'py, T> +where + T: PyClass, +{ + fn extract(obj: &'py PyAny<'py>) -> PyResult { + let cell: &PyCell = PyTryFrom::try_from(obj)?; + cell.try_borrow_mut().map_err(Into::into) + } +} + +impl<'a, 'py, T> FromPyObject<'a, 'py> for Option +where + T: FromPyObject<'a, 'py>, +{ + fn extract(obj: &'a PyAny<'py>) -> PyResult { + if obj.as_ptr() == unsafe { ffi::Py_None() } { + Ok(None) + } else { + T::extract(obj).map(Some) + } + } +} + +/// Trait implemented by Python object types that allow a checked downcast. +/// If `T` implements `PyTryFrom`, we can convert `&PyAny` to `&T`. +/// +/// This trait is similar to `std::convert::TryFrom` +pub trait PyTryFrom<'a, 'py>: Sized { + /// Cast from a concrete Python object type to PyObject. + fn try_from(any: &'a PyAny<'py>) -> Result<&'a Self, crate::PyDowncastError<'py>>; + + /// Cast from a concrete Python object type to PyObject. With exact type check. + fn try_from_exact(any: &'a PyAny<'py>) -> Result<&'a Self, crate::PyDowncastError<'py>>; + + /// Cast a PyAny to a specific type of PyObject. The caller must + /// have already verified the reference is for this type. + unsafe fn try_from_unchecked(any: &'a PyAny<'py>) -> &'a Self; +} + +impl<'a, 'py, T> PyTryFrom<'a, 'py> for T +where + T: PyNativeObject<'py> + PyNativeObjectInfo<'py> + 'py, + T::NativeType: PyTypeInfo, +{ + fn try_from(any: &'a PyAny<'py>) -> Result<&'a T, PyDowncastError<'py>> { + unsafe { + if T::NativeType::is_type_of(any.as_owned_ref()) { + Ok(T::try_from_unchecked(any)) + } else { + Err(PyDowncastError::new(any.into_ty_ref(), T::NativeType::NAME)) + } + } + } + + fn try_from_exact(any: &'a PyAny<'py>) -> Result<&'a T, PyDowncastError<'py>> { + unsafe { + if T::NativeType::is_exact_type_of(any.as_owned_ref()) { + Ok(T::try_from_unchecked(any)) + } else { + Err(PyDowncastError::new(any.into_ty_ref(), T::NativeType::NAME)) + } + } + } + + #[inline] + unsafe fn try_from_unchecked(any: &'a PyAny<'py>) -> &'a T { + T::unchecked_downcast(any) + } +} + +impl<'py, T> PyTryFrom<'py, 'py> for PyCell +where + T: 'py + PyClass, +{ + fn try_from(any: &'py PyAny<'py>) -> Result<&'py Self, PyDowncastError<'py>> { + unsafe { + if T::is_type_of(any.as_owned_ref()) { + Ok(Self::try_from_unchecked(any)) + } else { + Err(PyDowncastError::new(any.into_ty_ref(), T::NAME)) + } + } + } + fn try_from_exact(any: &'py PyAny<'py>) -> Result<&'py Self, PyDowncastError<'py>> { + unsafe { + if T::is_exact_type_of(any.as_owned_ref()) { + Ok(Self::try_from_unchecked(any)) + } else { + Err(PyDowncastError::new(any.into_ty_ref(), T::NAME)) + } + } + } + #[inline] + unsafe fn try_from_unchecked(any: &'py PyAny<'py>) -> &'py Self { + PyCell::unchecked_downcast(any.into_ty_ref()) + } +} diff --git a/src/experimental/objects/module.rs b/src/experimental/objects/module.rs new file mode 100644 index 00000000000..8770e28f47c --- /dev/null +++ b/src/experimental/objects/module.rs @@ -0,0 +1,278 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors +// +// based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython + +use crate::callback::IntoPyCallbackOutput; +use crate::err::{PyErr, PyResult}; +use crate::exceptions; +use crate::ffi; +use crate::objects::{PyAny, PyCFunction, PyDict, PyList, PyNativeObject}; +use crate::pyclass::PyClass; +use crate::type_object::PyTypeObject; +use crate::{ + types::{Module, Tuple}, + AsPyPointer, IntoPy, Py, PyObject, Python, +}; +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; +use std::str; + +/// Represents a Python `module` object. +#[repr(transparent)] +pub struct PyModule<'py>(pub(crate) PyAny<'py>); + +pyo3_native_object!(PyModule<'py>, Module, 'py); + +impl<'py> PyModule<'py> { + /// Creates a new module object with the `__name__` attribute set to name. + pub fn new(py: Python<'py>, name: &str) -> PyResult { + // Could use PyModule_NewObject, but it doesn't exist on PyPy. + let name = CString::new(name)?; + unsafe { PyAny::from_raw_or_fetch_err(py, ffi::PyModule_New(name.as_ptr())).map(Self) } + } + + /// Imports the Python module with the specified name. + pub fn import(py: Python<'py>, name: &str) -> PyResult { + crate::types::with_tmp_string(py, name, |name| unsafe { + PyAny::from_raw_or_fetch_err(py, ffi::PyImport_Import(name)).map(Self) + }) + } + + /// Loads the Python code specified into a new module. + /// + /// `code` is the raw Python you want to load into the module. + /// `file_name` is the file name to associate with the module + /// (this is used when Python reports errors, for example). + /// `module_name` is the name to give the module. + pub fn from_code( + py: Python<'py>, + code: &str, + file_name: &str, + module_name: &str, + ) -> PyResult { + let data = CString::new(code)?; + let filename = CString::new(file_name)?; + let module = CString::new(module_name)?; + + unsafe { + let cptr = ffi::Py_CompileString(data.as_ptr(), filename.as_ptr(), ffi::Py_file_input); + if cptr.is_null() { + return Err(PyErr::fetch(py)); + } + + let mptr = ffi::PyImport_ExecCodeModuleEx(module.as_ptr(), cptr, filename.as_ptr()); + PyAny::from_raw_or_fetch_err(py, mptr).map(Self) + } + } + + /// Return the dictionary object that implements module's namespace; + /// this object is the same as the `__dict__` attribute of the module object. + pub fn dict(&self) -> PyDict<'py> { + unsafe { + // PyModule_GetDict returns borrowed ptr; must make owned for safety (see #890). + let ptr = ffi::PyModule_GetDict(self.as_ptr()); + PyDict(PyAny::from_borrowed_ptr_or_panic(self.py(), ptr)) + } + } + + /// Return the index (`__all__`) of the module, creating one if needed. + pub fn index(&self) -> PyResult> { + match self.getattr("__all__") { + Ok(idx) => idx.extract(), + Err(err) => { + if err.is_instance::(self.py()) { + let l = PyList::empty(self.py()); + self.setattr("__all__", l.clone()).map_err(PyErr::from)?; + Ok(l) + } else { + Err(err) + } + } + } + } + + unsafe fn str_from_ptr(&self, ptr: *const c_char) -> PyResult<&str> { + if ptr.is_null() { + Err(PyErr::fetch(self.py())) + } else { + let slice = CStr::from_ptr(ptr).to_bytes(); + match str::from_utf8(slice) { + Ok(s) => Ok(s), + Err(e) => Err(PyErr::from_instance( + exceptions::PyUnicodeDecodeError::new_utf8(self.py(), slice, e)?, + )), + } + } + } + + /// Returns the module's name. + /// + /// May fail if the module does not have a `__name__` attribute. + pub fn name(&self) -> PyResult<&str> { + unsafe { self.str_from_ptr(ffi::PyModule_GetName(self.as_ptr())) } + } + + /// Returns the module's filename. + /// + /// May fail if the module does not have a `__file__` attribute. + #[cfg(not(all(windows, PyPy)))] + pub fn filename(&self) -> PyResult<&str> { + unsafe { self.str_from_ptr(ffi::PyModule_GetFilename(self.as_ptr())) } + } + + /// Calls a function in the module. + /// + /// This is equivalent to the Python expression `module.name(*args, **kwargs)`. + pub fn call( + &self, + name: &str, + args: impl IntoPy>, + kwargs: Option<&PyDict>, + ) -> PyResult> { + self.getattr(name)?.call(args, kwargs) + } + + /// Calls a function in the module with only positional arguments. + /// + /// This is equivalent to the Python expression `module.name(*args)`. + pub fn call1(&self, name: &str, args: impl IntoPy>) -> PyResult> { + self.getattr(name)?.call1(args) + } + + /// Calls a function in the module without arguments. + /// + /// This is equivalent to the Python expression `module.name()`. + pub fn call0(&self, name: &str) -> PyResult> { + self.getattr(name)?.call0() + } + + /// Gets a member from the module. + /// + /// This is equivalent to the Python expression `module.name`. + pub fn get(&self, name: &str) -> PyResult> { + self.getattr(name) + } + + /// Adds a member to the module. + /// + /// This is a convenience function which can be used from the module's initialization function. + pub fn add(&self, name: &str, value: V) -> PyResult<()> + where + V: IntoPy, + { + self.index()? + .append(name) + .expect("could not append __name__ to __all__"); + self.setattr(name, value.into_py(self.py())) + } + + /// Adds a new extension type to the module. + /// + /// This is a convenience function that initializes the `class`, + /// sets `new_type.__module__` to this module's name, + /// and adds the type to this module. + pub fn add_class(&self) -> PyResult<()> + where + T: PyClass, + { + self.add(T::NAME, ::type_object(self.py())) + } + + /// Adds a function or a (sub)module to a module, using the functions __name__ as name. + /// + /// Use this together with the`#[pyfunction]` and [wrap_pyfunction!] or `#[pymodule]` and + /// [wrap_pymodule!]. + /// + /// ```rust + /// use pyo3::prelude::*; + /// #[pymodule] + /// fn utils(_py: Python, _module: &PyModule) -> PyResult<()> { + /// Ok(()) + /// } + /// + /// #[pyfunction] + /// fn double(x: usize) -> usize { + /// x * 2 + /// } + /// #[pymodule] + /// fn top_level(_py: Python, module: &PyModule) -> PyResult<()> { + /// module.add_wrapped(pyo3::wrap_pymodule!(utils))?; + /// module.add_wrapped(pyo3::wrap_pyfunction!(double)) + /// } + /// ``` + /// + /// You can also add a function with a custom name using [add](PyModule::add): + /// + /// ```rust,ignore + /// m.add("also_double", wrap_pyfunction!(double)(m)?)?; + /// ``` + /// + /// **This function will be deprecated in the next release. Please use the specific + /// [add_function] and [add_submodule] functions instead.** + pub fn add_wrapped<'a, T>(&'a self, wrapper: &impl Fn(Python<'a>) -> T) -> PyResult<()> + where + T: IntoPyCallbackOutput, + { + let py = self.py(); + let function = wrapper(py).convert(py)?; + let name = function.getattr(py, "__name__")?; + let name = name.extract(py)?; + self.add(name, function) + } + + /// Add a submodule to a module. + /// + /// Use this together with `#[pymodule]` and [wrap_pymodule!]. + /// + /// ```rust + /// use pyo3::prelude::*; + /// + /// fn init_utils(module: &PyModule) -> PyResult<()> { + /// module.add("super_useful_constant", "important") + /// } + /// #[pymodule] + /// fn top_level(py: Python, module: &PyModule) -> PyResult<()> { + /// let utils = PyModule::new(py, "utils")?; + /// init_utils(utils)?; + /// module.add_submodule(utils) + /// } + /// ``` + pub fn add_submodule(&self, module: &PyModule) -> PyResult<()> { + let name = module.name()?; + self.add(name, module) + } + + /// Add a function to a module. + /// + /// Use this together with the`#[pyfunction]` and [wrap_pyfunction!]. + /// + /// ```rust + /// use pyo3::prelude::*; + /// #[pyfunction] + /// fn double(x: usize) -> usize { + /// x * 2 + /// } + /// #[pymodule] + /// fn double_mod(_py: Python, module: &PyModule) -> PyResult<()> { + /// module.add_function(pyo3::wrap_pyfunction!(double, module)?) + /// } + /// ``` + /// + /// You can also add a function with a custom name using [add](PyModule::add): + /// + /// ```rust + /// use pyo3::prelude::*; + /// #[pyfunction] + /// fn double(x: usize) -> usize { + /// x * 2 + /// } + /// #[pymodule] + /// fn double_mod(_py: Python, module: &PyModule) -> PyResult<()> { + /// module.add("also_double", pyo3::wrap_pyfunction!(double, module)?) + /// } + /// ``` + pub fn add_function<'a>(&'a self, fun: &'a PyCFunction) -> PyResult<()> { + let attr_name = fun.getattr("__name__")?; + self.add(attr_name.extract()?, fun) + } +} diff --git a/src/experimental/objects/num.rs b/src/experimental/objects/num.rs new file mode 100644 index 00000000000..d89b29c144d --- /dev/null +++ b/src/experimental/objects/num.rs @@ -0,0 +1,618 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors +// +// based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython + +use crate::{ + exceptions, ffi, + objects::{FromPyObject, PyAny, PyNativeObject}, + types::Int, + AsPyPointer, PyErr, PyResult, Python, +}; +use std::convert::TryFrom; +use std::i64; + +fn err_if_invalid_value( + py: Python, + invalid_value: T, + actual_value: T, +) -> PyResult { + if actual_value == invalid_value && PyErr::occurred(py) { + Err(PyErr::fetch(py)) + } else { + Ok(actual_value) + } +} + +macro_rules! int_fits_larger_int { + ($rust_type:ty, $larger_type:ty) => { + // impl ToPyObject for $rust_type { + // #[inline] + // fn to_object(&self, py: Python) -> PyObject { + // (*self as $larger_type).into_py(py) + // } + // } + // impl IntoPy for $rust_type { + // fn into_py(self, py: Python) -> PyObject { + // (self as $larger_type).into_py(py) + // } + // } + + impl FromPyObject<'_, '_> for $rust_type { + fn extract(obj: &PyAny) -> PyResult { + let val: $larger_type = obj.extract()?; + <$rust_type>::try_from(val) + .map_err(|e| exceptions::PyOverflowError::new_err(e.to_string())) + } + } + }; +} + +/// Represents a Python `int` object. +/// +/// You can usually avoid directly working with this type +/// by using [`ToPyObject`](trait.ToPyObject.html) +/// and [extract](struct.PyAny.html#method.extract) +/// with the primitive Rust integer types. +#[repr(transparent)] +pub struct PyInt<'py>(pub(crate) PyAny<'py>); + +pyo3_native_object!(PyInt<'py>, Int, 'py); + +macro_rules! int_fits_c_long { + ($rust_type:ty) => { + // impl ToPyObject for $rust_type { + // #![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_lossless))] + // fn to_object(&self, py: Python) -> PyObject { + // unsafe { PyObject::from_owned_ptr(py, ffi::PyLong_FromLong(*self as c_long)) } + // } + // } + // impl IntoPy for $rust_type { + // #![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_lossless))] + // fn into_py(self, py: Python) -> PyObject { + // unsafe { PyObject::from_owned_ptr(py, ffi::PyLong_FromLong(self as c_long)) } + // } + // } + + impl FromPyObject<'_, '_> for $rust_type { + fn extract(obj: &PyAny) -> PyResult { + let ptr = obj.as_ptr(); + let val = unsafe { + let num = ffi::PyNumber_Index(ptr); + if num.is_null() { + Err(PyErr::fetch(obj.py())) + } else { + let val = err_if_invalid_value(obj.py(), -1, ffi::PyLong_AsLong(num)); + ffi::Py_DECREF(num); + val + } + }?; + <$rust_type>::try_from(val) + .map_err(|e| exceptions::PyOverflowError::new_err(e.to_string())) + } + } + }; +} + +macro_rules! int_convert_u64_or_i64 { + ($rust_type:ty, $pylong_from_ll_or_ull:expr, $pylong_as_ll_or_ull:expr) => { + // impl ToPyObject for $rust_type { + // #[inline] + // fn to_object(&self, py: Python) -> PyObject { + // unsafe { PyObject::from_owned_ptr(py, $pylong_from_ll_or_ull(*self)) } + // } + // } + // impl IntoPy for $rust_type { + // #[inline] + // fn into_py(self, py: Python) -> PyObject { + // unsafe { PyObject::from_owned_ptr(py, $pylong_from_ll_or_ull(self)) } + // } + // } + impl FromPyObject<'_, '_> for $rust_type { + fn extract(ob: &PyAny) -> PyResult<$rust_type> { + let ptr = ob.as_ptr(); + unsafe { + let num = ffi::PyNumber_Index(ptr); + if num.is_null() { + Err(PyErr::fetch(ob.py())) + } else { + let result = err_if_invalid_value(ob.py(), !0, $pylong_as_ll_or_ull(num)); + ffi::Py_DECREF(num); + result + } + } + } + } + }; +} + +int_fits_c_long!(i8); +int_fits_c_long!(u8); +int_fits_c_long!(i16); +int_fits_c_long!(u16); +int_fits_c_long!(i32); + +// If c_long is 64-bits, we can use more types with int_fits_c_long!: +#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))] +int_fits_c_long!(u32); +#[cfg(any(target_pointer_width = "32", target_os = "windows"))] +int_fits_larger_int!(u32, u64); + +#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))] +int_fits_c_long!(i64); + +// manual implementation for i64 on systems with 32-bit long +#[cfg(any(target_pointer_width = "32", target_os = "windows"))] +int_convert_u64_or_i64!(i64, ffi::PyLong_FromLongLong, ffi::PyLong_AsLongLong); + +#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))] +int_fits_c_long!(isize); +#[cfg(any(target_pointer_width = "32", target_os = "windows"))] +int_fits_larger_int!(isize, i64); + +int_fits_larger_int!(usize, u64); + +// u64 has a manual implementation as it never fits into signed long +int_convert_u64_or_i64!( + u64, + ffi::PyLong_FromUnsignedLongLong, + ffi::PyLong_AsUnsignedLongLong +); + +// manual implementation for 128bit integers +#[cfg(not(any(Py_LIMITED_API, PyPy)))] +mod int128_conversion { + use crate::{ + ffi, + objects::{FromPyObject, PyAny, PyNativeObject}, + AsPyPointer, PyErr, PyResult, + }; + use std::os::raw::c_int; + + #[cfg(target_endian = "little")] + const IS_LITTLE_ENDIAN: c_int = 1; + #[cfg(not(target_endian = "little"))] + const IS_LITTLE_ENDIAN: c_int = 0; + + // for 128bit Integers + macro_rules! int_convert_128 { + ($rust_type: ty, $byte_size: expr, $is_signed: expr) => { + // impl ToPyObject for $rust_type { + // #[inline] + // fn to_object(&self, py: Python) -> PyObject { + // (*self).into_py(py) + // } + // } + // impl IntoPy for $rust_type { + // fn into_py(self, py: Python) -> PyObject { + // unsafe { + // let bytes = self.to_ne_bytes(); + // let obj = ffi::_PyLong_FromByteArray( + // bytes.as_ptr() as *const c_uchar, + // $byte_size, + // IS_LITTLE_ENDIAN, + // $is_signed, + // ); + // PyObject::from_owned_ptr(py, obj) + // } + // } + // } + + impl FromPyObject<'_, '_> for $rust_type { + fn extract(ob: &PyAny) -> PyResult<$rust_type> { + unsafe { + let num = ffi::PyNumber_Index(ob.as_ptr()); + if num.is_null() { + return Err(PyErr::fetch(ob.py())); + } + let mut buffer = [0; $byte_size]; + let ok = ffi::_PyLong_AsByteArray( + num as *mut ffi::PyLongObject, + buffer.as_mut_ptr(), + $byte_size, + IS_LITTLE_ENDIAN, + $is_signed, + ); + if ok == -1 { + Err(PyErr::fetch(ob.py())) + } else { + Ok(<$rust_type>::from_ne_bytes(buffer)) + } + } + } + } + }; + } + + int_convert_128!(i128, 16, 1); + int_convert_128!(u128, 16, 0); + + #[cfg(test)] + mod test { + use crate::{Python, ToPyObject}; + + #[test] + fn test_i128_max() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = std::i128::MAX; + let obj = v.to_object(py); + assert_eq!(v, obj.extract::(py).unwrap()); + assert_eq!(v as u128, obj.extract::(py).unwrap()); + assert!(obj.extract::(py).is_err()); + } + + #[test] + fn test_i128_min() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = std::i128::MIN; + let obj = v.to_object(py); + assert_eq!(v, obj.extract::(py).unwrap()); + assert!(obj.extract::(py).is_err()); + assert!(obj.extract::(py).is_err()); + } + + #[test] + fn test_u128_max() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = std::u128::MAX; + let obj = v.to_object(py); + assert_eq!(v, obj.extract::(py).unwrap()); + assert!(obj.extract::(py).is_err()); + } + + #[test] + fn test_u128_overflow() { + use crate::{exceptions, ffi, PyObject}; + use std::os::raw::c_uchar; + let gil = Python::acquire_gil(); + let py = gil.python(); + let overflow_bytes: [c_uchar; 20] = [255; 20]; + unsafe { + let obj = ffi::_PyLong_FromByteArray( + overflow_bytes.as_ptr() as *const c_uchar, + 20, + super::IS_LITTLE_ENDIAN, + 0, + ); + let obj = PyObject::from_owned_ptr(py, obj); + let err = obj.extract::(py).unwrap_err(); + assert!(err.is_instance::(py)); + } + } + } +} + +#[cfg(all(feature = "num-bigint", not(any(Py_LIMITED_API, PyPy))))] +mod bigint_conversion { + use super::*; + use crate::{err, Py}; + use num_bigint::{BigInt, BigUint}; + use std::os::raw::{c_int, c_uchar}; + + #[cfg(not(all(windows, PyPy)))] + unsafe fn extract(ob: &PyLong, buffer: &mut [c_uchar], is_signed: c_int) -> PyResult<()> { + err::error_on_minusone( + ob.py(), + ffi::_PyLong_AsByteArray( + ob.as_ptr() as *mut ffi::PyLongObject, + buffer.as_mut_ptr(), + buffer.len(), + 1, + is_signed, + ), + ) + } + + macro_rules! bigint_conversion { + ($rust_ty: ty, $is_signed: expr, $to_bytes: path, $from_bytes: path) => { + impl ToPyObject for $rust_ty { + fn to_object(&self, py: Python) -> PyObject { + unsafe { + let bytes = $to_bytes(self); + let obj = ffi::_PyLong_FromByteArray( + bytes.as_ptr() as *const c_uchar, + bytes.len(), + 1, + $is_signed, + ); + PyObject::from_owned_ptr(py, obj) + } + } + } + impl IntoPy for $rust_ty { + fn into_py(self, py: Python) -> PyObject { + self.to_object(py) + } + } + impl<'source> FromPyObject<'source> for $rust_ty { + fn extract(ob: &'source PyAny) -> PyResult<$rust_ty> { + let py = ob.py(); + unsafe { + let num = ffi::PyNumber_Index(ob.as_ptr()); + if num.is_null() { + return Err(PyErr::fetch(py)); + } + let n_bits = ffi::_PyLong_NumBits(num); + let n_bytes = if n_bits < 0 { + return Err(PyErr::fetch(py)); + } else if n_bits == 0 { + 0 + } else { + (n_bits as usize - 1 + $is_signed) / 8 + 1 + }; + let num: Py = Py::from_owned_ptr(py, num); + if n_bytes <= 128 { + let mut buffer = [0; 128]; + extract(num.as_ref(py), &mut buffer[..n_bytes], $is_signed)?; + Ok($from_bytes(&buffer[..n_bytes])) + } else { + let mut buffer = vec![0; n_bytes]; + extract(num.as_ref(py), &mut buffer, $is_signed)?; + Ok($from_bytes(&buffer)) + } + } + } + } + }; + } + bigint_conversion!(BigUint, 0, BigUint::to_bytes_le, BigUint::from_bytes_le); + bigint_conversion!( + BigInt, + 1, + BigInt::to_signed_bytes_le, + BigInt::from_signed_bytes_le + ); + + #[cfg(test)] + mod test { + use super::*; + use crate::types::{PyDict, PyModule}; + use indoc::indoc; + + fn python_fib(py: Python) -> &PyModule { + let fib_code = indoc!( + r#" + def fib(n): + f0, f1 = 0, 1 + for _ in range(n): + f0, f1 = f1, f0 + f1 + return f0 + + def fib_neg(n): + return -fib(n) + "# + ); + PyModule::from_code(py, fib_code, "fib.py", "fib").unwrap() + } + + fn rust_fib(n: usize) -> T + where + T: From, + for<'a> &'a T: std::ops::Add, + { + let mut f0: T = T::from(0); + let mut f1: T = T::from(1); + for _ in 0..n { + let f2 = &f0 + &f1; + f0 = std::mem::replace(&mut f1, f2); + } + f0 + } + + #[test] + fn convert_biguint() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let rs_result: BigUint = rust_fib(400); + let fib = python_fib(py); + let locals = PyDict::new(py); + locals.set_item("rs_result", &rs_result).unwrap(); + locals.set_item("fib", fib).unwrap(); + // Checks if Rust BigUint -> Python Long conversion is correct + py.run("assert fib.fib(400) == rs_result", None, Some(locals)) + .unwrap(); + // Checks if Python Long -> Rust BigUint conversion is correct if N is small + let py_result: BigUint = + FromPyObject::extract(fib.call1("fib", (400,)).unwrap()).unwrap(); + assert_eq!(rs_result, py_result); + // Checks if Python Long -> Rust BigUint conversion is correct if N is large + let rs_result: BigUint = rust_fib(2000); + let py_result: BigUint = + FromPyObject::extract(fib.call1("fib", (2000,)).unwrap()).unwrap(); + assert_eq!(rs_result, py_result); + } + + #[test] + fn convert_bigint() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let rs_result = rust_fib::(400) * -1; + let fib = python_fib(py); + let locals = PyDict::new(py); + locals.set_item("rs_result", &rs_result).unwrap(); + locals.set_item("fib", fib).unwrap(); + // Checks if Rust BigInt -> Python Long conversion is correct + py.run("assert fib.fib_neg(400) == rs_result", None, Some(locals)) + .unwrap(); + // Checks if Python Long -> Rust BigInt conversion is correct if N is small + let py_result: BigInt = + FromPyObject::extract(fib.call1("fib_neg", (400,)).unwrap()).unwrap(); + assert_eq!(rs_result, py_result); + // Checks if Python Long -> Rust BigInt conversion is correct if N is large + let rs_result = rust_fib::(2000) * -1; + let py_result: BigInt = + FromPyObject::extract(fib.call1("fib_neg", (2000,)).unwrap()).unwrap(); + assert_eq!(rs_result, py_result); + } + + fn python_index_class(py: Python) -> &PyModule { + let index_code = indoc!( + r#" + class C: + def __init__(self, x): + self.x = x + def __index__(self): + return self.x + "# + ); + PyModule::from_code(py, index_code, "index.py", "index").unwrap() + } + + #[test] + fn convert_index_class() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let index = python_index_class(py); + let locals = PyDict::new(py); + locals.set_item("index", index).unwrap(); + let ob = py.eval("index.C(10)", None, Some(locals)).unwrap(); + let _: BigInt = FromPyObject::extract(ob).unwrap(); + } + + #[test] + fn handle_zero() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let fib = python_fib(py); + let zero: BigInt = FromPyObject::extract(fib.call1("fib", (0,)).unwrap()).unwrap(); + assert_eq!(zero, BigInt::from(0)); + } + + /// `OverflowError` on converting Python int to BigInt, see issue #629 + #[test] + fn check_overflow() { + let gil = Python::acquire_gil(); + let py = gil.python(); + macro_rules! test { + ($T:ty, $value:expr, $py:expr) => { + let value = $value; + println!("{}: {}", stringify!($T), value); + let python_value = value.clone().to_object(py); + let roundtrip_value = python_value.extract::<$T>(py).unwrap(); + assert_eq!(value, roundtrip_value); + }; + } + for i in 0..=256usize { + // test a lot of values to help catch other bugs too + test!(BigInt, BigInt::from(i), py); + test!(BigUint, BigUint::from(i), py); + test!(BigInt, -BigInt::from(i), py); + test!(BigInt, BigInt::from(1) << i, py); + test!(BigUint, BigUint::from(1u32) << i, py); + test!(BigInt, -BigInt::from(1) << i, py); + test!(BigInt, (BigInt::from(1) << i) + 1u32, py); + test!(BigUint, (BigUint::from(1u32) << i) + 1u32, py); + test!(BigInt, (-BigInt::from(1) << i) + 1u32, py); + test!(BigInt, (BigInt::from(1) << i) - 1u32, py); + test!(BigUint, (BigUint::from(1u32) << i) - 1u32, py); + test!(BigInt, (-BigInt::from(1) << i) - 1u32, py); + } + } + } +} + +#[cfg(test)] +mod test { + use crate::Python; + use crate::ToPyObject; + + #[test] + fn test_u32_max() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = std::u32::MAX; + let obj = v.to_object(py); + assert_eq!(v, obj.extract::(py).unwrap()); + assert_eq!(u64::from(v), obj.extract::(py).unwrap()); + assert!(obj.extract::(py).is_err()); + } + + #[test] + fn test_i64_max() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = std::i64::MAX; + let obj = v.to_object(py); + assert_eq!(v, obj.extract::(py).unwrap()); + assert_eq!(v as u64, obj.extract::(py).unwrap()); + assert!(obj.extract::(py).is_err()); + } + + #[test] + fn test_i64_min() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = std::i64::MIN; + let obj = v.to_object(py); + assert_eq!(v, obj.extract::(py).unwrap()); + assert!(obj.extract::(py).is_err()); + assert!(obj.extract::(py).is_err()); + } + + #[test] + fn test_u64_max() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = std::u64::MAX; + let obj = v.to_object(py); + assert_eq!(v, obj.extract::(py).unwrap()); + assert!(obj.extract::(py).is_err()); + } + + macro_rules! test_common ( + ($test_mod_name:ident, $t:ty) => ( + mod $test_mod_name { + use crate::exceptions; + use crate::ToPyObject; + use crate::Python; + + #[test] + fn from_py_string_type_error() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let obj = ("123").to_object(py); + let err = obj.extract::<$t>(py).unwrap_err(); + assert!(err.is_instance::(py)); + } + + #[test] + fn from_py_float_type_error() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let obj = (12.3).to_object(py); + let err = obj.extract::<$t>(py).unwrap_err(); + assert!(err.is_instance::(py)); + } + + #[test] + fn to_py_object_and_back() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let val = 123 as $t; + let obj = val.to_object(py); + assert_eq!(obj.extract::<$t>(py).unwrap(), val as $t); + } + } + ) + ); + + test_common!(i8, i8); + test_common!(u8, u8); + test_common!(i16, i16); + test_common!(u16, u16); + test_common!(i32, i32); + test_common!(u32, u32); + test_common!(i64, i64); + test_common!(u64, u64); + test_common!(isize, isize); + test_common!(usize, usize); + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + test_common!(i128, i128); + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + test_common!(u128, u128); +} diff --git a/src/experimental/objects/sequence.rs b/src/experimental/objects/sequence.rs new file mode 100644 index 00000000000..19a6550cd3c --- /dev/null +++ b/src/experimental/objects/sequence.rs @@ -0,0 +1,756 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors + +use crate::err::{self, PyDowncastError, PyErr, PyResult}; +use crate::exceptions; +use crate::ffi::{self, Py_ssize_t}; +use crate::types::Sequence; +use crate::AsPyPointer; +use crate::{ + objects::{FromPyObject, PyAny, PyList, PyNativeObject, PyTryFrom, PyTuple}, + ToBorrowedObject, +}; + +/// Represents a reference to a Python object supporting the sequence protocol. +#[repr(transparent)] +pub struct PySequence<'py>(pub(crate) PyAny<'py>); +pyo3_native_object!(PySequence<'py>, Sequence, 'py); + +impl<'py> PySequence<'py> { + /// Returns the number of objects in sequence. + /// + /// This is equivalent to the Python expression `len(self)`. + #[inline] + pub fn len(&self) -> PyResult { + let v = unsafe { ffi::PySequence_Size(self.as_ptr()) }; + if v == -1 { + Err(PyErr::fetch(self.py())) + } else { + Ok(v as isize) + } + } + + #[inline] + pub fn is_empty(&self) -> PyResult { + self.len().map(|l| l == 0) + } + + /// Returns the concatenation of `self` and `other`. + /// + /// This is equivalent to the Python expression `self + other`. + #[inline] + pub fn concat(&self, other: &PySequence) -> PyResult { + unsafe { + PyAny::from_raw_or_fetch_err( + self.py(), + ffi::PySequence_Concat(self.as_ptr(), other.as_ptr()), + ) + .map(Self) + } + } + + /// Returns the result of repeating a sequence object `count` times. + /// + /// This is equivalent to the Python expression `self * count`. + /// NB: Python accepts negative counts; it returns an empty Sequence. + #[inline] + pub fn repeat(&self, count: isize) -> PyResult { + unsafe { + PyAny::from_raw_or_fetch_err( + self.py(), + ffi::PySequence_Repeat(self.as_ptr(), count as Py_ssize_t), + ) + .map(Self) + } + } + + /// Concatenates `self` and `other` in place. + /// + /// This is equivalent to the Python statement `self += other`. + #[inline] + pub fn in_place_concat(&self, other: &PySequence) -> PyResult<()> { + unsafe { + let ptr = ffi::PySequence_InPlaceConcat(self.as_ptr(), other.as_ptr()); + if ptr.is_null() { + Err(PyErr::fetch(self.py())) + } else { + Ok(()) + } + } + } + + /// Repeats the sequence object `count` times and updates `self`. + /// + /// This is equivalent to the Python statement `self *= count`. + /// NB: Python accepts negative counts; it empties the Sequence. + #[inline] + pub fn in_place_repeat(&self, count: isize) -> PyResult<()> { + unsafe { + let ptr = ffi::PySequence_InPlaceRepeat(self.as_ptr(), count as Py_ssize_t); + if ptr.is_null() { + Err(PyErr::fetch(self.py())) + } else { + Ok(()) + } + } + } + + /// Returns the `index`th element of the Sequence. + /// + /// This is equivalent to the Python expression `self[index]`. + #[inline] + pub fn get_item(&self, index: isize) -> PyResult> { + unsafe { + PyAny::from_raw_or_fetch_err( + self.py(), + ffi::PySequence_GetItem(self.as_ptr(), index as Py_ssize_t), + ) + } + } + + /// Returns the slice of sequence object between `begin` and `end`. + /// + /// This is equivalent to the Python expression `self[begin:end]`. + #[inline] + pub fn get_slice(&self, begin: isize, end: isize) -> PyResult> { + unsafe { + PyAny::from_raw_or_fetch_err( + self.py(), + ffi::PySequence_GetSlice(self.as_ptr(), begin as Py_ssize_t, end as Py_ssize_t), + ) + } + } + + /// Assigns object `item` to the `i`th element of self. + /// + /// This is equivalent to the Python statement `self[i] = v`. + #[inline] + pub fn set_item(&self, i: isize, item: I) -> PyResult<()> + where + I: ToBorrowedObject, + { + unsafe { + item.with_borrowed_ptr(self.py(), |item| { + err::error_on_minusone( + self.py(), + ffi::PySequence_SetItem(self.as_ptr(), i as Py_ssize_t, item), + ) + }) + } + } + + /// Deletes the `i`th element of self. + /// + /// This is equivalent to the Python statement `del self[i]`. + #[inline] + pub fn del_item(&self, i: isize) -> PyResult<()> { + unsafe { + err::error_on_minusone( + self.py(), + ffi::PySequence_DelItem(self.as_ptr(), i as Py_ssize_t), + ) + } + } + + /// Assigns the sequence `v` to the slice of `self` from `i1` to `i2`. + /// + /// This is equivalent to the Python statement `self[i1:i2] = v`. + #[inline] + pub fn set_slice(&self, i1: isize, i2: isize, v: &PyAny) -> PyResult<()> { + unsafe { + err::error_on_minusone( + self.py(), + ffi::PySequence_SetSlice( + self.as_ptr(), + i1 as Py_ssize_t, + i2 as Py_ssize_t, + v.as_ptr(), + ), + ) + } + } + + /// Deletes the slice from `i1` to `i2` from `self`. + /// + /// This is equivalent to the Python statement `del self[i1:i2]`. + #[inline] + pub fn del_slice(&self, i1: isize, i2: isize) -> PyResult<()> { + unsafe { + err::error_on_minusone( + self.py(), + ffi::PySequence_DelSlice(self.as_ptr(), i1 as Py_ssize_t, i2 as Py_ssize_t), + ) + } + } + + /// Returns the number of occurrences of `value` in self, that is, return the + /// number of keys for which `self[key] == value`. + #[inline] + #[cfg(not(PyPy))] + pub fn count(&self, value: V) -> PyResult + where + V: ToBorrowedObject, + { + let r = value.with_borrowed_ptr(self.py(), |ptr| unsafe { + ffi::PySequence_Count(self.as_ptr(), ptr) + }); + if r == -1 { + Err(PyErr::fetch(self.py())) + } else { + Ok(r as usize) + } + } + + /// Determines if self contains `value`. + /// + /// This is equivalent to the Python expression `value in self`. + #[inline] + pub fn contains(&self, value: V) -> PyResult + where + V: ToBorrowedObject, + { + let r = value.with_borrowed_ptr(self.py(), |ptr| unsafe { + ffi::PySequence_Contains(self.as_ptr(), ptr) + }); + match r { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(PyErr::fetch(self.py())), + } + } + + /// Returns the first index `i` for which `self[i] == value`. + /// + /// This is equivalent to the Python expression `self.index(value)`. + #[inline] + pub fn index(&self, value: V) -> PyResult + where + V: ToBorrowedObject, + { + let r = value.with_borrowed_ptr(self.py(), |ptr| unsafe { + ffi::PySequence_Index(self.as_ptr(), ptr) + }); + if r == -1 { + Err(PyErr::fetch(self.py())) + } else { + Ok(r as usize) + } + } + + /// Returns a fresh list based on the Sequence. + #[inline] + pub fn list(&self) -> PyResult> { + unsafe { + PyAny::from_raw_or_fetch_err(self.py(), ffi::PySequence_List(self.as_ptr())).map(PyList) + } + } + + /// Returns a fresh tuple based on the Sequence. + #[inline] + pub fn tuple(&self) -> PyResult> { + unsafe { + PyAny::from_raw_or_fetch_err(self.py(), ffi::PySequence_Tuple(self.as_ptr())) + .map(PyTuple) + } + } +} + +macro_rules! array_impls { + ($($N:expr),+) => { + $( + impl<'py, T> FromPyObject<'_, 'py> for [T; $N] + where + T: Copy + Default + for<'a> FromPyObject<'a, 'py>, + { + #[cfg(not(feature = "nightly"))] + fn extract(obj: &PyAny<'py>) -> PyResult { + let mut array = [T::default(); $N]; + extract_sequence_into_slice(obj, &mut array)?; + Ok(array) + } + + #[cfg(feature = "nightly")] + default fn extract(obj: &'a PyAny) -> PyResult { + let mut array = [T::default(); $N]; + extract_sequence_into_slice(obj, &mut array)?; + Ok(array) + } + } + + #[cfg(feature = "nightly")] + impl<'source, T> FromPyObject<'source> for [T; $N] + where + for<'a> T: Default + FromPyObject<'a> + crate::buffer::Element, + { + fn extract(obj: &'source PyAny) -> PyResult { + let mut array = [T::default(); $N]; + // first try buffer protocol + if let Ok(buf) = crate::buffer::PyBuffer::get(obj) { + if buf.dimensions() == 1 && buf.copy_to_slice(obj.py(), &mut array).is_ok() { + buf.release(obj.py()); + return Ok(array); + } + buf.release(obj.py()); + } + // fall back to sequence protocol + extract_sequence_into_slice(obj, &mut array)?; + Ok(array) + } + } + )+ + } +} + +array_impls!( + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32 +); + +impl<'py, T> FromPyObject<'_, 'py> for Vec +where + T: for<'a> FromPyObject<'a, 'py>, +{ + #[cfg(not(feature = "nightly"))] + fn extract(obj: &PyAny<'py>) -> PyResult { + extract_sequence(obj) + } + #[cfg(feature = "nightly")] + default fn extract(obj: &PyAny<'py>) -> PyResult { + extract_sequence(obj) + } +} + +#[cfg(feature = "nightly")] +impl<'py, T> FromPyObject<'_, 'py> for Vec +where + for<'a> T: FromPyObject<'a> + crate::buffer::Element, +{ + fn extract(obj: &PyAny) -> PyResult { + // first try buffer protocol + if let Ok(buf) = crate::buffer::PyBuffer::get(obj) { + if buf.dimensions() == 1 { + if let Ok(v) = buf.to_vec(obj.py()) { + buf.release(obj.py()); + return Ok(v); + } + } + buf.release(obj.py()); + } + // fall back to sequence protocol + extract_sequence(obj) + } +} + +fn extract_sequence<'py, T>(obj: &PyAny<'py>) -> PyResult> +where + T: for<'a> FromPyObject<'a, 'py>, +{ + let seq = ::try_from(obj)?; + let mut v = Vec::with_capacity(seq.len().unwrap_or(0) as usize); + for item in seq.iter()? { + v.push(item?.extract::()?); + } + Ok(v) +} + +fn extract_sequence_into_slice<'py, T>(obj: &PyAny<'py>, slice: &mut [T]) -> PyResult<()> +where + T: for<'a> FromPyObject<'a, 'py>, +{ + let seq = ::try_from(obj)?; + if seq.len()? as usize != slice.len() { + return Err(exceptions::PyBufferError::new_err( + "Slice length does not match buffer length.", + )); + } + for (value, item) in slice.iter_mut().zip(seq.iter()?) { + *value = item?.extract::()?; + } + Ok(()) +} + +impl<'a, 'py> PyTryFrom<'a, 'py> for PySequence<'py> { + fn try_from(value: &'a PyAny<'py>) -> Result<&'a PySequence<'py>, PyDowncastError<'py>> { + unsafe { + if ffi::PySequence_Check(value.as_ptr()) != 0 { + Ok(::try_from_unchecked(value)) + } else { + Err(PyDowncastError::new(value.into_ty_ref(), "Sequence")) + } + } + } + + fn try_from_exact(value: &'a PyAny<'py>) -> Result<&'a PySequence<'py>, PyDowncastError<'py>> { + ::try_from(value) + } + + #[inline] + unsafe fn try_from_unchecked(value: &'a PyAny<'py>) -> &'a PySequence<'py> { + let ptr = value as *const _ as *const PySequence; + &*ptr + } +} + +#[cfg(test)] +mod test { + use crate::objects::{PyAny, PySequence, PyTryFrom}; + use crate::AsPyPointer; + use crate::Python; + use crate::{PyObject, ToPyObject}; + + fn get_object() -> PyObject { + // Convenience function for getting a single unique object + let gil = Python::acquire_gil(); + let py = gil.python(); + + let obj = py.eval("object()", None, None).unwrap(); + + obj.to_object(py) + } + + #[test] + fn test_numbers_are_not_sequences() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = 42i32; + assert!(::try_from(v.to_object(py).as_object(py)).is_err()); + } + + #[test] + fn test_strings_are_sequences() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = "London Calling"; + assert!(::try_from(v.to_object(py).as_object(py)).is_ok()); + } + #[test] + fn test_seq_empty() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v: Vec = vec![]; + let ob = v.to_object(py); + let seq = ob.as_object::(py).downcast::().unwrap(); + assert_eq!(0, seq.len().unwrap()); + + let needle = 7i32.to_object(py); + assert_eq!(false, seq.contains(&needle).unwrap()); + } + + #[test] + fn test_seq_contains() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v: Vec = vec![1, 1, 2, 3, 5, 8]; + let ob = v.to_object(py); + let seq = ob.as_object::(py).downcast::().unwrap(); + assert_eq!(6, seq.len().unwrap()); + + let bad_needle = 7i32.to_object(py); + assert_eq!(false, seq.contains(&bad_needle).unwrap()); + + let good_needle = 8i32.to_object(py); + assert_eq!(true, seq.contains(&good_needle).unwrap()); + + let type_coerced_needle = 8f32.to_object(py); + assert_eq!(true, seq.contains(&type_coerced_needle).unwrap()); + } + + #[test] + fn test_seq_get_item() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v: Vec = vec![1, 1, 2, 3, 5, 8]; + let ob = v.to_object(py); + let seq = ob.as_object::(py).downcast::().unwrap(); + assert_eq!(1, seq.get_item(0).unwrap().extract::().unwrap()); + assert_eq!(1, seq.get_item(1).unwrap().extract::().unwrap()); + assert_eq!(2, seq.get_item(2).unwrap().extract::().unwrap()); + assert_eq!(3, seq.get_item(3).unwrap().extract::().unwrap()); + assert_eq!(5, seq.get_item(4).unwrap().extract::().unwrap()); + assert_eq!(8, seq.get_item(5).unwrap().extract::().unwrap()); + assert_eq!(8, seq.get_item(-1).unwrap().extract::().unwrap()); + assert_eq!(5, seq.get_item(-2).unwrap().extract::().unwrap()); + assert_eq!(3, seq.get_item(-3).unwrap().extract::().unwrap()); + assert_eq!(2, seq.get_item(-4).unwrap().extract::().unwrap()); + assert_eq!(1, seq.get_item(-5).unwrap().extract::().unwrap()); + assert!(seq.get_item(10).is_err()); + } + + // fn test_get_slice() {} + // fn test_set_slice() {} + // fn test_del_slice() {} + + #[test] + fn test_seq_del_item() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v: Vec = vec![1, 1, 2, 3, 5, 8]; + let ob = v.to_object(py); + let seq = ob.as_object::(py).downcast::().unwrap(); + assert!(seq.del_item(10).is_err()); + assert_eq!(1, seq.get_item(0).unwrap().extract::().unwrap()); + assert!(seq.del_item(0).is_ok()); + assert_eq!(1, seq.get_item(0).unwrap().extract::().unwrap()); + assert!(seq.del_item(0).is_ok()); + assert_eq!(2, seq.get_item(0).unwrap().extract::().unwrap()); + assert!(seq.del_item(0).is_ok()); + assert_eq!(3, seq.get_item(0).unwrap().extract::().unwrap()); + assert!(seq.del_item(0).is_ok()); + assert_eq!(5, seq.get_item(0).unwrap().extract::().unwrap()); + assert!(seq.del_item(0).is_ok()); + assert_eq!(8, seq.get_item(0).unwrap().extract::().unwrap()); + assert!(seq.del_item(0).is_ok()); + assert_eq!(0, seq.len().unwrap()); + assert!(seq.del_item(0).is_err()); + } + + #[test] + fn test_seq_set_item() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v: Vec = vec![1, 2]; + let ob = v.to_object(py); + let seq = ob.as_object::(py).downcast::().unwrap(); + assert_eq!(2, seq.get_item(1).unwrap().extract::().unwrap()); + assert!(seq.set_item(1, 10).is_ok()); + assert_eq!(10, seq.get_item(1).unwrap().extract::().unwrap()); + } + + #[test] + fn test_seq_set_item_refcnt() { + let obj = get_object(); + { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v: Vec = vec![1, 2]; + let ob = v.to_object(py); + let seq = ob.as_object::(py).downcast::().unwrap(); + assert!(seq.set_item(1, &obj).is_ok()); + assert!(seq.get_item(1).unwrap().as_ptr() == obj.as_ptr()); + } + { + let gil = Python::acquire_gil(); + let py = gil.python(); + assert_eq!(1, obj.get_refcnt(py)); + } + } + + #[test] + fn test_seq_index() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v: Vec = vec![1, 1, 2, 3, 5, 8]; + let ob = v.to_object(py); + let seq = ob.as_object::(py).downcast::().unwrap(); + assert_eq!(0, seq.index(1i32).unwrap()); + assert_eq!(2, seq.index(2i32).unwrap()); + assert_eq!(3, seq.index(3i32).unwrap()); + assert_eq!(4, seq.index(5i32).unwrap()); + assert_eq!(5, seq.index(8i32).unwrap()); + assert!(seq.index(42i32).is_err()); + } + + #[test] + #[cfg(not(PyPy))] + fn test_seq_count() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v: Vec = vec![1, 1, 2, 3, 5, 8]; + let ob = v.to_object(py); + let seq = ob.as_object::(py).downcast::().unwrap(); + assert_eq!(2, seq.count(1i32).unwrap()); + assert_eq!(1, seq.count(2i32).unwrap()); + assert_eq!(1, seq.count(3i32).unwrap()); + assert_eq!(1, seq.count(5i32).unwrap()); + assert_eq!(1, seq.count(8i32).unwrap()); + assert_eq!(0, seq.count(42i32).unwrap()); + } + + #[test] + fn test_seq_iter() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v: Vec = vec![1, 1, 2, 3, 5, 8]; + let ob = v.to_object(py); + let seq = ob.as_object::(py).downcast::().unwrap(); + let mut idx = 0; + for el in seq.iter().unwrap() { + assert_eq!(v[idx], el.unwrap().extract::().unwrap()); + idx += 1; + } + assert_eq!(idx, v.len()); + } + + #[test] + fn test_seq_strings() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = vec!["It", "was", "the", "worst", "of", "times"]; + let ob = v.to_object(py); + let seq = ob.as_object::(py).downcast::().unwrap(); + + let bad_needle = "blurst".to_object(py); + assert_eq!(false, seq.contains(bad_needle).unwrap()); + + let good_needle = "worst".to_object(py); + assert_eq!(true, seq.contains(good_needle).unwrap()); + } + + #[test] + fn test_seq_concat() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v: Vec = vec![1, 2, 3]; + let ob = v.to_object(py); + let seq = ob.as_object::(py).downcast::().unwrap(); + let concat_seq = seq.concat(&seq).unwrap(); + assert_eq!(6, concat_seq.len().unwrap()); + let concat_v: Vec = vec![1, 2, 3, 1, 2, 3]; + for (el, cc) in concat_seq.iter().unwrap().zip(concat_v) { + assert_eq!(cc, el.unwrap().extract::().unwrap()); + } + } + + #[test] + fn test_seq_concat_string() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = "string"; + let ob = v.to_object(py); + let seq = ob.as_object::(py).downcast::().unwrap(); + let concat_seq = seq.concat(&seq).unwrap(); + assert_eq!(12, concat_seq.len().unwrap()); + /*let concat_v = "stringstring".to_owned(); + for (el, cc) in seq.iter(py).unwrap().zip(concat_v.chars()) { + assert_eq!(cc, el.unwrap().extract::(py).unwrap()); //TODO: extract::() is not implemented + }*/ + } + + #[test] + fn test_seq_repeat() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = vec!["foo", "bar"]; + let ob = v.to_object(py); + let seq = ob.as_object::(py).downcast::().unwrap(); + let repeat_seq = seq.repeat(3).unwrap(); + assert_eq!(6, repeat_seq.len().unwrap()); + let repeated = vec!["foo", "bar", "foo", "bar", "foo", "bar"]; + for (el, rpt) in repeat_seq.iter().unwrap().zip(repeated.iter()) { + assert_eq!(*rpt, el.unwrap().extract::().unwrap()); + } + } + + #[test] + fn test_list_coercion() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = vec!["foo", "bar"]; + let ob = v.to_object(py); + let seq = ob.as_object::(py).downcast::().unwrap(); + assert!(seq.list().is_ok()); + } + + #[test] + fn test_strings_coerce_to_lists() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = "foo"; + let ob = v.to_object(py); + let seq = ::try_from(ob.as_object(py)).unwrap(); + assert!(seq.list().is_ok()); + } + + #[test] + fn test_tuple_coercion() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = ("foo", "bar"); + let ob = v.to_object(py); + let seq = ob.as_object::(py).downcast::().unwrap(); + assert!(seq.tuple().is_ok()); + } + + #[test] + fn test_lists_coerce_to_tuples() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = vec!["foo", "bar"]; + let ob = v.to_object(py); + let seq = ob.as_object::(py).downcast::().unwrap(); + assert!(seq.tuple().is_ok()); + } + + #[test] + fn test_extract_tuple_to_vec() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v: Vec = py.eval("(1, 2)", None, None).unwrap().extract().unwrap(); + assert!(v == [1, 2]); + } + + #[test] + fn test_extract_range_to_vec() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v: Vec = py + .eval("range(1, 5)", None, None) + .unwrap() + .extract() + .unwrap(); + assert!(v == [1, 2, 3, 4]); + } + + #[test] + fn test_extract_bytearray_to_array() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v: [u8; 3] = py + .eval("bytearray(b'abc')", None, None) + .unwrap() + .extract() + .unwrap(); + assert!(&v == b"abc"); + } + + #[test] + fn test_extract_bytearray_to_vec() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v: Vec = py + .eval("bytearray(b'abc')", None, None) + .unwrap() + .extract() + .unwrap(); + assert!(v == b"abc"); + } + + #[test] + fn test_seq_try_from_unchecked() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = vec!["foo", "bar"]; + let ob = v.to_object(py); + let any = ob.as_object::(py); + let seq_from = unsafe { ::try_from_unchecked(any) }; + assert!(seq_from.list().is_ok()); + } + + #[test] + fn test_is_empty() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let list = vec![1].to_object(py); + let seq = list + .as_object::(py) + .downcast::() + .unwrap(); + assert_eq!(seq.is_empty().unwrap(), false); + let vec: Vec = Vec::new(); + let empty_list = vec.to_object(py); + let empty_seq = empty_list + .as_object::(py) + .downcast::() + .unwrap(); + assert_eq!(empty_seq.is_empty().unwrap(), true); + } +} diff --git a/src/experimental/objects/set.rs b/src/experimental/objects/set.rs new file mode 100644 index 00000000000..b4f6177ab6b --- /dev/null +++ b/src/experimental/objects/set.rs @@ -0,0 +1,593 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors +// + +use crate::err::{self, PyErr, PyResult}; +#[cfg(Py_LIMITED_API)] +use crate::objects::PyIterator; +use crate::{ + ffi, + objects::{FromPyObject, PyAny, PyNativeObject}, + types::{FrozenSet, Set}, + AsPyPointer, IntoPy, PyObject, Python, ToBorrowedObject, ToPyObject, +}; +use std::cmp; +use std::collections::{BTreeSet, HashSet}; +use std::{collections, hash, ptr}; + +/// Represents a Python `set` +#[repr(transparent)] +pub struct PySet<'py>(pub(crate) PyAny<'py>); +pyo3_native_object!(PySet<'py>, Set, 'py); + +/// Represents a Python `frozenset` +#[repr(transparent)] +pub struct PyFrozenSet<'py>(pub(crate) PyAny<'py>); +pyo3_native_object!(PyFrozenSet<'py>, FrozenSet, 'py); + +impl<'py> PySet<'py> { + /// Creates a new set with elements from the given slice. + /// + /// Returns an error if some element is not hashable. + pub fn new(py: Python<'py>, elements: &[T]) -> PyResult { + let list = elements.to_object(py); + unsafe { PyAny::from_raw_or_fetch_err(py, ffi::PySet_New(list.as_ptr())).map(Self) } + } + + /// Creates a new empty set. + pub fn empty(py: Python<'py>) -> PyResult { + unsafe { PyAny::from_raw_or_fetch_err(py, ffi::PySet_New(ptr::null_mut())).map(Self) } + } + + /// Removes all elements from the set. + #[inline] + pub fn clear(&self) { + unsafe { + ffi::PySet_Clear(self.as_ptr()); + } + } + + /// Returns the number of items in the set. + /// + /// This is equivalent to the Python expression `len(self)`. + #[inline] + pub fn len(&self) -> usize { + unsafe { ffi::PySet_Size(self.as_ptr()) as usize } + } + + /// Checks if set is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Determines if the set contains the specified key. + /// + /// This is equivalent to the Python expression `key in self`. + pub fn contains(&self, key: K) -> PyResult + where + K: ToPyObject, + { + key.with_borrowed_ptr(self.py(), |key| unsafe { + match ffi::PySet_Contains(self.as_ptr(), key) { + 1 => Ok(true), + 0 => Ok(false), + _ => Err(PyErr::fetch(self.py())), + } + }) + } + + /// Removes the element from the set if it is present. + pub fn discard(&self, key: K) + where + K: ToPyObject, + { + key.with_borrowed_ptr(self.py(), |key| unsafe { + ffi::PySet_Discard(self.as_ptr(), key); + }) + } + + /// Adds an element to the set. + pub fn add(&self, key: K) -> PyResult<()> + where + K: ToPyObject, + { + key.with_borrowed_ptr(self.py(), move |key| unsafe { + err::error_on_minusone(self.py(), ffi::PySet_Add(self.as_ptr(), key)) + }) + } + + /// Removes and returns an arbitrary element from the set. + pub fn pop(&self) -> Option> { + let element = + unsafe { PyAny::from_raw_or_fetch_err(self.py(), ffi::PySet_Pop(self.as_ptr())) }; + match element { + Ok(e) => Some(e), + Err(_) => None, + } + } + + /// Returns an iterator of values in this set. + /// + /// Note that it can be unsafe to use when the set might be changed by other code. + pub fn iter(&self) -> PySetIterator<'_, 'py> { + PySetIterator::new(self) + } +} + +#[cfg(Py_LIMITED_API)] +pub struct PySetIterator<'a, 'py> { + it: &'a PyIterator<'py>, +} + +#[cfg(Py_LIMITED_API)] +impl<'a, 'py> PySetIterator<'a, 'py> { + fn new(set: &'a PyAny<'py>) -> Self { + PySetIterator { + it: PyIterator::from_object(set.py(), set).unwrap(), + } + } +} + +#[cfg(Py_LIMITED_API)] +impl<'py> Iterator for PySetIterator<'py> { + type Item = PyAny<'py>; + + #[inline] + fn next(&mut self) -> Option { + self.it.next().map(|p| p.unwrap()) + } +} + +#[cfg(not(Py_LIMITED_API))] +pub struct PySetIterator<'a, 'py> { + set: &'a PyAny<'py>, + pos: isize, +} + +#[cfg(not(Py_LIMITED_API))] +impl<'a, 'py> PySetIterator<'a, 'py> { + fn new(set: &'a PyAny<'py>) -> Self { + PySetIterator { set, pos: 0 } + } +} + +#[cfg(not(Py_LIMITED_API))] +impl<'py> Iterator for PySetIterator<'_, 'py> { + type Item = PyAny<'py>; + + #[inline] + fn next(&mut self) -> Option { + unsafe { + let mut key: *mut ffi::PyObject = std::ptr::null_mut(); + let mut hash: ffi::Py_hash_t = 0; + if ffi::_PySet_NextEntry(self.set.as_ptr(), &mut self.pos, &mut key, &mut hash) != 0 { + // _PySet_NextEntry returns borrowed object; for safety must make owned (see #890) + Some(PyAny::from_borrowed_ptr_or_panic(self.set.py(), key)) + } else { + None + } + } + } +} + +impl<'a, 'py> std::iter::IntoIterator for &'a PySet<'py> { + type Item = PyAny<'py>; + type IntoIter = PySetIterator<'a, 'py>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl ToPyObject for collections::HashSet +where + T: hash::Hash + Eq + ToPyObject, +{ + fn to_object(&self, py: Python) -> PyObject { + let set = PySet::new::(py, &[]).expect("Failed to construct empty set"); + { + for val in self { + set.add(val).expect("Failed to add to set"); + } + } + set.into() + } +} + +impl ToPyObject for collections::BTreeSet +where + T: hash::Hash + Eq + ToPyObject, +{ + fn to_object(&self, py: Python) -> PyObject { + let set = PySet::new::(py, &[]).expect("Failed to construct empty set"); + { + for val in self { + set.add(val).expect("Failed to add to set"); + } + } + set.into() + } +} + +impl IntoPy for HashSet +where + K: IntoPy + Eq + hash::Hash, + S: hash::BuildHasher + Default, +{ + fn into_py(self, py: Python) -> PyObject { + let set = PySet::empty(py).expect("Failed to construct empty set"); + { + for val in self { + set.add(val.into_py(py)).expect("Failed to add to set"); + } + } + set.into() + } +} + +impl<'py, K, S> FromPyObject<'_, 'py> for HashSet +where + K: for<'a> FromPyObject<'a, 'py> + cmp::Eq + hash::Hash, + S: hash::BuildHasher + Default, +{ + fn extract(ob: &PyAny<'py>) -> PyResult { + let set: &PySet = ob.downcast()?; + set.iter().map(|key| K::extract(&key)).collect() + } +} + +impl IntoPy for BTreeSet +where + K: IntoPy + cmp::Ord, +{ + fn into_py(self, py: Python) -> PyObject { + let set = PySet::empty(py).expect("Failed to construct empty set"); + { + for val in self { + set.add(val.into_py(py)).expect("Failed to add to set"); + } + } + set.into() + } +} + +impl<'py, K> FromPyObject<'_, 'py> for BTreeSet +where + K: for<'a> FromPyObject<'a, 'py> + cmp::Ord, +{ + fn extract(ob: &PyAny<'py>) -> PyResult { + let set: &PySet = ob.downcast()?; + set.iter().map(|key| K::extract(&key)).collect() + } +} + +impl<'py> PyFrozenSet<'py> { + /// Creates a new frozenset. + /// + /// May panic when running out of memory. + pub fn new(py: Python<'py>, elements: &[T]) -> PyResult { + let list = elements.to_object(py); + unsafe { PyAny::from_raw_or_fetch_err(py, ffi::PyFrozenSet_New(list.as_ptr())).map(Self) } + } + + /// Creates a new empty frozen set + pub fn empty(py: Python<'py>) -> PyResult { + unsafe { PyAny::from_raw_or_fetch_err(py, ffi::PyFrozenSet_New(ptr::null_mut())).map(Self) } + } + + /// Return the number of items in the set. + /// This is equivalent to len(p) on a set. + #[inline] + pub fn len(&self) -> usize { + unsafe { ffi::PySet_Size(self.as_ptr()) as usize } + } + + /// Check if set is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Determine if the set contains the specified key. + /// This is equivalent to the Python expression `key in self`. + pub fn contains(&self, key: K) -> PyResult + where + K: ToBorrowedObject, + { + key.with_borrowed_ptr(self.py(), |key| unsafe { + match ffi::PySet_Contains(self.as_ptr(), key) { + 1 => Ok(true), + 0 => Ok(false), + _ => Err(PyErr::fetch(self.py())), + } + }) + } + + /// Returns an iterator of values in this frozen set. + /// + /// Note that it can be unsafe to use when the set might be changed by other code. + pub fn iter(&self) -> PySetIterator<'_, 'py> { + PySetIterator::new(self) + } +} + +impl<'a, 'py> std::iter::IntoIterator for &'a PyFrozenSet<'py> { + type Item = PyAny<'py>; + type IntoIter = PySetIterator<'a, 'py>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +#[cfg(feature = "hashbrown")] +mod hashbrown_hashset_conversion { + use super::*; + use crate::{FromPyObject, PyObject, PyResult, ToPyObject}; + + impl ToPyObject for hashbrown::HashSet + where + T: hash::Hash + Eq + ToPyObject, + { + fn to_object(&self, py: Python) -> PyObject { + let set = PySet::new::(py, &[]).expect("Failed to construct empty set"); + { + for val in self { + set.add(val).expect("Failed to add to set"); + } + } + set.into() + } + } + + impl IntoPy for hashbrown::HashSet + where + K: IntoPy + Eq + hash::Hash, + S: hash::BuildHasher + Default, + { + fn into_py(self, py: Python) -> PyObject { + let set = PySet::empty(py).expect("Failed to construct empty set"); + { + for val in self { + set.add(val.into_py(py)).expect("Failed to add to set"); + } + } + set.into() + } + } + + impl<'source, K, S> FromPyObject<'source> for hashbrown::HashSet + where + K: FromPyObject<'source> + cmp::Eq + hash::Hash, + S: hash::BuildHasher + Default, + { + fn extract(ob: &'source PyAny) -> PyResult { + let set: &PySet = ob.downcast()?; + set.iter().map(K::extract).collect() + } + } + + #[test] + fn test_extract_hashbrown_hashset() { + use std::iter::FromIterator; + let gil = Python::acquire_gil(); + let py = gil.python(); + + let set = PySet::new(py, &[1, 2, 3, 4, 5]).unwrap(); + let hash_set: hashbrown::HashSet = set.extract().unwrap(); + assert_eq!( + hash_set, + hashbrown::HashSet::from_iter([1, 2, 3, 4, 5].iter().copied()) + ); + } + + #[test] + fn test_hashbrown_hashset_into_py() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let hs: hashbrown::HashSet = [1, 2, 3, 4, 5].iter().cloned().collect(); + + let hso: PyObject = hs.clone().into_py(py); + + assert_eq!(hs, hso.extract(py).unwrap()); + } +} + +#[cfg(test)] +mod test { + use super::{PyFrozenSet, PySet}; + use crate::{objects::PyTryFrom, IntoPy, PyObject, Python, ToPyObject}; + use std::collections::{BTreeSet, HashSet}; + use std::iter::FromIterator; + + #[test] + fn test_set_new() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let set = PySet::new(py, &[1]).unwrap(); + assert_eq!(1, set.len()); + + let v = vec![1]; + assert!(PySet::new(py, &[v]).is_err()); + } + + #[test] + fn test_set_empty() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let set = PySet::empty(py).unwrap(); + assert_eq!(0, set.len()); + } + + #[test] + fn test_set_len() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let mut v = HashSet::new(); + let ob = v.to_object(py); + let set = ::try_from(ob.as_object(py)).unwrap(); + assert_eq!(0, set.len()); + v.insert(7); + let ob = v.to_object(py); + let set2 = ::try_from(ob.as_object(py)).unwrap(); + assert_eq!(1, set2.len()); + } + + #[test] + fn test_set_clear() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let set = PySet::new(py, &[1]).unwrap(); + assert_eq!(1, set.len()); + set.clear(); + assert_eq!(0, set.len()); + } + + #[test] + fn test_set_contains() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let set = PySet::new(py, &[1]).unwrap(); + assert!(set.contains(1).unwrap()); + } + + #[test] + fn test_set_discard() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let set = PySet::new(py, &[1]).unwrap(); + set.discard(2); + assert_eq!(1, set.len()); + set.discard(1); + assert_eq!(0, set.len()); + } + + #[test] + fn test_set_add() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let set = PySet::new(py, &[1, 2]).unwrap(); + set.add(1).unwrap(); // Add a dupliated element + assert!(set.contains(1).unwrap()); + } + + #[test] + fn test_set_pop() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let set = PySet::new(py, &[1]).unwrap(); + let val = set.pop(); + assert!(val.is_some()); + let val2 = set.pop(); + assert!(val2.is_none()); + assert!(py + .eval("print('Exception state should not be set.')", None, None) + .is_ok()); + } + + #[test] + fn test_set_iter() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let set = PySet::new(py, &[1]).unwrap(); + + // iter method + for el in set.iter() { + assert_eq!(1i32, el.extract().unwrap()); + } + + // intoiterator iteration + for el in &set { + assert_eq!(1i32, el.extract().unwrap()); + } + } + + #[test] + fn test_frozenset_new_and_len() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let set = PyFrozenSet::new(py, &[1]).unwrap(); + assert_eq!(1, set.len()); + + let v = vec![1]; + assert!(PyFrozenSet::new(py, &[v]).is_err()); + } + + #[test] + fn test_frozenset_empty() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let set = PyFrozenSet::empty(py).unwrap(); + assert_eq!(0, set.len()); + } + + #[test] + fn test_frozenset_contains() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let set = PyFrozenSet::new(py, &[1]).unwrap(); + assert!(set.contains(1).unwrap()); + } + + #[test] + fn test_frozenset_iter() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let set = PyFrozenSet::new(py, &[1]).unwrap(); + + // iter method + for el in set.iter() { + assert_eq!(1i32, el.extract::().unwrap()); + } + + // intoiterator iteration + for el in &set { + assert_eq!(1i32, el.extract::().unwrap()); + } + } + + #[test] + fn test_extract_hashset() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let set = PySet::new(py, &[1, 2, 3, 4, 5]).unwrap(); + let hash_set: HashSet = set.extract().unwrap(); + assert_eq!( + hash_set, + HashSet::from_iter([1, 2, 3, 4, 5].iter().copied()) + ); + } + + #[test] + fn test_extract_btreeset() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let set = PySet::new(py, &[1, 2, 3, 4, 5]).unwrap(); + let hash_set: BTreeSet = set.extract().unwrap(); + assert_eq!( + hash_set, + BTreeSet::from_iter([1, 2, 3, 4, 5].iter().copied()) + ); + } + + #[test] + fn test_set_into_py() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let bt: BTreeSet = [1, 2, 3, 4, 5].iter().cloned().collect(); + let hs: HashSet = [1, 2, 3, 4, 5].iter().cloned().collect(); + + let bto: PyObject = bt.clone().into_py(py); + let hso: PyObject = hs.clone().into_py(py); + + assert_eq!(bt, bto.extract(py).unwrap()); + assert_eq!(hs, hso.extract(py).unwrap()); + } +} diff --git a/src/experimental/objects/slice.rs b/src/experimental/objects/slice.rs new file mode 100644 index 00000000000..73a0e79fbb1 --- /dev/null +++ b/src/experimental/objects/slice.rs @@ -0,0 +1,88 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors + +use crate::err::{PyErr, PyResult}; +use crate::ffi::{self, Py_ssize_t}; +use crate::{ + objects::{PyAny, PyNativeObject}, + types::Slice, + AsPyPointer, PyObject, Python, ToPyObject, +}; +use std::os::raw::c_long; + +/// Represents a Python `slice`. +/// +/// Only `c_long` indices supported at the moment by the `PySlice` object. +#[repr(transparent)] +pub struct PySlice<'py>(pub(crate) PyAny<'py>); +pyo3_native_object!(PySlice<'py>, Slice, 'py); + +/// Represents Python `slice` indices. +pub struct PySliceIndices { + pub start: isize, + pub stop: isize, + pub step: isize, + pub slicelength: isize, +} + +impl PySliceIndices { + pub fn new(start: isize, stop: isize, step: isize) -> PySliceIndices { + PySliceIndices { + start, + stop, + step, + slicelength: 0, + } + } +} + +impl<'py> PySlice<'py> { + /// Constructs a new slice with the given elements. + pub fn new(py: Python<'py>, start: isize, stop: isize, step: isize) -> Self { + unsafe { + let ptr = ffi::PySlice_New( + ffi::PyLong_FromLong(start as c_long), + ffi::PyLong_FromLong(stop as c_long), + ffi::PyLong_FromLong(step as c_long), + ); + Self(PyAny::from_raw_or_panic(py, ptr)) + } + } + + /// Retrieves the start, stop, and step indices from the slice object, + /// assuming a sequence of length `length`, and stores the length of the + /// slice in its `slicelength` member. + #[inline] + pub fn indices(&self, length: c_long) -> PyResult { + // non-negative Py_ssize_t should always fit into Rust usize + unsafe { + let slicelength: isize = 0; + let start: isize = 0; + let stop: isize = 0; + let step: isize = 0; + let r = ffi::PySlice_GetIndicesEx( + self.as_ptr(), + length as Py_ssize_t, + &start as *const _ as *mut _, + &stop as *const _ as *mut _, + &step as *const _ as *mut _, + &slicelength as *const _ as *mut _, + ); + if r == 0 { + Ok(PySliceIndices { + start, + stop, + step, + slicelength, + }) + } else { + Err(PyErr::fetch(self.py())) + } + } + } +} + +impl ToPyObject for PySliceIndices { + fn to_object(&self, py: Python) -> PyObject { + PySlice::new(py, self.start, self.stop, self.step).into() + } +} diff --git a/src/experimental/objects/str.rs b/src/experimental/objects/str.rs new file mode 100644 index 00000000000..ebf121c3eb7 --- /dev/null +++ b/src/experimental/objects/str.rs @@ -0,0 +1,295 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors + +use crate::types::Str; +use crate::{ + ffi, + objects::{FromPyObject, PyAny, PyBytes, PyNativeObject}, + AsPyPointer, IntoPy, PyErr, PyObject, PyResult, Python, ToPyObject, +}; +use std::borrow::Cow; +use std::os::raw::c_char; +use std::str; + +/// Represents a Python `str` (a Unicode string object). +/// +/// This type is immutable. +#[repr(transparent)] +pub struct PyStr<'py>(pub(crate) PyAny<'py>); + +pyo3_native_object!(PyStr<'py>, Str, 'py); + +impl<'py> PyStr<'py> { + /// Creates a new Python string object. + /// + /// Panics if out of memory. + pub fn new(py: Python<'py>, s: &str) -> Self { + let ptr = s.as_ptr() as *const c_char; + let len = s.len() as ffi::Py_ssize_t; + unsafe { + Self(PyAny::from_raw_or_panic( + py, + ffi::PyUnicode_FromStringAndSize(ptr, len), + )) + } + } + + pub fn from_object(src: &PyAny<'py>, encoding: &str, errors: &str) -> Self { + unsafe { + Self(PyAny::from_raw_or_panic( + src.py(), + ffi::PyUnicode_FromEncodedObject( + src.as_ptr(), + encoding.as_ptr() as *const c_char, + errors.as_ptr() as *const c_char, + ), + )) + } + } + + /// Gets the Python string as a byte slice. + /// + /// Returns a `UnicodeEncodeError` if the input is not valid unicode + /// (containing unpaired surrogates). + #[inline] + pub fn to_str(&self) -> PyResult<&str> { + #[cfg(not(Py_LIMITED_API))] + unsafe { + let mut size: ffi::Py_ssize_t = 0; + let data = ffi::PyUnicode_AsUTF8AndSize(self.as_ptr(), &mut size) as *const u8; + if data.is_null() { + Err(PyErr::fetch(self.py())) + } else { + let slice = std::slice::from_raw_parts(data, size as usize); + Ok(std::str::from_utf8_unchecked(slice)) + } + } + #[cfg(Py_LIMITED_API)] + unsafe { + let data = ffi::PyUnicode_AsUTF8String(self.as_ptr()); + if data.is_null() { + Err(PyErr::fetch(self.py())) + } else { + let bytes = self.py().from_owned_ptr::(data); + Ok(std::str::from_utf8_unchecked(bytes.as_bytes())) + } + } + } + + /// Converts the `PyStr` into a Rust string. + /// + /// Unpaired surrogates invalid UTF-8 sequences are + /// replaced with `U+FFFD REPLACEMENT CHARACTER`. + pub fn to_string_lossy(&self) -> Cow { + match self.to_str() { + Ok(s) => Cow::Borrowed(s), + Err(_) => { + let bytes: PyBytes<'py> = unsafe { + PyBytes(PyAny::from_raw_or_panic( + self.py(), + ffi::PyUnicode_AsEncodedString( + self.as_ptr(), + b"utf-8\0" as *const _ as _, + b"surrogatepass\0" as *const _ as _, + ), + )) + }; + Cow::Owned(String::from_utf8_lossy(bytes.as_bytes()).to_string()) + } + } + } +} + +/// Converts a Rust `str` to a Python object. +/// See `PyStr::new` for details on the conversion. +impl ToPyObject for str { + #[inline] + fn to_object(&self, py: Python) -> PyObject { + PyStr::new(py, self).into() + } +} + +impl<'a> IntoPy for &'a str { + #[inline] + fn into_py(self, py: Python) -> PyObject { + PyStr::new(py, self).into() + } +} + +/// Converts a Rust `Cow` to a Python object. +/// See `PyStr::new` for details on the conversion. +impl<'a> ToPyObject for Cow<'a, str> { + #[inline] + fn to_object(&self, py: Python) -> PyObject { + PyStr::new(py, self).into() + } +} + +/// Converts a Rust `String` to a Python object. +/// See `PyStr::new` for details on the conversion. +impl ToPyObject for String { + #[inline] + fn to_object(&self, py: Python) -> PyObject { + PyStr::new(py, self).into() + } +} + +impl ToPyObject for char { + fn to_object(&self, py: Python) -> PyObject { + self.into_py(py) + } +} + +impl IntoPy for char { + fn into_py(self, py: Python) -> PyObject { + let mut bytes = [0u8; 4]; + PyStr::new(py, self.encode_utf8(&mut bytes)).into() + } +} + +impl IntoPy for String { + fn into_py(self, py: Python) -> PyObject { + PyStr::new(py, &self).into() + } +} + +impl<'a> IntoPy for &'a String { + #[inline] + fn into_py(self, py: Python) -> PyObject { + PyStr::new(py, self).into() + } +} + +/// Allows extracting strings from Python objects. +/// Accepts Python `str` objects. +impl<'a> FromPyObject<'a, '_> for &'a str { + fn extract(ob: &'a PyAny<'_>) -> PyResult { + ob.downcast::()?.to_str() + } +} + +/// Allows extracting strings from Python objects. +/// Accepts Python `str` objects. +impl FromPyObject<'_, '_> for String { + fn extract(obj: &PyAny) -> PyResult { + obj.downcast::()?.to_str().map(ToOwned::to_owned) + } +} + +impl FromPyObject<'_, '_> for char { + fn extract(obj: &PyAny) -> PyResult { + let s = obj.downcast::()?.to_str()?; + let mut iter = s.chars(); + if let (Some(ch), None) = (iter.next(), iter.next()) { + Ok(ch) + } else { + Err(crate::exceptions::PyValueError::new_err( + "expected a string of length 1", + )) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::experimental::ToPyObject; + + #[test] + fn test_non_bmp() { + Python::with_gil(|py| { + let s = "\u{1F30F}"; + let py_str = s.to_object(py); + assert_eq!(s, py_str.extract::().unwrap()); + }) + } + + #[test] + fn test_extract_str() { + Python::with_gil(|py| { + let s = "Hello Python"; + let py_str = s.to_object(py); + let s2: &str = py_str.extract().unwrap(); + assert_eq!(s, s2); + }) + } + + #[test] + fn test_extract_char() { + Python::with_gil(|py| { + let ch = 'πŸ˜ƒ'; + let py_str = ch.to_object(py); + let ch2: char = FromPyObject::extract(&py_str).unwrap(); + assert_eq!(ch, ch2); + }) + } + + #[test] + fn test_extract_char_err() { + Python::with_gil(|py| { + let s = "Hello Python"; + let py_str = s.to_object(py); + let err: crate::PyResult = FromPyObject::extract(&py_str); + assert!(err + .unwrap_err() + .to_string() + .contains("expected a string of length 1")); + }) + } + + #[test] + fn test_to_str_ascii() { + Python::with_gil(|py| { + let s = "ascii 🐈"; + let py_str = PyStr::new(py, s); + assert_eq!(s, py_str.to_str().unwrap()); + }) + } + + #[test] + fn test_to_str_surrogate() { + Python::with_gil(|py| { + let obj: PyAny = py.eval(r#"'\ud800'"#, None, None).unwrap().to_owned(); + let py_str = obj.downcast::().unwrap(); + assert!(py_str.to_str().is_err()); + }) + } + + #[test] + fn test_to_str_unicode() { + Python::with_gil(|py| { + let s = "ε“ˆε“ˆπŸˆ"; + let py_str = PyStr::new(py, s); + assert_eq!(s, py_str.to_str().unwrap()); + }) + } + + #[test] + fn test_to_string_lossy() { + Python::with_gil(|py| { + let obj: PyObject = py + .eval(r#"'🐈 Hello \ud800World'"#, None, None) + .unwrap() + .into(); + let py_str = obj.as_object::(py).downcast::().unwrap(); + assert_eq!(py_str.to_string_lossy(), "🐈 Hello οΏ½οΏ½οΏ½World"); + }) + } + + #[test] + fn test_debug_string() { + Python::with_gil(|py| { + let v = "Hello\n".to_object(py); + let s = v.downcast::().unwrap(); + assert_eq!(format!("{:?}", s), "'Hello\\n'"); + }) + } + + #[test] + fn test_display_string() { + Python::with_gil(|py| { + let v = "Hello\n".to_object(py); + let s = v.downcast::().unwrap(); + assert_eq!(format!("{}", s), "Hello\n"); + }) + } +} diff --git a/src/experimental/objects/tuple.rs b/src/experimental/objects/tuple.rs new file mode 100644 index 00000000000..c71df88fb10 --- /dev/null +++ b/src/experimental/objects/tuple.rs @@ -0,0 +1,322 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors + +use crate::ffi::{self, Py_ssize_t}; +use crate::{ + exceptions, + objects::{FromPyObject, PyAny, PyNativeObject, PyTryFrom}, + types::Tuple, + AsPyPointer, IntoPy, IntoPyPointer, Py, PyErr, PyObject, PyResult, Python, ToPyObject, +}; + +/// Represents a Python `tuple` object. +/// +/// This type is immutable. +#[repr(transparent)] +pub struct PyTuple<'py>(pub(crate) PyAny<'py>); +pyo3_native_object!(PyTuple<'py>, Tuple, 'py); + +impl<'py> PyTuple<'py> { + /// Constructs a new tuple with the given elements. + pub fn new(py: Python<'py>, elements: impl IntoIterator) -> Self + where + T: ToPyObject, + U: ExactSizeIterator, + { + let elements_iter = elements.into_iter(); + let len = elements_iter.len(); + unsafe { + let ptr = ffi::PyTuple_New(len as Py_ssize_t); + for (i, e) in elements_iter.enumerate() { + ffi::PyTuple_SetItem(ptr, i as Py_ssize_t, e.to_object(py).into_ptr()); + } + Self(PyAny::from_raw_or_panic(py, ptr)) + } + } + + /// Constructs an empty tuple (on the Python side, a singleton object). + pub fn empty(py: Python<'py>) -> Self { + unsafe { Self(PyAny::from_raw_or_panic(py, ffi::PyTuple_New(0))) } + } + + /// Gets the length of the tuple. + pub fn len(&self) -> usize { + unsafe { + // non-negative Py_ssize_t should always fit into Rust uint + ffi::PyTuple_Size(self.as_ptr()) as usize + } + } + + /// Checks if the tuple is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Takes a slice of the tuple pointed from `low` to `high` and returns it as a new tuple. + pub fn slice(&self, low: isize, high: isize) -> Self { + unsafe { + Self(PyAny::from_raw_or_panic( + self.py(), + ffi::PyTuple_GetSlice(self.as_ptr(), low, high), + )) + } + } + + /// Takes a slice of the tuple from `low` to the end and returns it as a new tuple. + pub fn split_from(&self, low: isize) -> Self { + unsafe { + let ptr = ffi::PyTuple_GetSlice(self.as_ptr(), low, self.len() as Py_ssize_t); + Self(PyAny::from_raw_or_panic(self.py(), ptr)) + } + } + + /// Gets the tuple item at the specified index. + /// + /// Panics if the index is out of range. + pub fn get_item(&self, index: usize) -> PyAny<'py> { + assert!(index < self.len()); + unsafe { + PyAny::from_borrowed_ptr_or_panic( + self.py(), + ffi::PyTuple_GetItem(self.as_ptr(), index as Py_ssize_t), + ) + } + } + + /// Returns `self` as a slice of objects. + /// + /// Not available when compiled with Py_LIMITED_API. + #[cfg(not(Py_LIMITED_API))] + pub fn as_slice(&self) -> &[PyAny<'py>] { + // This is safe because PyAny has the same memory layout as *mut ffi::PyObject, + // and because tuples are immutable. + unsafe { + let ptr = self.as_ptr() as *mut ffi::PyTupleObject; + let slice = std::slice::from_raw_parts((*ptr).ob_item.as_ptr(), self.len()); + &*(slice as *const [*mut ffi::PyObject] as *const [PyAny]) + } + } + + /// Returns an iterator over the tuple items. + pub fn iter(&self) -> PyTupleIterator<'_, 'py> { + PyTupleIterator { + tuple: self, + index: 0, + length: self.len(), + } + } +} + +/// Used by `PyTuple::iter()`. +pub struct PyTupleIterator<'a, 'py> { + tuple: &'a PyTuple<'py>, + index: usize, + length: usize, +} + +impl<'a, 'py> Iterator for PyTupleIterator<'a, 'py> { + type Item = PyAny<'py>; + + #[inline] + fn next(&mut self) -> Option { + if self.index < self.length { + let item = self.tuple.get_item(self.index); + self.index += 1; + Some(item) + } else { + None + } + } +} + +impl<'a, 'py> IntoIterator for &'a PyTuple<'py> { + type Item = PyAny<'py>; + type IntoIter = PyTupleIterator<'a, 'py>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +fn wrong_tuple_length(t: &PyTuple, expected_length: usize) -> PyErr { + let msg = format!( + "Expected tuple of length {}, but got tuple of length {}.", + expected_length, + t.len() + ); + exceptions::PyValueError::new_err(msg) +} + +macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+} => { + impl <$($T: ToPyObject),+> ToPyObject for ($($T,)+) { + fn to_object(&self, py: Python) -> PyObject { + unsafe { + let ptr = ffi::PyTuple_New($length); + $(ffi::PyTuple_SetItem(ptr, $n, self.$n.to_object(py).into_ptr());)+ + PyObject::from_owned_ptr(py, ptr) + } + } + } + impl <$($T: IntoPy),+> IntoPy for ($($T,)+) { + fn into_py(self, py: Python) -> PyObject { + unsafe { + let ptr = ffi::PyTuple_New($length); + $(ffi::PyTuple_SetItem(ptr, $n, self.$n.into_py(py).into_ptr());)+ + PyObject::from_owned_ptr(py, ptr) + } + } + } + + impl <$($T: IntoPy),+> IntoPy> for ($($T,)+) { + fn into_py(self, py: Python) -> Py { + unsafe { + let ptr = ffi::PyTuple_New($length); + $(ffi::PyTuple_SetItem(ptr, $n, self.$n.into_py(py).into_ptr());)+ + Py::from_owned_ptr(py, ptr) + } + } + } + + impl<'py, $($T),+> FromPyObject<'_, 'py> for ($($T,)+) + where + $($T: for<'a> FromPyObject<'a, 'py>),+ + { + fn extract(obj: &PyAny<'py>) -> PyResult + { + let t = ::try_from(obj)?; + if t.len() == $length { + Ok(( + $(t.get_item($n).extract::<$T>()?,)+ + )) + } else { + Err(wrong_tuple_length(t, $length)) + } + } + } +}); + +tuple_conversion!(1, (ref0, 0, A)); +tuple_conversion!(2, (ref0, 0, A), (ref1, 1, B)); +tuple_conversion!(3, (ref0, 0, A), (ref1, 1, B), (ref2, 2, C)); +tuple_conversion!(4, (ref0, 0, A), (ref1, 1, B), (ref2, 2, C), (ref3, 3, D)); +tuple_conversion!( + 5, + (ref0, 0, A), + (ref1, 1, B), + (ref2, 2, C), + (ref3, 3, D), + (ref4, 4, E) +); +tuple_conversion!( + 6, + (ref0, 0, A), + (ref1, 1, B), + (ref2, 2, C), + (ref3, 3, D), + (ref4, 4, E), + (ref5, 5, F) +); +tuple_conversion!( + 7, + (ref0, 0, A), + (ref1, 1, B), + (ref2, 2, C), + (ref3, 3, D), + (ref4, 4, E), + (ref5, 5, F), + (ref6, 6, G) +); +tuple_conversion!( + 8, + (ref0, 0, A), + (ref1, 1, B), + (ref2, 2, C), + (ref3, 3, D), + (ref4, 4, E), + (ref5, 5, F), + (ref6, 6, G), + (ref7, 7, H) +); +tuple_conversion!( + 9, + (ref0, 0, A), + (ref1, 1, B), + (ref2, 2, C), + (ref3, 3, D), + (ref4, 4, E), + (ref5, 5, F), + (ref6, 6, G), + (ref7, 7, H), + (ref8, 8, I) +); + +#[cfg(test)] +mod test { + use crate::objects::{PyTryFrom, PyTuple}; + use crate::{Python, ToPyObject}; + use std::collections::HashSet; + + #[test] + fn test_new() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let ob = PyTuple::new(py, &[1, 2, 3]); + assert_eq!(3, ob.len()); + assert_eq!((1, 2, 3), ob.extract().unwrap()); + + let mut map = HashSet::new(); + map.insert(1); + map.insert(2); + PyTuple::new(py, &map); + } + + #[test] + fn test_len() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let ob = (1, 2, 3).to_object(py); + let tuple = ::try_from(ob.as_object(py)).unwrap(); + assert_eq!(3, tuple.len()); + assert_eq!((1, 2, 3), tuple.extract().unwrap()); + } + + #[test] + fn test_iter() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let ob = (1, 2, 3).to_object(py); + let tuple = ::try_from(ob.as_object(py)).unwrap(); + assert_eq!(3, tuple.len()); + let mut iter = tuple.iter(); + assert_eq!(1, iter.next().unwrap().extract().unwrap()); + assert_eq!(2, iter.next().unwrap().extract().unwrap()); + assert_eq!(3, iter.next().unwrap().extract().unwrap()); + } + + #[test] + fn test_into_iter() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let ob = (1, 2, 3).to_object(py); + let tuple = ::try_from(ob.as_object(py)).unwrap(); + assert_eq!(3, tuple.len()); + + for (i, item) in tuple.iter().enumerate() { + assert_eq!(i + 1, item.extract().unwrap()); + } + } + + #[test] + #[cfg(not(Py_LIMITED_API))] + fn test_as_slice() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let ob = (1, 2, 3).to_object(py); + let tuple = ::try_from(ob.as_object(py)).unwrap(); + + let slice = tuple.as_slice(); + assert_eq!(3, slice.len()); + assert_eq!(1, slice[0].extract().unwrap()); + assert_eq!(2, slice[1].extract().unwrap()); + assert_eq!(3, slice[2].extract().unwrap()); + } +} diff --git a/src/experimental/objects/typeobject.rs b/src/experimental/objects/typeobject.rs new file mode 100644 index 00000000000..15eea9b208c --- /dev/null +++ b/src/experimental/objects/typeobject.rs @@ -0,0 +1,68 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors +// +// based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython + +use crate::err::{PyErr, PyResult}; +use crate::type_object::PyTypeObject; +use crate::{ + ffi, + objects::{PyAny, PyNativeObject, PyStr}, + types::Type, + AsPyPointer, Python, +}; + +/// Represents a reference to a Python `type object`. +#[repr(transparent)] +pub struct PyType<'py>(pub(crate) PyAny<'py>); +pyo3_native_object!(PyType<'py>, Type, 'py); + +impl<'py> PyType<'py> { + /// Creates a new type object. + #[inline] + pub fn new(py: Python<'py>) -> Self { + T::type_object(py).to_owned() + } + + /// Retrieves the underlying FFI pointer associated with this Python object. + #[inline] + pub fn as_type_ptr(&self) -> *mut ffi::PyTypeObject { + self.as_ptr() as *mut ffi::PyTypeObject + } + + /// Gets the name of the `PyType`. + pub fn name(&self) -> PyResult> { + self.getattr("__qualname__")?.extract() + } + + /// Checks whether `self` is subclass of type `T`. + /// + /// Equivalent to Python's `issubclass` function. + pub fn is_subclass(&self) -> PyResult + where + T: PyTypeObject, + { + let result = + unsafe { ffi::PyObject_IsSubclass(self.as_ptr(), T::type_object(self.py()).as_ptr()) }; + if result == -1 { + Err(PyErr::fetch(self.py())) + } else if result == 1 { + Ok(true) + } else { + Ok(false) + } + } + + /// Check whether `obj` is an instance of `self`. + /// + /// Equivalent to Python's `isinstance` function. + pub fn is_instance(&self, obj: &T) -> PyResult { + let result = unsafe { ffi::PyObject_IsInstance(obj.as_ptr(), self.as_ptr()) }; + if result == -1 { + Err(PyErr::fetch(self.py())) + } else if result == 1 { + Ok(true) + } else { + Ok(false) + } + } +} diff --git a/src/instance.rs b/src/instance.rs index 573bea5863f..41230728f09 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -6,8 +6,8 @@ use crate::pycell::{PyBorrowError, PyBorrowMutError, PyCell}; use crate::type_object::PyBorrowFlagLayout; use crate::types::{PyDict, PyTuple}; use crate::{ - ffi, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyAny, PyClass, PyClassInitializer, - PyRef, PyRefMut, PyTypeInfo, Python, ToPyObject, + ffi, objects::PyNativeObject, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyAny, PyClass, + PyClassInitializer, PyRef, PyRefMut, PyTypeInfo, Python, ToPyObject, }; use std::marker::PhantomData; use std::mem; @@ -132,6 +132,19 @@ where } } +impl Py +where + T: PyNativeType, +{ + #[inline] + pub fn as_object<'a, 'py, O>(&'a self, _py: Python<'py>) -> &'a O + where + O: PyNativeObject<'py, NativeType = T>, + { + unsafe { &*(self as *const Self as *const O) } + } +} + impl Py where T: PyClass, @@ -568,6 +581,18 @@ where } } +impl<'a, T> crate::experimental::FromPyObject<'a, '_> for Py +where + T: PyTypeInfo, + &'a T::AsRefTarget: FromPyObject<'a>, + T::AsRefTarget: 'a + AsPyPointer, +{ + /// Extracts `Self` from the source `PyObject`. + fn extract(ob: &'a crate::experimental::objects::PyAny) -> PyResult { + ob.as_owned_ref().extract() + } +} + /// Py can be used as an error when T is an Error. /// /// However for GIL lifetime reasons, cause() cannot be implemented for Py. @@ -657,4 +682,15 @@ mod test { assert_eq!(p.get_refcnt(py), cnt); }); } + + #[test] + fn test_as_object() { + Python::with_gil(|py| { + let dict: Py = PyDict::new(py).into(); + assert_eq!( + dict.as_ptr(), + dict.as_object::(py).as_ptr() + ); + }) + } } diff --git a/src/lib.rs b/src/lib.rs index be7502ce033..fbaa776e148 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -163,6 +163,7 @@ pub use { #[doc(hidden)] pub use libc; +pub mod experimental; // The CPython stable ABI does not include PyBuffer. #[cfg(not(Py_LIMITED_API))] pub mod buffer; @@ -197,6 +198,8 @@ mod python; pub mod type_object; pub mod types; +pub(crate) use experimental::*; + /// The proc macros, which are also part of the prelude. #[cfg(feature = "macros")] pub mod proc_macro { diff --git a/src/types/boolobject.rs b/src/types/boolobject.rs index 1853631bca5..027ee3ca1df 100644 --- a/src/types/boolobject.rs +++ b/src/types/boolobject.rs @@ -1,8 +1,5 @@ // Copyright (c) 2017-present PyO3 Project and Contributors -use crate::{ - ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyObject, PyResult, PyTryFrom, Python, - ToPyObject, -}; +use crate::{ffi, AsPyPointer, FromPyObject, PyAny, PyResult, PyTryFrom, Python}; /// Represents a Python `bool`. #[repr(transparent)] @@ -24,30 +21,6 @@ impl PyBool { } } -/// Converts a Rust `bool` to a Python `bool`. -impl ToPyObject for bool { - #[inline] - fn to_object(&self, py: Python) -> PyObject { - unsafe { - PyObject::from_borrowed_ptr( - py, - if *self { - ffi::Py_True() - } else { - ffi::Py_False() - }, - ) - } - } -} - -impl IntoPy for bool { - #[inline] - fn into_py(self, py: Python) -> PyObject { - PyBool::new(py, self).into() - } -} - /// Converts a Python `bool` to a Rust `bool`. /// /// Fails with `TypeError` if the input is not a Python `bool`. diff --git a/src/types/bytes.rs b/src/types/bytes.rs index ab5b13290a0..c4e69719221 100644 --- a/src/types/bytes.rs +++ b/src/types/bytes.rs @@ -1,7 +1,4 @@ -use crate::{ - ffi, AsPyPointer, FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult, PyTryFrom, Python, - ToPyObject, -}; +use crate::{ffi, AsPyPointer, FromPyObject, Py, PyAny, PyResult, Python}; use std::ops::Index; use std::os::raw::c_char; use std::slice::SliceIndex; @@ -95,15 +92,9 @@ impl> Index for PyBytes { } } -impl<'a> IntoPy for &'a [u8] { - fn into_py(self, py: Python) -> PyObject { - PyBytes::new(py, self).to_object(py) - } -} - impl<'a> FromPyObject<'a> for &'a [u8] { fn extract(obj: &'a PyAny) -> PyResult { - Ok(::try_from(obj)?.as_bytes()) + ::extract(crate::objects::PyAny::from_type_any(&obj)) } } #[cfg(test)] diff --git a/src/types/dict.rs b/src/types/dict.rs index abd0395894b..173246ff048 100644 --- a/src/types/dict.rs +++ b/src/types/dict.rs @@ -5,12 +5,11 @@ use crate::types::{PyAny, PyList}; #[cfg(not(PyPy))] use crate::IntoPyPointer; use crate::{ - ffi, AsPyPointer, FromPyObject, IntoPy, PyNativeType, PyObject, PyTryFrom, Python, - ToBorrowedObject, ToPyObject, + ffi, AsPyPointer, FromPyObject, PyNativeType, PyObject, Python, ToBorrowedObject, ToPyObject, }; use std::collections::{BTreeMap, HashMap}; use std::ptr::NonNull; -use std::{cmp, collections, hash}; +use std::{cmp, hash}; /// Represents a Python `dict`. #[repr(transparent)] @@ -214,54 +213,6 @@ impl<'a> std::iter::IntoIterator for &'a PyDict { } } -impl ToPyObject for collections::HashMap -where - K: hash::Hash + cmp::Eq + ToPyObject, - V: ToPyObject, - H: hash::BuildHasher, -{ - fn to_object(&self, py: Python) -> PyObject { - IntoPyDict::into_py_dict(self, py).into() - } -} - -impl ToPyObject for collections::BTreeMap -where - K: cmp::Eq + ToPyObject, - V: ToPyObject, -{ - fn to_object(&self, py: Python) -> PyObject { - IntoPyDict::into_py_dict(self, py).into() - } -} - -impl IntoPy for collections::HashMap -where - K: hash::Hash + cmp::Eq + IntoPy, - V: IntoPy, - H: hash::BuildHasher, -{ - fn into_py(self, py: Python) -> PyObject { - let iter = self - .into_iter() - .map(|(k, v)| (k.into_py(py), v.into_py(py))); - IntoPyDict::into_py_dict(iter, py).into() - } -} - -impl IntoPy for collections::BTreeMap -where - K: cmp::Eq + IntoPy, - V: IntoPy, -{ - fn into_py(self, py: Python) -> PyObject { - let iter = self - .into_iter() - .map(|(k, v)| (k.into_py(py), v.into_py(py))); - IntoPyDict::into_py_dict(iter, py).into() - } -} - /// Conversion trait that allows a sequence of tuples to be converted into `PyDict` /// Primary use case for this trait is `call` and `call_method` methods as keywords argument. pub trait IntoPyDict { @@ -323,14 +274,14 @@ where } } -impl<'source, K, V, S> FromPyObject<'source> for HashMap +impl<'py, K, V, S> FromPyObject<'py> for HashMap where - K: FromPyObject<'source> + cmp::Eq + hash::Hash, - V: FromPyObject<'source>, + K: FromPyObject<'py> + cmp::Eq + hash::Hash, + V: FromPyObject<'py>, S: hash::BuildHasher + Default, { - fn extract(ob: &'source PyAny) -> Result { - let dict = ::try_from(ob)?; + fn extract(ob: &'py PyAny) -> Result { + let dict = ob.downcast::()?; let mut ret = HashMap::with_capacity_and_hasher(dict.len(), S::default()); for (k, v) in dict.iter() { ret.insert(K::extract(k)?, V::extract(v)?); @@ -339,13 +290,13 @@ where } } -impl<'source, K, V> FromPyObject<'source> for BTreeMap +impl<'py, K, V> FromPyObject<'py> for BTreeMap where - K: FromPyObject<'source> + cmp::Ord, - V: FromPyObject<'source>, + K: FromPyObject<'py> + cmp::Ord, + V: FromPyObject<'py>, { - fn extract(ob: &'source PyAny) -> Result { - let dict = ::try_from(ob)?; + fn extract(ob: &'py PyAny) -> Result { + let dict = ob.downcast::()?; let mut ret = BTreeMap::new(); for (k, v) in dict.iter() { ret.insert(K::extract(k)?, V::extract(v)?); diff --git a/src/types/floatob.rs b/src/types/floatob.rs index 5b773237069..1703ba15038 100644 --- a/src/types/floatob.rs +++ b/src/types/floatob.rs @@ -1,10 +1,7 @@ // Copyright (c) 2017-present PyO3 Project and Contributors // // based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython -use crate::{ - ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyErr, PyNativeType, PyObject, PyResult, Python, - ToPyObject, -}; +use crate::{ffi, AsPyPointer, FromPyObject, PyAny, PyErr, PyNativeType, PyResult, Python}; use std::os::raw::c_double; /// Represents a Python `float` object. @@ -35,18 +32,6 @@ impl PyFloat { } } -impl ToPyObject for f64 { - fn to_object(&self, py: Python) -> PyObject { - PyFloat::new(py, *self).into() - } -} - -impl IntoPy for f64 { - fn into_py(self, py: Python) -> PyObject { - PyFloat::new(py, self).into() - } -} - impl<'source> FromPyObject<'source> for f64 { // PyFloat_AsDouble returns -1.0 upon failure #![cfg_attr(feature = "cargo-clippy", allow(clippy::float_cmp))] @@ -61,18 +46,6 @@ impl<'source> FromPyObject<'source> for f64 { } } -impl ToPyObject for f32 { - fn to_object(&self, py: Python) -> PyObject { - PyFloat::new(py, f64::from(*self)).into() - } -} - -impl IntoPy for f32 { - fn into_py(self, py: Python) -> PyObject { - PyFloat::new(py, f64::from(self)).into() - } -} - impl<'source> FromPyObject<'source> for f32 { fn extract(obj: &'source PyAny) -> PyResult { Ok(obj.extract::()? as f32) diff --git a/src/types/function.rs b/src/types/function.rs index 832a52b9b73..2c3afeb0a4c 100644 --- a/src/types/function.rs +++ b/src/types/function.rs @@ -77,16 +77,16 @@ impl PyCFunction { let (mod_ptr, module_name) = if let Some(m) = module { let mod_ptr = m.as_ptr(); let name = m.name()?.into_py(py); - (mod_ptr, name.as_ptr()) + (mod_ptr, Some(name)) } else { - (std::ptr::null_mut(), std::ptr::null_mut()) + (std::ptr::null_mut(), None) }; unsafe { py.from_owned_ptr_or_err::(ffi::PyCFunction_NewEx( Box::into_raw(Box::new(def)), mod_ptr, - module_name, + module_name.as_ptr(), )) } } diff --git a/src/types/list.rs b/src/types/list.rs index 4a0586e4fcd..3d10593a620 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -5,8 +5,7 @@ use crate::err::{self, PyResult}; use crate::ffi::{self, Py_ssize_t}; use crate::{ - AsPyPointer, IntoPy, IntoPyPointer, PyAny, PyNativeType, PyObject, Python, ToBorrowedObject, - ToPyObject, + AsPyPointer, IntoPyPointer, PyAny, PyNativeType, PyObject, Python, ToBorrowedObject, ToPyObject, }; /// Represents a Python `list`. @@ -162,67 +161,6 @@ impl<'a> std::iter::IntoIterator for &'a PyList { } } -impl ToPyObject for [T] -where - T: ToPyObject, -{ - fn to_object(&self, py: Python<'_>) -> PyObject { - unsafe { - let ptr = ffi::PyList_New(self.len() as Py_ssize_t); - for (i, e) in self.iter().enumerate() { - let obj = e.to_object(py).into_ptr(); - ffi::PyList_SetItem(ptr, i as Py_ssize_t, obj); - } - PyObject::from_owned_ptr(py, ptr) - } - } -} - -macro_rules! array_impls { - ($($N:expr),+) => { - $( - impl IntoPy for [T; $N] - where - T: ToPyObject - { - fn into_py(self, py: Python) -> PyObject { - self.as_ref().to_object(py) - } - } - )+ - } -} - -array_impls!( - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 26, 27, 28, 29, 30, 31, 32 -); - -impl ToPyObject for Vec -where - T: ToPyObject, -{ - fn to_object(&self, py: Python<'_>) -> PyObject { - self.as_slice().to_object(py) - } -} - -impl IntoPy for Vec -where - T: IntoPy, -{ - fn into_py(self, py: Python) -> PyObject { - unsafe { - let ptr = ffi::PyList_New(self.len() as Py_ssize_t); - for (i, e) in self.into_iter().enumerate() { - let obj = e.into_py(py).into_ptr(); - ffi::PyList_SetItem(ptr, i as Py_ssize_t, obj); - } - PyObject::from_owned_ptr(py, ptr) - } - } -} - #[cfg(test)] mod test { use crate::types::PyList; diff --git a/src/types/mod.rs b/src/types/mod.rs index fbd01e412cd..17169f9b5cd 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -157,6 +157,13 @@ macro_rules! pyobject_native_type_extract { $crate::PyTryFrom::try_from(obj).map_err(Into::into) } } + + impl<'py, $($type_param,)*> $crate::experimental::FromPyObject<'py, 'py> for &'py $name { + fn extract(obj: &'py $crate::experimental::objects::PyAny<'py>) -> $crate::PyResult { + use $crate::experimental::PyNativeObject; + $crate::PyTryFrom::try_from(obj.as_owned_ref()).map_err(Into::into) + } + } } } @@ -243,3 +250,33 @@ mod slice; mod string; mod tuple; mod typeobject; + +pub mod experimental { + pub type Any = super::PyAny; + pub type Bool = super::PyBool; + pub type ByteArray = super::PyByteArray; + pub type Bytes = super::PyBytes; + pub type Complex = super::PyComplex; + pub type Date = super::PyDate; + pub type DateTime = super::PyDateTime; + pub type Time = super::PyTime; + pub type TimeDelta = super::PyDelta; + pub type TzInfo = super::PyTzInfo; + pub type Dict = super::PyDict; + pub type Float = super::PyFloat; + pub type CFunction = super::PyCFunction; + pub type Function = super::PyFunction; + pub type Iterator = super::PyIterator; + pub type List = super::PyList; + pub type Module = super::PyModule; + pub type Int = super::PyLong; + pub type Sequence = super::PySequence; + pub type FrozenSet = super::PyFrozenSet; + pub type Set = super::PySet; + pub type Slice = super::PySlice; + pub type Str = super::PyString; + pub type Tuple = super::PyTuple; + pub type Type = super::PyType; +} + +pub(crate) use experimental::*; diff --git a/src/types/set.rs b/src/types/set.rs index 5593490afd8..27476713429 100644 --- a/src/types/set.rs +++ b/src/types/set.rs @@ -5,12 +5,12 @@ use crate::err::{self, PyErr, PyResult}; #[cfg(Py_LIMITED_API)] use crate::types::PyIterator; use crate::{ - ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyNativeType, PyObject, Python, - ToBorrowedObject, ToPyObject, + ffi, AsPyPointer, FromPyObject, PyAny, PyNativeType, PyObject, Python, ToBorrowedObject, + ToPyObject, }; use std::cmp; use std::collections::{BTreeSet, HashSet}; -use std::{collections, hash, ptr}; +use std::{hash, ptr}; /// Represents a Python `set` #[repr(transparent)] @@ -183,52 +183,6 @@ impl<'a> std::iter::IntoIterator for &'a PySet { } } -impl ToPyObject for collections::HashSet -where - T: hash::Hash + Eq + ToPyObject, -{ - fn to_object(&self, py: Python) -> PyObject { - let set = PySet::new::(py, &[]).expect("Failed to construct empty set"); - { - for val in self { - set.add(val).expect("Failed to add to set"); - } - } - set.into() - } -} - -impl ToPyObject for collections::BTreeSet -where - T: hash::Hash + Eq + ToPyObject, -{ - fn to_object(&self, py: Python) -> PyObject { - let set = PySet::new::(py, &[]).expect("Failed to construct empty set"); - { - for val in self { - set.add(val).expect("Failed to add to set"); - } - } - set.into() - } -} - -impl IntoPy for HashSet -where - K: IntoPy + Eq + hash::Hash, - S: hash::BuildHasher + Default, -{ - fn into_py(self, py: Python) -> PyObject { - let set = PySet::empty(py).expect("Failed to construct empty set"); - { - for val in self { - set.add(val.into_py(py)).expect("Failed to add to set"); - } - } - set.into() - } -} - impl<'source, K, S> FromPyObject<'source> for HashSet where K: FromPyObject<'source> + cmp::Eq + hash::Hash, @@ -240,21 +194,6 @@ where } } -impl IntoPy for BTreeSet -where - K: IntoPy + cmp::Ord, -{ - fn into_py(self, py: Python) -> PyObject { - let set = PySet::empty(py).expect("Failed to construct empty set"); - { - for val in self { - set.add(val.into_py(py)).expect("Failed to add to set"); - } - } - set.into() - } -} - impl<'source, K> FromPyObject<'source> for BTreeSet where K: FromPyObject<'source> + cmp::Ord, diff --git a/src/types/string.rs b/src/types/string.rs index ee5d3314504..23bf5170ab0 100644 --- a/src/types/string.rs +++ b/src/types/string.rs @@ -1,10 +1,7 @@ // Copyright (c) 2017-present PyO3 Project and Contributors use crate::types::PyBytes; -use crate::{ - ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyErr, PyNativeType, PyObject, PyResult, - PyTryFrom, Python, ToPyObject, -}; +use crate::{ffi, AsPyPointer, FromPyObject, PyAny, PyErr, PyNativeType, PyResult, Python}; use std::borrow::Cow; use std::os::raw::c_char; use std::str; @@ -107,71 +104,11 @@ where } } -/// Converts a Rust `str` to a Python object. -/// See `PyString::new` for details on the conversion. -impl ToPyObject for str { - #[inline] - fn to_object(&self, py: Python) -> PyObject { - PyString::new(py, self).into() - } -} - -impl<'a> IntoPy for &'a str { - #[inline] - fn into_py(self, py: Python) -> PyObject { - PyString::new(py, self).into() - } -} - -/// Converts a Rust `Cow` to a Python object. -/// See `PyString::new` for details on the conversion. -impl<'a> ToPyObject for Cow<'a, str> { - #[inline] - fn to_object(&self, py: Python) -> PyObject { - PyString::new(py, self).into() - } -} - -/// Converts a Rust `String` to a Python object. -/// See `PyString::new` for details on the conversion. -impl ToPyObject for String { - #[inline] - fn to_object(&self, py: Python) -> PyObject { - PyString::new(py, self).into() - } -} - -impl ToPyObject for char { - fn to_object(&self, py: Python) -> PyObject { - self.into_py(py) - } -} - -impl IntoPy for char { - fn into_py(self, py: Python) -> PyObject { - let mut bytes = [0u8; 4]; - PyString::new(py, self.encode_utf8(&mut bytes)).into() - } -} - -impl IntoPy for String { - fn into_py(self, py: Python) -> PyObject { - PyString::new(py, &self).into() - } -} - -impl<'a> IntoPy for &'a String { - #[inline] - fn into_py(self, py: Python) -> PyObject { - PyString::new(py, self).into() - } -} - /// Allows extracting strings from Python objects. /// Accepts Python `str` and `unicode` objects. impl<'source> FromPyObject<'source> for &'source str { - fn extract(ob: &'source PyAny) -> PyResult { - ::try_from(ob)?.to_str() + fn extract(obj: &'source PyAny) -> PyResult { + ::extract(crate::objects::PyAny::from_type_any(&obj)) } } @@ -179,23 +116,13 @@ impl<'source> FromPyObject<'source> for &'source str { /// Accepts Python `str` and `unicode` objects. impl FromPyObject<'_> for String { fn extract(obj: &PyAny) -> PyResult { - ::try_from(obj)? - .to_str() - .map(ToOwned::to_owned) + ::extract(crate::objects::PyAny::from_type_any(&obj)) } } impl FromPyObject<'_> for char { fn extract(obj: &PyAny) -> PyResult { - let s = PyString::try_from(obj)?.to_str()?; - let mut iter = s.chars(); - if let (Some(ch), None) = (iter.next(), iter.next()) { - Ok(ch) - } else { - Err(crate::exceptions::PyValueError::new_err( - "expected a string of length 1", - )) - } + ::extract(crate::objects::PyAny::from_type_any(&obj)) } } diff --git a/src/types/tuple.rs b/src/types/tuple.rs index 9dbe9395164..ae2d2288998 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -2,8 +2,8 @@ use crate::ffi::{self, Py_ssize_t}; use crate::{ - exceptions, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, Py, PyAny, PyErr, PyNativeType, - PyObject, PyResult, PyTryFrom, Python, ToPyObject, + exceptions, AsPyPointer, FromPyObject, IntoPyPointer, PyAny, PyErr, PyNativeType, PyResult, + PyTryFrom, Python, ToPyObject, }; /// Represents a Python `tuple` object. @@ -142,35 +142,6 @@ fn wrong_tuple_length(t: &PyTuple, expected_length: usize) -> PyErr { } macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+} => { - impl <$($T: ToPyObject),+> ToPyObject for ($($T,)+) { - fn to_object(&self, py: Python) -> PyObject { - unsafe { - let ptr = ffi::PyTuple_New($length); - $(ffi::PyTuple_SetItem(ptr, $n, self.$n.to_object(py).into_ptr());)+ - PyObject::from_owned_ptr(py, ptr) - } - } - } - impl <$($T: IntoPy),+> IntoPy for ($($T,)+) { - fn into_py(self, py: Python) -> PyObject { - unsafe { - let ptr = ffi::PyTuple_New($length); - $(ffi::PyTuple_SetItem(ptr, $n, self.$n.into_py(py).into_ptr());)+ - PyObject::from_owned_ptr(py, ptr) - } - } - } - - impl <$($T: IntoPy),+> IntoPy> for ($($T,)+) { - fn into_py(self, py: Python) -> Py { - unsafe { - let ptr = ffi::PyTuple_New($length); - $(ffi::PyTuple_SetItem(ptr, $n, self.$n.into_py(py).into_ptr());)+ - Py::from_owned_ptr(py, ptr) - } - } - } - impl<'s, $($T: FromPyObject<'s>),+> FromPyObject<'s> for ($($T,)+) { fn extract(obj: &'s PyAny) -> PyResult { diff --git a/tests/test_module.rs b/tests/test_module.rs index 5ed75bf4b41..92b03f3cb4f 100644 --- a/tests/test_module.rs +++ b/tests/test_module.rs @@ -293,6 +293,7 @@ fn vararg_module(_py: Python, m: &PyModule) -> PyResult<()> { m.add_function(pyo3::wrap_pyfunction!(ext_vararg_fn, m)?) .unwrap(); + Ok(()) }