Skip to content

Commit 7ba19e1

Browse files
authored
allow customizing the inline repr of a duck array (#4248)
* call the duck array's _repr_short_ method if it exists * rename to _repr_inline_ * add a crude test to make sure the object's _repr_inline_ is called * add a section about duck arrays * update whats-new.rst * fix a link * make sure the tests are not run without support for NEP18 * move the explanation for max_width into the text block * use double instead of single quotes * add back the docstring * link to internals in whats-new.rst
1 parent 98dc1f4 commit 7ba19e1

File tree

4 files changed

+77
-0
lines changed

4 files changed

+77
-0
lines changed

doc/internals.rst

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,38 @@ xarray objects via the (readonly) :py:attr:`Dataset.variables
4242
<xarray.Dataset.variables>` and
4343
:py:attr:`DataArray.variable <xarray.DataArray.variable>` attributes.
4444

45+
Duck arrays
46+
-----------
47+
48+
.. warning::
49+
50+
This is a experimental feature.
51+
52+
xarray can wrap custom `duck array`_ objects as long as they define numpy's
53+
``shape``, ``dtype`` and ``ndim`` properties and the ``__array__``,
54+
``__array_ufunc__`` and ``__array_function__`` methods.
55+
56+
In certain situations (e.g. when printing the collapsed preview of
57+
variables of a ``Dataset``), xarray will display the repr of a `duck array`_
58+
in a single line, truncating it to a certain number of characters. If that
59+
would drop too much information, the `duck array`_ may define a
60+
``_repr_inline_`` method that takes ``max_width`` (number of characters) as an
61+
argument:
62+
63+
.. code:: python
64+
65+
class MyDuckArray:
66+
...
67+
68+
def _repr_inline_(self, max_width):
69+
""" format to a single line with at most max_width characters """
70+
...
71+
72+
...
73+
74+
.. _duck array: https://numpy.org/neps/nep-0022-ndarray-duck-typing-overview.html
75+
76+
4577
Extending xarray
4678
----------------
4779

doc/whats-new.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ New Features
2929
property for :py:class:`CFTimeIndex` and show ``calendar`` and ``length`` in
3030
:py:meth:`CFTimeIndex.__repr__` (:issue:`2416`, :pull:`4092`)
3131
`Aaron Spring <https://github.com/aaronspring>`_.
32+
- Use a wrapped array's ``_repr_inline_`` method to construct the collapsed ``repr``
33+
of :py:class:`DataArray` and :py:class:`Dataset` objects and
34+
document the new method in :doc:`internals`. (:pull:`4248`).
35+
By `Justus Magin <https://github.com/keewis>`_.
3236

3337

3438
Bug fixes

xarray/core/formatting.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ def inline_variable_array_repr(var, max_width):
261261
return inline_dask_repr(var.data)
262262
elif isinstance(var._data, sparse_array_type):
263263
return inline_sparse_repr(var.data)
264+
elif hasattr(var._data, "_repr_inline_"):
265+
return var._data._repr_inline_(max_width)
264266
elif hasattr(var._data, "__array_function__"):
265267
return maybe_truncate(repr(var._data).replace("\n", " "), max_width)
266268
else:

xarray/tests/test_formatting.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import xarray as xr
99
from xarray.core import formatting
10+
from xarray.core.npcompat import IS_NEP18_ACTIVE
1011

1112
from . import raises_regex
1213

@@ -391,6 +392,44 @@ def test_array_repr(self):
391392
assert actual == expected
392393

393394

395+
@pytest.mark.skipif(not IS_NEP18_ACTIVE, reason="requires __array_function__")
396+
def test_inline_variable_array_repr_custom_repr():
397+
class CustomArray:
398+
def __init__(self, value, attr):
399+
self.value = value
400+
self.attr = attr
401+
402+
def _repr_inline_(self, width):
403+
formatted = f"({self.attr}) {self.value}"
404+
if len(formatted) > width:
405+
formatted = f"({self.attr}) ..."
406+
407+
return formatted
408+
409+
def __array_function__(self, *args, **kwargs):
410+
return NotImplemented
411+
412+
@property
413+
def shape(self):
414+
return self.value.shape
415+
416+
@property
417+
def dtype(self):
418+
return self.value.dtype
419+
420+
@property
421+
def ndim(self):
422+
return self.value.ndim
423+
424+
value = CustomArray(np.array([20, 40]), "m")
425+
variable = xr.Variable("x", value)
426+
427+
max_width = 10
428+
actual = formatting.inline_variable_array_repr(variable, max_width=10)
429+
430+
assert actual == value._repr_inline_(max_width)
431+
432+
394433
def test_set_numpy_options():
395434
original_options = np.get_printoptions()
396435
with formatting.set_numpy_options(threshold=10):

0 commit comments

Comments
 (0)