@@ -1619,6 +1619,8 @@ array_reduce(PyArrayObject *self, PyObject *NPY_UNUSED(args))
1619
1619
1620
1620
Notice because Python does not describe a mechanism to write
1621
1621
raw data to the pickle, this performs a copy to a string first
1622
+ This issue is now adressed in protocol 5, where a buffer is serialized
1623
+ instead of a string,
1622
1624
*/
1623
1625
1624
1626
state = PyTuple_New (5 );
@@ -1651,6 +1653,132 @@ array_reduce(PyArrayObject *self, PyObject *NPY_UNUSED(args))
1651
1653
return ret ;
1652
1654
}
1653
1655
1656
+ static PyObject *
1657
+ array_reduce_ex (PyArrayObject * self , PyObject * args )
1658
+ {
1659
+ int protocol ;
1660
+ PyObject * ret = NULL , * numeric_mod = NULL , * from_buffer_func = NULL ;
1661
+ PyObject * buffer_tuple = NULL , * pickle_module = NULL , * pickle_class = NULL ;
1662
+ PyObject * class_args = NULL , * class_args_tuple = NULL , * unused = NULL ;
1663
+ PyObject * subclass_array_reduce = NULL ;
1664
+ PyObject * buffer = NULL , * transposed_array = NULL ;
1665
+ PyArray_Descr * descr = NULL ;
1666
+ char order ;
1667
+
1668
+ if (PyArg_ParseTuple (args , "i" , & protocol )){
1669
+ descr = PyArray_DESCR (self );
1670
+ if ((protocol < 5 ) ||
1671
+ (!PyArray_IS_C_CONTIGUOUS ((PyArrayObject * )self ) &&
1672
+ !PyArray_IS_F_CONTIGUOUS ((PyArrayObject * )self )) ||
1673
+ PyDataType_FLAGCHK (descr , NPY_ITEM_HASOBJECT ) ||
1674
+ (PyType_IsSubtype (((PyObject * )self )-> ob_type , & PyArray_Type ) &&
1675
+ ((PyObject * )self )-> ob_type != & PyArray_Type ) ||
1676
+ PyDataType_ISUNSIZED (descr )) {
1677
+ /* The PickleBuffer class from version 5 of the pickle protocol
1678
+ * can only be used for arrays backed by a contiguous data buffer.
1679
+ * For all other cases we fallback to the generic array_reduce
1680
+ * method that involves using a temporary bytes allocation. However
1681
+ * we do not call array_reduce directly but instead lookup and call
1682
+ * the __reduce__ method to make sure that it's possible customize
1683
+ * pickling in sub-classes. */
1684
+ subclass_array_reduce = PyObject_GetAttrString ((PyObject * )self ,
1685
+ "__reduce__" );
1686
+ return PyObject_CallObject (subclass_array_reduce , unused );
1687
+ }
1688
+ else if (protocol == 5 ){
1689
+ ret = PyTuple_New (2 );
1690
+
1691
+ if (ret == NULL ) {
1692
+ return NULL ;
1693
+ }
1694
+
1695
+ /* if the python version is below 3.8, the pickle module does not provide
1696
+ * built-in support for protocol 5. We try importing the pickle5
1697
+ * backport instead */
1698
+ #if PY_VERSION_HEX >= 0x03080000
1699
+ pickle_module = PyImport_ImportModule ("pickle" );
1700
+ #elif PY_VERSION_HEX < 0x03080000 && PY_VERSION_HEX >= 0x03060000
1701
+ pickle_module = PyImport_ImportModule ("pickle5" );
1702
+ if (pickle_module == NULL ){
1703
+ /* for protocol 5, raise a clear ImportError if pickle5 is not found
1704
+ */
1705
+ PyErr_SetString (PyExc_ImportError , "Using pickle protocol 5 "
1706
+ "requires the pickle5 module for python versions >=3.6 "
1707
+ "and <3.8" );
1708
+ return NULL ;
1709
+ }
1710
+ #else
1711
+ PyErr_SetString (PyExc_ValueError , "pickle protocol 5 is not available "
1712
+ "for python versions < 3.6" );
1713
+ return NULL ;
1714
+ #endif
1715
+ if (pickle_module == NULL ){
1716
+ return NULL ;
1717
+ }
1718
+
1719
+ pickle_class = PyObject_GetAttrString (pickle_module ,
1720
+ "PickleBuffer" );
1721
+
1722
+ class_args_tuple = PyTuple_New (1 );
1723
+ if (!PyArray_IS_C_CONTIGUOUS ((PyArrayObject * )self ) &&
1724
+ PyArray_IS_F_CONTIGUOUS ((PyArrayObject * )self )){
1725
+
1726
+ /* if the array if Fortran-contiguous and not C-contiguous,
1727
+ * the PickleBuffer instance will hold a view on the transpose
1728
+ * of the initial array, that is C-contiguous. */
1729
+ order = 'F' ;
1730
+ transposed_array = PyArray_Transpose ((PyArrayObject * )self , NULL );
1731
+ PyTuple_SET_ITEM (class_args_tuple , 0 , transposed_array );
1732
+ }
1733
+ else {
1734
+ order = 'C' ;
1735
+ PyTuple_SET_ITEM (class_args_tuple , 0 , (PyObject * )self );
1736
+ Py_INCREF (self );
1737
+ }
1738
+
1739
+ class_args = Py_BuildValue ("O" , class_args_tuple );
1740
+
1741
+ buffer = PyObject_CallObject (pickle_class , class_args );
1742
+
1743
+ numeric_mod = PyImport_ImportModule ("numpy.core.numeric" );
1744
+ if (numeric_mod == NULL ) {
1745
+ Py_DECREF (ret );
1746
+ return NULL ;
1747
+ }
1748
+ from_buffer_func = PyObject_GetAttrString (numeric_mod ,
1749
+ "_frombuffer" );
1750
+ Py_DECREF (numeric_mod );
1751
+
1752
+ Py_INCREF (descr );
1753
+
1754
+ buffer_tuple = PyTuple_New (4 );
1755
+ PyTuple_SET_ITEM (buffer_tuple , 0 , buffer );
1756
+ PyTuple_SET_ITEM (buffer_tuple , 1 , (PyObject * )descr );
1757
+ PyTuple_SET_ITEM (buffer_tuple , 2 ,
1758
+ PyObject_GetAttrString ((PyObject * )self ,
1759
+ "shape" ));
1760
+ PyTuple_SET_ITEM (buffer_tuple , 3 ,
1761
+ PyUnicode_FromStringAndSize (& order ,
1762
+ (Py_ssize_t )1 ));
1763
+
1764
+ PyTuple_SET_ITEM (ret , 0 , from_buffer_func );
1765
+ PyTuple_SET_ITEM (ret , 1 , buffer_tuple );
1766
+
1767
+ return ret ;
1768
+ }
1769
+ else {
1770
+ PyErr_Format (PyExc_ValueError ,
1771
+ "cannot call __reduce_ex__ with protocol >= %d" ,
1772
+ 5 );
1773
+ return NULL ;
1774
+ }
1775
+ }
1776
+ else {
1777
+ return NULL ;
1778
+ }
1779
+
1780
+ }
1781
+
1654
1782
static PyObject *
1655
1783
array_setstate (PyArrayObject * self , PyObject * args )
1656
1784
{
@@ -2524,6 +2652,9 @@ NPY_NO_EXPORT PyMethodDef array_methods[] = {
2524
2652
{"__reduce__" ,
2525
2653
(PyCFunction ) array_reduce ,
2526
2654
METH_VARARGS , NULL },
2655
+ {"__reduce_ex__" ,
2656
+ (PyCFunction ) array_reduce_ex ,
2657
+ METH_VARARGS , NULL },
2527
2658
{"__setstate__" ,
2528
2659
(PyCFunction ) array_setstate ,
2529
2660
METH_VARARGS , NULL },
0 commit comments