Skip to content

Commit

Permalink
allow customizing the inline repr of a duck array (#4248)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
keewis authored Aug 6, 2020
1 parent 98dc1f4 commit 7ba19e1
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 0 deletions.
32 changes: 32 additions & 0 deletions doc/internals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,38 @@ xarray objects via the (readonly) :py:attr:`Dataset.variables
<xarray.Dataset.variables>` and
:py:attr:`DataArray.variable <xarray.DataArray.variable>` attributes.

Duck arrays
-----------

.. warning::

This is a experimental feature.

xarray can wrap custom `duck array`_ objects as long as they define numpy's
``shape``, ``dtype`` and ``ndim`` properties and the ``__array__``,
``__array_ufunc__`` and ``__array_function__`` methods.

In certain situations (e.g. when printing the collapsed preview of
variables of a ``Dataset``), xarray will display the repr of a `duck array`_
in a single line, truncating it to a certain number of characters. If that
would drop too much information, the `duck array`_ may define a
``_repr_inline_`` method that takes ``max_width`` (number of characters) as an
argument:

.. code:: python
class MyDuckArray:
...
def _repr_inline_(self, max_width):
""" format to a single line with at most max_width characters """
...
...
.. _duck array: https://numpy.org/neps/nep-0022-ndarray-duck-typing-overview.html


Extending xarray
----------------

Expand Down
4 changes: 4 additions & 0 deletions doc/whats-new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ New Features
property for :py:class:`CFTimeIndex` and show ``calendar`` and ``length`` in
:py:meth:`CFTimeIndex.__repr__` (:issue:`2416`, :pull:`4092`)
`Aaron Spring <https://github.com/aaronspring>`_.
- Use a wrapped array's ``_repr_inline_`` method to construct the collapsed ``repr``
of :py:class:`DataArray` and :py:class:`Dataset` objects and
document the new method in :doc:`internals`. (:pull:`4248`).
By `Justus Magin <https://github.com/keewis>`_.


Bug fixes
Expand Down
2 changes: 2 additions & 0 deletions xarray/core/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,8 @@ def inline_variable_array_repr(var, max_width):
return inline_dask_repr(var.data)
elif isinstance(var._data, sparse_array_type):
return inline_sparse_repr(var.data)
elif hasattr(var._data, "_repr_inline_"):
return var._data._repr_inline_(max_width)
elif hasattr(var._data, "__array_function__"):
return maybe_truncate(repr(var._data).replace("\n", " "), max_width)
else:
Expand Down
39 changes: 39 additions & 0 deletions xarray/tests/test_formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import xarray as xr
from xarray.core import formatting
from xarray.core.npcompat import IS_NEP18_ACTIVE

from . import raises_regex

Expand Down Expand Up @@ -391,6 +392,44 @@ def test_array_repr(self):
assert actual == expected


@pytest.mark.skipif(not IS_NEP18_ACTIVE, reason="requires __array_function__")
def test_inline_variable_array_repr_custom_repr():
class CustomArray:
def __init__(self, value, attr):
self.value = value
self.attr = attr

def _repr_inline_(self, width):
formatted = f"({self.attr}) {self.value}"
if len(formatted) > width:
formatted = f"({self.attr}) ..."

return formatted

def __array_function__(self, *args, **kwargs):
return NotImplemented

@property
def shape(self):
return self.value.shape

@property
def dtype(self):
return self.value.dtype

@property
def ndim(self):
return self.value.ndim

value = CustomArray(np.array([20, 40]), "m")
variable = xr.Variable("x", value)

max_width = 10
actual = formatting.inline_variable_array_repr(variable, max_width=10)

assert actual == value._repr_inline_(max_width)


def test_set_numpy_options():
original_options = np.get_printoptions()
with formatting.set_numpy_options(threshold=10):
Expand Down

0 comments on commit 7ba19e1

Please sign in to comment.