diff --git a/numpy/core/src/multiarray/methods.c b/numpy/core/src/multiarray/methods.c index a908e648d669..6a121574bfcc 100644 --- a/numpy/core/src/multiarray/methods.c +++ b/numpy/core/src/multiarray/methods.c @@ -20,6 +20,7 @@ #include "item_selection.h" #include "conversion_utils.h" #include "shape.h" +#include "strfuncs.h" #include "methods.h" #include "alloc.h" @@ -2533,6 +2534,10 @@ NPY_NO_EXPORT PyMethodDef array_methods[] = { (PyCFunction) array_complex, METH_VARARGS, NULL}, + {"__format__", + (PyCFunction) array_format, + METH_VARARGS, NULL}, + #ifndef NPY_PY3K /* * While we could put these in `tp_sequence`, its' easier to define them diff --git a/numpy/core/src/multiarray/strfuncs.c b/numpy/core/src/multiarray/strfuncs.c index f7980ffe06c6..bb94eb9f305f 100644 --- a/numpy/core/src/multiarray/strfuncs.c +++ b/numpy/core/src/multiarray/strfuncs.c @@ -197,3 +197,31 @@ array_str(PyArrayObject *self) } return s; } + +NPY_NO_EXPORT PyObject * +array_format(PyArrayObject *self, PyObject *args) +{ + PyObject *format; + if (!PyArg_ParseTuple(args, "O:__format__", &format)) + return NULL; + + /* 0d arrays - forward to the scalar type */ + if (PyArray_NDIM(self) == 0) { + PyObject *item = PyArray_ToScalar(PyArray_DATA(self), self); + PyObject *res; + + if (item == NULL) { + return NULL; + } + res = PyObject_Format(item, format); + Py_DECREF(item); + return res; + } + /* Everything else - use the builtin */ + else { + return PyObject_CallMethod( + (PyObject *)&PyBaseObject_Type, "__format__", "OO", + (PyObject *)self, format + ); + } +} diff --git a/numpy/core/src/multiarray/strfuncs.h b/numpy/core/src/multiarray/strfuncs.h index 8e80897c2ccb..5dd661a20dc4 100644 --- a/numpy/core/src/multiarray/strfuncs.h +++ b/numpy/core/src/multiarray/strfuncs.h @@ -10,4 +10,7 @@ array_repr(PyArrayObject *self); NPY_NO_EXPORT PyObject * array_str(PyArrayObject *self); +NPY_NO_EXPORT PyObject * +array_format(PyArrayObject *self, PyObject *args); + #endif diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index 92fc21b83b2b..b1a6fbe449bd 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -7013,6 +7013,37 @@ def test_null_inside_ustring_array_is_truthy(self): assert_(a) +class TestFormat(object): + + def test_0d(self): + a = np.array(np.pi) + assert_equal('{:0.3g}'.format(a), '3.14') + assert_equal('{:0.3g}'.format(a[()]), '3.14') + + def test_1d_no_format(self): + a = np.array([np.pi]) + assert_equal('{}'.format(a), str(a)) + + def test_1d_format(self): + # until gh-5543, ensure that the behaviour matches what it used to be + a = np.array([np.pi]) + + def ret_and_exc(f, *args, **kwargs): + try: + return f(*args, **kwargs), None + except Exception as e: + # exceptions don't compare equal, so return type and args + # which do + return None, (type(e), e.args) + + # Could switch on python version here, but all we care about is + # that the behaviour hasn't changed + assert_equal( + ret_and_exc(object.__format__, a, '30'), + ret_and_exc('{:30}'.format, a) + ) + + class TestCTypes(object): def test_ctypes_is_available(self):