1use std::ffi::{c_int, c_long, c_longlong, c_short, c_uint, c_ulong, c_ulonglong, c_ushort};
2use std::mem::size_of;
3use std::ptr;
4
5#[cfg(feature = "half")]
6use half::{bf16, f16};
7use num_traits::{Bounded, Zero};
8#[cfg(feature = "half")]
9use pyo3::sync::PyOnceLock;
10use pyo3::{
11 conversion::IntoPyObject,
12 exceptions::{PyIndexError, PyValueError},
13 ffi::{self, PyTuple_Size},
14 pyobject_native_type_named,
15 types::{PyAnyMethods, PyDict, PyDictMethods, PyTuple, PyType},
16 Borrowed, Bound, Py, PyAny, PyResult, PyTypeInfo, Python,
17};
18
19use crate::npyffi::{
20 self, _PyDataType_GET_ITEM_DATA, NpyTypes, PyArray_Descr, PyDataType_ALIGNMENT,
21 PyDataType_ELSIZE, PyDataType_FIELDS, PyDataType_FLAGS, PyDataType_NAMES, PyDataType_SUBARRAY,
22 NPY_ALIGNED_STRUCT, NPY_BYTEORDER_CHAR, NPY_ITEM_HASOBJECT, NPY_TYPES, PY_ARRAY_API,
23};
24
25pub use num_complex::{Complex32, Complex64};
26
27#[repr(transparent)]
51pub struct PyArrayDescr(PyAny);
52
53pyobject_native_type_named!(PyArrayDescr);
54
55unsafe impl PyTypeInfo for PyArrayDescr {
56 const NAME: &'static str = "PyArrayDescr";
57 const MODULE: Option<&'static str> = Some("numpy");
58
59 #[inline]
60 fn type_object_raw<'py>(py: Python<'py>) -> *mut ffi::PyTypeObject {
61 unsafe { npyffi::get_type_object(py, NpyTypes::PyArrayDescr_Type) }
62 }
63}
64
65#[inline]
67pub fn dtype<'py, T: Element>(py: Python<'py>) -> Bound<'py, PyArrayDescr> {
68 T::get_dtype(py)
69}
70
71impl PyArrayDescr {
72 #[inline]
78 pub fn new<'a, 'py, T>(py: Python<'py>, ob: T) -> PyResult<Bound<'py, Self>>
79 where
80 T: IntoPyObject<'py>,
81 {
82 fn inner<'py>(
83 py: Python<'py>,
84 obj: Borrowed<'_, 'py, PyAny>,
85 ) -> PyResult<Bound<'py, PyArrayDescr>> {
86 let mut descr: *mut PyArray_Descr = ptr::null_mut();
87 unsafe {
88 PY_ARRAY_API.PyArray_DescrConverter2(py, obj.as_ptr(), &mut descr);
90 Bound::from_owned_ptr_or_err(py, descr.cast()).map(|any| any.cast_into_unchecked())
91 }
92 }
93
94 inner(
95 py,
96 ob.into_pyobject(py)
97 .map_err(Into::into)?
98 .into_any()
99 .as_borrowed(),
100 )
101 }
102
103 #[inline]
105 pub fn object(py: Python<'_>) -> Bound<'_, Self> {
106 Self::from_npy_type(py, NPY_TYPES::NPY_OBJECT)
107 }
108
109 #[inline]
111 pub fn of<'py, T: Element>(py: Python<'py>) -> Bound<'py, Self> {
112 T::get_dtype(py)
113 }
114
115 fn from_npy_type<'py>(py: Python<'py>, npy_type: NPY_TYPES) -> Bound<'py, Self> {
116 unsafe {
117 let descr = PY_ARRAY_API.PyArray_DescrFromType(py, npy_type as _);
118 Bound::from_owned_ptr(py, descr.cast()).cast_into_unchecked()
119 }
120 }
121
122 pub(crate) fn new_from_npy_type<'py>(py: Python<'py>, npy_type: NPY_TYPES) -> Bound<'py, Self> {
123 unsafe {
124 let descr = PY_ARRAY_API.PyArray_DescrNewFromType(py, npy_type as _);
125 Bound::from_owned_ptr(py, descr.cast()).cast_into_unchecked()
126 }
127 }
128}
129
130#[doc(alias = "PyArrayDescr")]
132pub trait PyArrayDescrMethods<'py>: Sealed {
133 fn as_dtype_ptr(&self) -> *mut PyArray_Descr;
135
136 fn into_dtype_ptr(self) -> *mut PyArray_Descr;
140
141 fn is_equiv_to(&self, other: &Self) -> bool;
143
144 fn typeobj(&self) -> Bound<'py, PyType>;
151
152 fn num(&self) -> c_int {
162 unsafe { &*_PyDataType_GET_ITEM_DATA(self.as_dtype_ptr()) }.type_num
163 }
164
165 fn itemsize(&self) -> usize;
171
172 fn alignment(&self) -> usize;
178
179 fn byteorder(&self) -> u8 {
187 unsafe { &*_PyDataType_GET_ITEM_DATA(self.as_dtype_ptr()) }
188 .byteorder
189 .max(0) as _
190 }
191
192 fn char(&self) -> u8 {
200 unsafe { &*_PyDataType_GET_ITEM_DATA(self.as_dtype_ptr()) }
201 .type_
202 .max(0) as _
203 }
204
205 fn kind(&self) -> u8 {
213 unsafe { &*_PyDataType_GET_ITEM_DATA(self.as_dtype_ptr()) }
214 .kind
215 .max(0) as _
216 }
217
218 fn flags(&self) -> u64;
224
225 fn ndim(&self) -> usize;
231
232 fn base(&self) -> Bound<'py, PyArrayDescr>;
240
241 fn shape(&self) -> Vec<usize>;
249
250 fn has_object(&self) -> bool {
256 self.flags() & NPY_ITEM_HASOBJECT != 0
257 }
258
259 fn is_aligned_struct(&self) -> bool {
268 self.flags() & NPY_ALIGNED_STRUCT != 0
269 }
270
271 fn has_subarray(&self) -> bool;
275
276 fn has_fields(&self) -> bool;
280
281 fn is_native_byteorder(&self) -> Option<bool> {
283 match self.byteorder() {
285 b'=' => Some(true),
286 b'|' => None,
287 byteorder => Some(byteorder == NPY_BYTEORDER_CHAR::NPY_NATBYTE as u8),
288 }
289 }
290
291 fn names(&self) -> Option<Vec<String>>;
299
300 fn get_field(&self, name: &str) -> PyResult<(Bound<'py, PyArrayDescr>, usize)>;
311}
312
313mod sealed {
314 pub trait Sealed {}
315}
316
317use sealed::Sealed;
318
319impl<'py> PyArrayDescrMethods<'py> for Bound<'py, PyArrayDescr> {
320 fn as_dtype_ptr(&self) -> *mut PyArray_Descr {
321 self.as_ptr() as _
322 }
323
324 fn into_dtype_ptr(self) -> *mut PyArray_Descr {
325 self.into_ptr() as _
326 }
327
328 fn is_equiv_to(&self, other: &Self) -> bool {
329 let self_ptr = self.as_dtype_ptr();
330 let other_ptr = other.as_dtype_ptr();
331
332 unsafe {
333 self_ptr == other_ptr
334 || PY_ARRAY_API.PyArray_EquivTypes(self.py(), self_ptr, other_ptr) != 0
335 }
336 }
337
338 fn typeobj(&self) -> Bound<'py, PyType> {
339 let dtype_type_ptr = unsafe { &*_PyDataType_GET_ITEM_DATA(self.as_dtype_ptr()) }.typeobj;
340 unsafe { PyType::from_borrowed_type_ptr(self.py(), dtype_type_ptr) }
341 }
342
343 fn itemsize(&self) -> usize {
344 unsafe { PyDataType_ELSIZE(self.py(), self.as_dtype_ptr()).max(0) as _ }
345 }
346
347 fn alignment(&self) -> usize {
348 unsafe { PyDataType_ALIGNMENT(self.py(), self.as_dtype_ptr()).max(0) as _ }
349 }
350
351 fn flags(&self) -> u64 {
352 unsafe { PyDataType_FLAGS(self.py(), self.as_dtype_ptr()) as _ }
353 }
354
355 fn ndim(&self) -> usize {
356 let subarray = unsafe { PyDataType_SUBARRAY(self.py(), self.as_dtype_ptr()).as_ref() };
357 match subarray {
358 None => 0,
359 Some(subarray) => unsafe { PyTuple_Size(subarray.shape) }.max(0) as _,
360 }
361 }
362
363 fn base(&self) -> Bound<'py, PyArrayDescr> {
364 let subarray = unsafe { PyDataType_SUBARRAY(self.py(), self.as_dtype_ptr()).as_ref() };
365 match subarray {
366 None => self.clone(),
367 Some(subarray) => unsafe {
368 Bound::from_borrowed_ptr(self.py(), subarray.base.cast()).cast_into_unchecked()
369 },
370 }
371 }
372
373 fn shape(&self) -> Vec<usize> {
374 let subarray = unsafe { PyDataType_SUBARRAY(self.py(), self.as_dtype_ptr()).as_ref() };
375 match subarray {
376 None => Vec::new(),
377 Some(subarray) => {
378 let shape = unsafe { Borrowed::from_ptr(self.py(), subarray.shape) };
380 shape.extract().unwrap()
381 }
382 }
383 }
384
385 fn has_subarray(&self) -> bool {
386 unsafe { !PyDataType_SUBARRAY(self.py(), self.as_dtype_ptr()).is_null() }
387 }
388
389 fn has_fields(&self) -> bool {
390 unsafe { !PyDataType_NAMES(self.py(), self.as_dtype_ptr()).is_null() }
391 }
392
393 fn names(&self) -> Option<Vec<String>> {
394 if !self.has_fields() {
395 return None;
396 }
397 let names = unsafe {
398 Borrowed::from_ptr(self.py(), PyDataType_NAMES(self.py(), self.as_dtype_ptr()))
399 };
400 names.extract().ok()
401 }
402
403 fn get_field(&self, name: &str) -> PyResult<(Bound<'py, PyArrayDescr>, usize)> {
404 if !self.has_fields() {
405 return Err(PyValueError::new_err(
406 "cannot get field information: type descriptor has no fields",
407 ));
408 }
409 let dict = unsafe {
410 Borrowed::from_ptr(self.py(), PyDataType_FIELDS(self.py(), self.as_dtype_ptr()))
411 };
412 let dict = unsafe { dict.cast_unchecked::<PyDict>() };
413 let tuple = dict
415 .get_item(name)?
416 .ok_or_else(|| PyIndexError::new_err(name.to_owned()))?
417 .cast_into::<PyTuple>()
418 .unwrap();
419 let dtype = tuple
421 .get_item(0)
422 .unwrap()
423 .cast_into::<PyArrayDescr>()
424 .unwrap();
425 let offset = tuple.get_item(1).unwrap().extract().unwrap();
426 Ok((dtype, offset))
427 }
428}
429
430impl Sealed for Bound<'_, PyArrayDescr> {}
431
432pub unsafe trait Element: Sized + Send + Sync {
470 const IS_COPY: bool;
478
479 fn get_dtype(py: Python<'_>) -> Bound<'_, PyArrayDescr>;
481
482 fn clone_ref(&self, py: Python<'_>) -> Self;
484
485 #[inline]
490 fn vec_from_slice(py: Python<'_>, slc: &[Self]) -> Vec<Self> {
491 slc.iter().map(|elem| elem.clone_ref(py)).collect()
492 }
493
494 #[inline]
499 fn array_from_view<D>(
500 py: Python<'_>,
501 view: ::ndarray::ArrayView<'_, Self, D>,
502 ) -> ::ndarray::Array<Self, D>
503 where
504 D: ::ndarray::Dimension,
505 {
506 view.map(|elem| elem.clone_ref(py))
507 }
508}
509
510fn npy_int_type_lookup<T, T0, T1, T2>(npy_types: [NPY_TYPES; 3]) -> NPY_TYPES {
511 match size_of::<T>() {
515 x if x == size_of::<T0>() => npy_types[0],
516 x if x == size_of::<T1>() => npy_types[1],
517 x if x == size_of::<T2>() => npy_types[2],
518 _ => panic!("Unable to match integer type descriptor: {npy_types:?}"),
519 }
520}
521
522fn npy_int_type<T: Bounded + Zero + Sized + PartialEq>() -> NPY_TYPES {
523 let is_unsigned = T::min_value() == T::zero();
524 let bit_width = 8 * size_of::<T>();
525
526 match (is_unsigned, bit_width) {
527 (false, 8) => NPY_TYPES::NPY_BYTE,
528 (false, 16) => NPY_TYPES::NPY_SHORT,
529 (false, 32) => npy_int_type_lookup::<i32, c_long, c_int, c_short>([
530 NPY_TYPES::NPY_LONG,
531 NPY_TYPES::NPY_INT,
532 NPY_TYPES::NPY_SHORT,
533 ]),
534 (false, 64) => npy_int_type_lookup::<i64, c_long, c_longlong, c_int>([
535 NPY_TYPES::NPY_LONG,
536 NPY_TYPES::NPY_LONGLONG,
537 NPY_TYPES::NPY_INT,
538 ]),
539 (true, 8) => NPY_TYPES::NPY_UBYTE,
540 (true, 16) => NPY_TYPES::NPY_USHORT,
541 (true, 32) => npy_int_type_lookup::<u32, c_ulong, c_uint, c_ushort>([
542 NPY_TYPES::NPY_ULONG,
543 NPY_TYPES::NPY_UINT,
544 NPY_TYPES::NPY_USHORT,
545 ]),
546 (true, 64) => npy_int_type_lookup::<u64, c_ulong, c_ulonglong, c_uint>([
547 NPY_TYPES::NPY_ULONG,
548 NPY_TYPES::NPY_ULONGLONG,
549 NPY_TYPES::NPY_UINT,
550 ]),
551 _ => unreachable!(),
552 }
553}
554
555macro_rules! clone_methods_impl {
558 ($Self:ty) => {
559 #[inline]
560 fn clone_ref(&self, _py: ::pyo3::Python<'_>) -> $Self {
561 ::std::clone::Clone::clone(self)
562 }
563
564 #[inline]
565 fn vec_from_slice(_py: ::pyo3::Python<'_>, slc: &[$Self]) -> Vec<$Self> {
566 ::std::borrow::ToOwned::to_owned(slc)
567 }
568
569 #[inline]
570 fn array_from_view<D>(
571 _py: ::pyo3::Python<'_>,
572 view: ::ndarray::ArrayView<'_, $Self, D>,
573 ) -> ::ndarray::Array<$Self, D>
574 where
575 D: ::ndarray::Dimension,
576 {
577 ::ndarray::ArrayView::to_owned(&view)
578 }
579 };
580}
581pub(crate) use clone_methods_impl;
582use pyo3::BoundObject;
583
584macro_rules! impl_element_scalar {
585 (@impl: $ty:ty, $npy_type:expr $(,#[$meta:meta])*) => {
586 $(#[$meta])*
587 unsafe impl Element for $ty {
588 const IS_COPY: bool = true;
589
590 fn get_dtype(py: Python<'_>) -> Bound<'_, PyArrayDescr> {
591 PyArrayDescr::from_npy_type(py, $npy_type)
592 }
593
594 clone_methods_impl!($ty);
595 }
596 };
597 ($ty:ty => $npy_type:ident $(,#[$meta:meta])*) => {
598 impl_element_scalar!(@impl: $ty, NPY_TYPES::$npy_type $(,#[$meta])*);
599 };
600 ($($tys:ty),+) => {
601 $(impl_element_scalar!(@impl: $tys, npy_int_type::<$tys>());)+
602 };
603}
604
605impl_element_scalar!(bool => NPY_BOOL);
606
607impl_element_scalar!(i8, i16, i32, i64);
608impl_element_scalar!(u8, u16, u32, u64);
609
610impl_element_scalar!(f32 => NPY_FLOAT);
611impl_element_scalar!(f64 => NPY_DOUBLE);
612
613#[cfg(feature = "half")]
614impl_element_scalar!(f16 => NPY_HALF);
615
616#[cfg(feature = "half")]
617unsafe impl Element for bf16 {
618 const IS_COPY: bool = true;
619
620 fn get_dtype(py: Python<'_>) -> Bound<'_, PyArrayDescr> {
621 static DTYPE: PyOnceLock<Py<PyArrayDescr>> = PyOnceLock::new();
622
623 DTYPE
624 .get_or_init(py, || {
625 PyArrayDescr::new(py, "bfloat16").expect("A package which provides a `bfloat16` data type for NumPy is required to use the `half::bf16` element type.").unbind()
626 })
627 .clone_ref(py)
628 .into_bound(py)
629 }
630
631 clone_methods_impl!(Self);
632}
633
634impl_element_scalar!(Complex32 => NPY_CFLOAT,
635 #[doc = "Complex type with `f32` components which maps to `numpy.csingle` (`numpy.complex64`)."]);
636impl_element_scalar!(Complex64 => NPY_CDOUBLE,
637 #[doc = "Complex type with `f64` components which maps to `numpy.cdouble` (`numpy.complex128`)."]);
638
639#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
640impl_element_scalar!(usize, isize);
641
642unsafe impl Element for Py<PyAny> {
643 const IS_COPY: bool = false;
644
645 fn get_dtype(py: Python<'_>) -> Bound<'_, PyArrayDescr> {
646 PyArrayDescr::object(py)
647 }
648
649 #[inline]
650 fn clone_ref(&self, py: Python<'_>) -> Self {
651 Py::clone_ref(self, py)
652 }
653}
654
655#[cfg(test)]
656mod tests {
657 use super::*;
658
659 use pyo3::types::PyString;
660 use pyo3::{py_run, types::PyTypeMethods};
661
662 use crate::npyffi::{is_numpy_2, NPY_NEEDS_PYAPI};
663
664 #[test]
665 fn test_dtype_new() {
666 Python::attach(|py| {
667 assert!(PyArrayDescr::new(py, "float64")
668 .unwrap()
669 .is(dtype::<f64>(py)));
670
671 let dt = PyArrayDescr::new(py, [("a", "O"), ("b", "?")].as_ref()).unwrap();
672 assert_eq!(dt.names(), Some(vec!["a".to_owned(), "b".to_owned()]));
673 assert!(dt.has_object());
674 assert!(dt.get_field("a").unwrap().0.is(dtype::<Py<PyAny>>(py)));
675 assert!(dt.get_field("b").unwrap().0.is(dtype::<bool>(py)));
676
677 assert!(PyArrayDescr::new(py, 123_usize).is_err());
678 });
679 }
680
681 #[test]
682 fn test_dtype_names() {
683 fn type_name<T: Element>(py: Python<'_>) -> Bound<'_, PyString> {
684 dtype::<T>(py).typeobj().qualname().unwrap()
685 }
686 Python::attach(|py| {
687 if is_numpy_2(py) {
688 assert_eq!(type_name::<bool>(py), "bool");
689 } else {
690 assert_eq!(type_name::<bool>(py), "bool_");
691 }
692
693 assert_eq!(type_name::<i8>(py), "int8");
694 assert_eq!(type_name::<i16>(py), "int16");
695 assert_eq!(type_name::<i32>(py), "int32");
696 assert_eq!(type_name::<i64>(py), "int64");
697 assert_eq!(type_name::<u8>(py), "uint8");
698 assert_eq!(type_name::<u16>(py), "uint16");
699 assert_eq!(type_name::<u32>(py), "uint32");
700 assert_eq!(type_name::<u64>(py), "uint64");
701 assert_eq!(type_name::<f32>(py), "float32");
702 assert_eq!(type_name::<f64>(py), "float64");
703
704 assert_eq!(type_name::<Complex32>(py), "complex64");
705 assert_eq!(type_name::<Complex64>(py), "complex128");
706
707 #[cfg(target_pointer_width = "32")]
708 {
709 assert_eq!(type_name::<usize>(py), "uint32");
710 assert_eq!(type_name::<isize>(py), "int32");
711 }
712
713 #[cfg(target_pointer_width = "64")]
714 {
715 assert_eq!(type_name::<usize>(py), "uint64");
716 assert_eq!(type_name::<isize>(py), "int64");
717 }
718 });
719 }
720
721 #[test]
722 fn test_dtype_methods_scalar() {
723 Python::attach(|py| {
724 let dt = dtype::<f64>(py);
725
726 assert_eq!(dt.num(), NPY_TYPES::NPY_DOUBLE as c_int);
727 assert_eq!(dt.flags(), 0);
728 assert_eq!(dt.typeobj().qualname().unwrap(), "float64");
729 assert_eq!(dt.char(), b'd');
730 assert_eq!(dt.kind(), b'f');
731 assert_eq!(dt.byteorder(), b'=');
732 assert_eq!(dt.is_native_byteorder(), Some(true));
733 assert_eq!(dt.itemsize(), 8);
734 assert_eq!(dt.alignment(), 8);
735 assert!(!dt.has_object());
736 assert!(dt.names().is_none());
737 assert!(!dt.has_fields());
738 assert!(!dt.is_aligned_struct());
739 assert!(!dt.has_subarray());
740 assert!(dt.base().is_equiv_to(&dt));
741 assert_eq!(dt.ndim(), 0);
742 assert_eq!(dt.shape(), Vec::<usize>::new());
743 });
744 }
745
746 #[test]
747 fn test_dtype_methods_subarray() {
748 Python::attach(|py| {
749 let locals = PyDict::new(py);
750 py_run!(
751 py,
752 *locals,
753 "dtype = __import__('numpy').dtype(('f8', (2, 3)))"
754 );
755 let dt = locals
756 .get_item("dtype")
757 .unwrap()
758 .unwrap()
759 .cast_into::<PyArrayDescr>()
760 .unwrap();
761
762 assert_eq!(dt.num(), NPY_TYPES::NPY_VOID as c_int);
763 assert_eq!(dt.flags(), 0);
764 assert_eq!(dt.typeobj().qualname().unwrap(), "void");
765 assert_eq!(dt.char(), b'V');
766 assert_eq!(dt.kind(), b'V');
767 assert_eq!(dt.byteorder(), b'|');
768 assert_eq!(dt.is_native_byteorder(), None);
769 assert_eq!(dt.itemsize(), 48);
770 assert_eq!(dt.alignment(), 8);
771 assert!(!dt.has_object());
772 assert!(dt.names().is_none());
773 assert!(!dt.has_fields());
774 assert!(!dt.is_aligned_struct());
775 assert!(dt.has_subarray());
776 assert_eq!(dt.ndim(), 2);
777 assert_eq!(dt.shape(), vec![2, 3]);
778 assert!(dt.base().is_equiv_to(&dtype::<f64>(py)));
779 });
780 }
781
782 #[test]
783 fn test_dtype_methods_record() {
784 Python::attach(|py| {
785 let locals = PyDict::new(py);
786 py_run!(
787 py,
788 *locals,
789 "dtype = __import__('numpy').dtype([('x', 'u1'), ('y', 'f8'), ('z', 'O')], align=True)"
790 );
791 let dt = locals
792 .get_item("dtype")
793 .unwrap()
794 .unwrap()
795 .cast_into::<PyArrayDescr>()
796 .unwrap();
797
798 assert_eq!(dt.num(), NPY_TYPES::NPY_VOID as c_int);
799 assert_ne!(dt.flags() & NPY_ITEM_HASOBJECT, 0);
800 assert_ne!(dt.flags() & NPY_NEEDS_PYAPI, 0);
801 assert_ne!(dt.flags() & NPY_ALIGNED_STRUCT, 0);
802 assert_eq!(dt.typeobj().qualname().unwrap(), "void");
803 assert_eq!(dt.char(), b'V');
804 assert_eq!(dt.kind(), b'V');
805 assert_eq!(dt.byteorder(), b'|');
806 assert_eq!(dt.is_native_byteorder(), None);
807 assert_eq!(dt.itemsize(), 24);
808 assert_eq!(dt.alignment(), 8);
809 assert!(dt.has_object());
810 assert_eq!(
811 dt.names(),
812 Some(vec!["x".to_owned(), "y".to_owned(), "z".to_owned()])
813 );
814 assert!(dt.has_fields());
815 assert!(dt.is_aligned_struct());
816 assert!(!dt.has_subarray());
817 assert_eq!(dt.ndim(), 0);
818 assert_eq!(dt.shape(), Vec::<usize>::new());
819 assert!(dt.base().is_equiv_to(&dt));
820 let x = dt.get_field("x").unwrap();
821 assert!(x.0.is_equiv_to(&dtype::<u8>(py)));
822 assert_eq!(x.1, 0);
823 let y = dt.get_field("y").unwrap();
824 assert!(y.0.is_equiv_to(&dtype::<f64>(py)));
825 assert_eq!(y.1, 8);
826 let z = dt.get_field("z").unwrap();
827 assert!(z.0.is_equiv_to(&dtype::<Py<PyAny>>(py)));
828 assert_eq!(z.1, 16);
829 });
830 }
831}