diff --git a/newsfragments/2911.added.md b/newsfragments/2911.added.md new file mode 100644 index 00000000..4348412f --- /dev/null +++ b/newsfragments/2911.added.md @@ -0,0 +1 @@ +Add `py.Ellipsis()` and `py_any.is_ellipsis()` methods. diff --git a/pyo3-ffi/src/sliceobject.rs b/pyo3-ffi/src/sliceobject.rs index 5f3138ef..6b23f5ba 100644 --- a/pyo3-ffi/src/sliceobject.rs +++ b/pyo3-ffi/src/sliceobject.rs @@ -2,6 +2,7 @@ use crate::object::*; use crate::pyport::Py_ssize_t; use std::os::raw::c_int; +#[cfg_attr(windows, link(name = "pythonXY"))] extern "C" { #[cfg_attr(PyPy, link_name = "_PyPy_EllipsisObject")] static mut _Py_EllipsisObject: PyObject; diff --git a/src/instance.rs b/src/instance.rs index b96c4cd5..fd937e5d 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -519,6 +519,13 @@ impl Py { unsafe { ffi::Py_None() == self.as_ptr() } } + /// Returns whether the object is Ellipsis, e.g. `...`. + /// + /// This is equivalent to the Python expression `self is ...`. + pub fn is_ellipsis(&self) -> bool { + unsafe { ffi::Py_Ellipsis() == self.as_ptr() } + } + /// Returns whether the object is considered to be true. /// /// This is equivalent to the Python expression `bool(self)`. @@ -1147,6 +1154,22 @@ a = A() }) } + #[test] + fn test_is_ellipsis() { + Python::with_gil(|py| { + let v = py + .eval("...", None, None) + .map_err(|e| e.print(py)) + .unwrap() + .to_object(py); + + assert!(v.is_ellipsis()); + + let not_ellipsis = 5.to_object(py); + assert!(!not_ellipsis.is_ellipsis()); + }); + } + #[cfg(feature = "macros")] mod using_macros { use super::*; diff --git a/src/marker.rs b/src/marker.rs index 707402c9..714af761 100644 --- a/src/marker.rs +++ b/src/marker.rs @@ -619,6 +619,13 @@ impl<'py> Python<'py> { unsafe { PyObject::from_borrowed_ptr(self, ffi::Py_None()) } } + /// Gets the Python builtin value `Ellipsis`, or `...`. + #[allow(non_snake_case)] // the Python keyword starts with uppercase + #[inline] + pub fn Ellipsis(self) -> PyObject { + unsafe { PyObject::from_borrowed_ptr(self, ffi::Py_Ellipsis()) } + } + /// Gets the Python builtin value `NotImplemented`. #[allow(non_snake_case)] // the Python keyword starts with uppercase #[inline] @@ -1032,4 +1039,15 @@ mod tests { let state = unsafe { crate::ffi::PyGILState_Check() }; assert_eq!(state, GIL_NOT_HELD); } + + #[test] + fn test_ellipsis() { + Python::with_gil(|py| { + assert_eq!(py.Ellipsis().to_string(), "Ellipsis"); + + let v = py.eval("...", None, None).map_err(|e| e.print(py)).unwrap(); + + assert!(v.eq(py.Ellipsis()).unwrap()); + }); + } } diff --git a/src/types/any.rs b/src/types/any.rs index 5abe9785..6b3cd7a0 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -669,6 +669,13 @@ impl PyAny { unsafe { ffi::Py_None() == self.as_ptr() } } + /// Returns whether the object is Ellipsis, e.g. `...`. + /// + /// This is equivalent to the Python expression `self is ...`. + pub fn is_ellipsis(&self) -> bool { + unsafe { ffi::Py_Ellipsis() == 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`. @@ -1135,4 +1142,16 @@ class SimpleClass: let bools = [true, false]; test_eq_methods_generic(&bools); } + + #[test] + fn test_is_ellipsis() { + Python::with_gil(|py| { + let v = py.eval("...", None, None).map_err(|e| e.print(py)).unwrap(); + + assert!(v.is_ellipsis()); + + let not_ellipsis = 5.to_object(py).into_ref(py); + assert!(!not_ellipsis.is_ellipsis()); + }); + } }