summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
authorMark Wiebe <mwiebe@enthought.com>2011-05-31 15:36:59 -0500
committerMark Wiebe <mwiebe@enthought.com>2011-05-31 15:36:59 -0500
commita17a4996e4ed63d1b855a0917fb5fcdd5855a7d0 (patch)
treeadf9fcdc2293e33a9d1369e05a593706f82003db /numpy
parent6441c2a788d0cc2a45c5e8a3ef0891ca4e42d96e (diff)
downloadnumpy-a17a4996e4ed63d1b855a0917fb5fcdd5855a7d0.tar.gz
ENH: core: Generalize ndarray.astype to take new standard keyword arguments
These include order=, casting=, subok=. Also added a forcecopy= parameter to allow skipping of the copy when possible.
Diffstat (limited to 'numpy')
-rw-r--r--numpy/add_newdocs.py28
-rw-r--r--numpy/core/src/multiarray/methods.c119
-rw-r--r--numpy/core/tests/test_api.py60
3 files changed, 197 insertions, 10 deletions
diff --git a/numpy/add_newdocs.py b/numpy/add_newdocs.py
index 37f326867..e596607fa 100644
--- a/numpy/add_newdocs.py
+++ b/numpy/add_newdocs.py
@@ -3001,14 +3001,38 @@ add_newdoc('numpy.core.multiarray', 'ndarray', ('argsort',
add_newdoc('numpy.core.multiarray', 'ndarray', ('astype',
"""
- a.astype(t)
+ a.astype(dtype, order='K', casting='unsafe', subok=True, forcecopy=True)
Copy of the array, cast to a specified type.
Parameters
----------
- t : str or dtype
+ dtype : str or dtype
Typecode or data-type to which the array is cast.
+ order : {'C', 'F', 'A', or 'K'}, optional
+ Controls the memory layout order of the result.
+ 'C' means C order, 'F' means Fortran order, 'A'
+ means 'F' order if all the arrays are Fortran contiguous,
+ 'C' order otherwise, and 'K' means as close to the
+ order the array elements appear in memory as possible.
+ Default is 'K'.
+ casting : {'no', 'equiv', 'safe', 'same_kind', 'unsafe'}, optional
+ Controls what kind of data casting may occur. Defaults to 'unsafe'
+ for backwards compatibility.
+
+ * 'no' means the data types should not be cast at all.
+ * 'equiv' means only byte-order changes are allowed.
+ * 'safe' means only casts which can preserve values are allowed.
+ * 'same_kind' means only safe casts or casts within a kind,
+ like float64 to float32, are allowed.
+ * 'unsafe' means any data conversions may be done.
+ subok : bool, optional
+ If True, then sub-classes will be passed-through (default), otherwise
+ the returned array will be forced to be a base-class array.
+ forcecopy : bool, optional
+ By default, astype always returns a new array. If this is set to
+ false, and the `dtype`, `order`, and `subok` requirements are
+ satisfied, the input array is returned instead of a copy.
Raises
------
diff --git a/numpy/core/src/multiarray/methods.c b/numpy/core/src/multiarray/methods.c
index a4c92b2a1..748360bf1 100644
--- a/numpy/core/src/multiarray/methods.c
+++ b/numpy/core/src/multiarray/methods.c
@@ -768,18 +768,121 @@ array_setasflat(PyArrayObject *self, PyObject *args)
Py_RETURN_NONE;
}
-static PyObject *
-array_astype(PyArrayObject *self, PyObject *args)
+static const char *
+npy_casting_to_string(NPY_CASTING casting)
{
- PyArray_Descr *descr = NULL;
+ switch (casting) {
+ case NPY_NO_CASTING:
+ return "'no'";
+ case NPY_EQUIV_CASTING:
+ return "'equiv'";
+ case NPY_SAFE_CASTING:
+ return "'safe'";
+ case NPY_SAME_KIND_CASTING:
+ return "'same_kind'";
+ case NPY_UNSAFE_CASTING:
+ return "'unsafe'";
+ default:
+ return "<unknown>";
+ }
+}
- if (!PyArg_ParseTuple(args, "O&", PyArray_DescrConverter,
- &descr)) {
- Py_XDECREF(descr);
+static PyObject *
+array_astype(PyArrayObject *self, PyObject *args, PyObject *kwds)
+{
+ static char *kwlist[] = {"dtype", "order", "casting",
+ "subok", "forcecopy", NULL};
+ PyArray_Descr *dtype = NULL;
+ /*
+ * TODO: UNSAFE default for compatibility, I think
+ * switching to SAME_KIND by default would be good.
+ */
+ NPY_CASTING casting = NPY_UNSAFE_CASTING;
+ NPY_ORDER order = NPY_KEEPORDER;
+ int forcecopy = 1, subok = 1;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|O&O&ii", kwlist,
+ PyArray_DescrConverter, &dtype,
+ PyArray_OrderConverter, &order,
+ PyArray_CastingConverter, &casting,
+ &subok,
+ &forcecopy)) {
+ Py_XDECREF(dtype);
return NULL;
}
- return PyArray_CastToType(self, descr, PyArray_ISFORTRAN(self));
+ /*
+ * If the memory layout matches and, data types are equivalent,
+ * and it's not a subtype if subok is False, then we
+ * can skip the copy.
+ */
+ if (!forcecopy && (order == NPY_KEEPORDER ||
+ (order == NPY_ANYORDER &&
+ (PyArray_IS_C_CONTIGUOUS(self) ||
+ PyArray_IS_F_CONTIGUOUS(self))) ||
+ (order == NPY_CORDER &&
+ PyArray_IS_C_CONTIGUOUS(self)) ||
+ (order == NPY_FORTRANORDER &&
+ PyArray_IS_F_CONTIGUOUS(self))) &&
+ (subok || PyArray_CheckExact(self)) &&
+ PyArray_EquivTypes(dtype, PyArray_DESCR(self))) {
+ Py_DECREF(dtype);
+ Py_INCREF(self);
+ return (PyObject *)self;
+ }
+ else if (PyArray_CanCastArrayTo(self, dtype, casting)) {
+ PyArrayObject *ret;
+
+ if (dtype->elsize == 0) {
+ PyArray_DESCR_REPLACE(dtype);
+ if (dtype == NULL) {
+ return NULL;
+ }
+
+ if (dtype->type_num == PyArray_DESCR(self)->type_num ||
+ dtype->type_num == NPY_VOID) {
+ dtype->elsize = PyArray_DESCR(self)->elsize;
+ }
+ else if (PyArray_DESCR(self)->type_num == NPY_STRING &&
+ dtype->type_num == NPY_UNICODE) {
+ dtype->elsize = PyArray_DESCR(self)->elsize * 4;
+ }
+ else if (PyArray_DESCR(self)->type_num == NPY_UNICODE &&
+ dtype->type_num == NPY_STRING) {
+ dtype->elsize = PyArray_DESCR(self)->elsize / 4;
+ }
+ }
+
+ /* This steals the reference to dtype, so no DECREF of dtype */
+ ret = (PyArrayObject *)PyArray_NewLikeArray(
+ self, order, dtype, subok);
+
+ if (ret == NULL) {
+ return NULL;
+ }
+ if (PyArray_CopyInto(ret, self) < 0) {
+ Py_DECREF(ret);
+ return NULL;
+ }
+
+ return (PyObject *)ret;
+ }
+ else {
+ PyObject *errmsg;
+ errmsg = PyUString_FromString("Cannot cast array from ");
+ PyUString_ConcatAndDel(&errmsg,
+ PyObject_Repr((PyObject *)PyArray_DESCR(self)));
+ PyUString_ConcatAndDel(&errmsg,
+ PyUString_FromString(" to "));
+ PyUString_ConcatAndDel(&errmsg,
+ PyObject_Repr((PyObject *)dtype));
+ PyUString_ConcatAndDel(&errmsg,
+ PyUString_FromFormat(" according to the rule %s",
+ npy_casting_to_string(casting)));
+ PyErr_SetObject(PyExc_TypeError, errmsg);
+ Py_DECREF(dtype);
+ return NULL;
+ }
}
/* default sub-type implementation */
@@ -2196,7 +2299,7 @@ NPY_NO_EXPORT PyMethodDef array_methods[] = {
METH_VARARGS | METH_KEYWORDS, NULL},
{"astype",
(PyCFunction)array_astype,
- METH_VARARGS, NULL},
+ METH_VARARGS | METH_KEYWORDS, NULL},
{"byteswap",
(PyCFunction)array_byteswap,
METH_VARARGS, NULL},
diff --git a/numpy/core/tests/test_api.py b/numpy/core/tests/test_api.py
index 255ef4565..388e011bd 100644
--- a/numpy/core/tests/test_api.py
+++ b/numpy/core/tests/test_api.py
@@ -24,5 +24,65 @@ def test_fastCopyAndTranspose():
assert_equal(b, a.T)
assert_(b.flags.owndata)
+def test_array_astype():
+ a = np.arange(6, dtype='f4').reshape(2,3)
+ # Default behavior: allows unsafe casts, keeps memory layout,
+ # always copies.
+ b = a.astype('i4')
+ assert_equal(a, b)
+ assert_equal(b.dtype, np.dtype('i4'))
+ assert_equal(a.strides, b.strides)
+ b = a.T.astype('i4')
+ assert_equal(a.T, b)
+ assert_equal(b.dtype, np.dtype('i4'))
+ assert_equal(a.T.strides, b.strides)
+ b = a.astype('f4')
+ assert_equal(a, b)
+ assert_(not (a is b))
+
+ # forcecopy=False parameter can sometimes skip a copy
+ b = a.astype('f4', forcecopy=False)
+ assert_(a is b)
+
+ # order parameter allows overriding of the memory layout,
+ # forcing a copy if the layout is wrong
+ b = a.astype('f4', order='F', forcecopy=False)
+ assert_equal(a, b)
+ assert_(not (a is b))
+ assert_(b.flags.f_contiguous)
+
+ b = a.astype('f4', order='C', forcecopy=False)
+ assert_equal(a, b)
+ assert_(a is b)
+ assert_(b.flags.c_contiguous)
+
+ # casting parameter allows catching bad casts
+ b = a.astype('c8', casting='safe')
+ assert_equal(a, b)
+ assert_equal(b.dtype, np.dtype('c8'))
+
+ assert_raises(TypeError, a.astype, 'i4', casting='safe')
+
+ # subok=False passes through a non-subclassed array
+ b = a.astype('f4', subok=0, forcecopy=False)
+ assert_(a is b)
+
+ a = np.matrix([[0,1,2],[3,4,5]], dtype='f4')
+
+ # subok=True passes through a matrix
+ b = a.astype('f4', subok=True, forcecopy=False)
+ assert_(a is b)
+
+ # subok=True is default, and creates a subtype on a cast
+ b = a.astype('i4', forcecopy=False)
+ assert_equal(a, b)
+ assert_equal(type(b), np.matrix)
+
+ # subok=False never returns a matrix
+ b = a.astype('f4', subok=False, forcecopy=False)
+ assert_equal(a, b)
+ assert_(not (a is b))
+ assert_(type(b) != np.matrix)
+
if __name__ == "__main__":
run_module_suite()