Skip to content

Commit 0e6ea5c

Browse files
authored
Warn instead of erroring when overriding attributes with accessors (#1149)
* Warn instead of erroring when overriding attributes with accessors Fixes GH1080, GH1082 CC smartass101 rabernat * use a custom warning class * grammar
1 parent d01077a commit 0e6ea5c

File tree

4 files changed

+34
-15
lines changed

4 files changed

+34
-15
lines changed

doc/internals.rst

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Extending xarray
5454
np.random.seed(123456)
5555
5656
xarray is designed as a general purpose library, and hence tries to avoid
57-
including overly domain specific methods. But inevitably, the need for more
57+
including overly domain specific functionality. But inevitably, the need for more
5858
domain specific logic arises.
5959

6060
One standard solution to this problem is to subclass Dataset and/or DataArray to
@@ -69,7 +69,11 @@ task, even if most methods are only forwarding to xarray implementations.
6969

7070
__ https://github.com/pydata/xarray/issues/706
7171

72-
To resolve this dilemma, xarray has the experimental
72+
If you simply want the ability to call a function with the syntax of a
73+
method call, then the builtin :py:meth:`~xarray.DataArray.pipe` method (copied
74+
from pandas) may suffice.
75+
76+
To resolve this issue for more complex cases, xarray has the
7377
:py:func:`~xarray.register_dataset_accessor` and
7478
:py:func:`~xarray.register_dataarray_accessor` decorators for adding custom
7579
"accessors" on xarray objects. Here's how you might use these decorators to
@@ -90,14 +94,17 @@ defined that returns an instance of your class:
9094
return GeoAccessor(self)
9195
9296
However, using the register accessor decorators is preferable to simply adding
93-
your own ad-hoc property (i.e., ``Dataset.geo = property(...)``), for two
97+
your own ad-hoc property (i.e., ``Dataset.geo = property(...)``), for several
9498
reasons:
9599

96-
1. It ensures that the name of your property does not conflict with any other
97-
attributes or methods.
100+
1. It ensures that the name of your property does not accidentally conflict with
101+
any other attributes or methods (including other accessors).
98102
2. Instances of accessor object will be cached on the xarray object that creates
99103
them. This means you can save state on them (e.g., to cache computed
100104
properties).
105+
3. Using an accessor provides an implicit namespace for your custom
106+
functionality that clearly identifies it as separate from built-in xarray
107+
methods.
101108

102109
Back in an interactive IPython session, we can use these properties:
103110

@@ -115,7 +122,9 @@ Back in an interactive IPython session, we can use these properties:
115122
116123
The intent here is that libraries that extend xarray could add such an accessor
117124
to implement subclass specific functionality rather than using actual subclasses
118-
or patching in a large number of domain specific methods.
125+
or patching in a large number of domain specific methods. For further reading
126+
on ways to write new accessors and the philosophy behind the approach, see
127+
:issue:`1080`.
119128

120129
To help users keep things straight, please `let us know
121130
<https://github.com/pydata/xarray/issues>`_ if you plan to write a new accessor

doc/whats-new.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ Enhancements
104104
- New top-level functions :py:func:`~xarray.full_like`,
105105
:py:func:`~xarray.zeros_like`, and :py:func:`~xarray.ones_like`
106106
By `Guido Imperiale <https://github.com/crusaderky>`_.
107+
- Overriding a preexisting attribute with
108+
:py:func:`~xarray.register_dataset_accessor` or
109+
:py:func:`~xarray.register_dataarray_accessor` now issues a warning instead of
110+
raising an error (:issue:`1082`).
111+
By `Stephan Hoyer <https://github.com/shoyer>`_.
107112

108113
Bug fixes
109114
~~~~~~~~~

xarray/core/extensions.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
from __future__ import division
33
from __future__ import print_function
44
import traceback
5+
import warnings
56

67
from .dataarray import DataArray
78
from .dataset import Dataset
89
from .pycompat import PY2
910

1011

11-
class AccessorRegistrationError(Exception):
12-
"""Exception for conflicts in accessor registration."""
12+
class AccessorRegistrationWarning(Warning):
13+
"""Warning for conflicts in accessor registration."""
1314

1415

1516
class _CachedAccessor(object):
@@ -43,10 +44,12 @@ def __get__(self, obj, cls):
4344
def _register_accessor(name, cls):
4445
def decorator(accessor):
4546
if hasattr(cls, name):
46-
raise AccessorRegistrationError(
47-
'cannot register accessor %r under name %r for type %r '
48-
'because an attribute with that name already exists.'
49-
% (accessor, name, cls))
47+
warnings.warn(
48+
'registration of accessor %r under name %r for type %r is '
49+
'overriding a preexisting attribute with the same name.'
50+
% (accessor, name, cls),
51+
AccessorRegistrationWarning,
52+
stacklevel=2)
5053
setattr(cls, name, _CachedAccessor(name, accessor))
5154
return accessor
5255
return decorator
@@ -58,7 +61,8 @@ def register_dataarray_accessor(name):
5861
Parameters
5962
----------
6063
name : str
61-
Name under which the accessor should be registered.
64+
Name under which the accessor should be registered. A warning is issued
65+
if this name conflicts with a preexisting attribute.
6266
6367
Examples
6468
--------
@@ -105,7 +109,8 @@ def register_dataset_accessor(name):
105109
Parameters
106110
----------
107111
name : str
108-
Name under which the property should be registered.
112+
Name under which the accessor should be registered. A warning is issued
113+
if this name conflicts with a preexisting attribute.
109114
110115
See also
111116
--------

xarray/test/test_extensions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def foo(self):
5252
del xr.Dataset.demo
5353
assert not hasattr(xr.Dataset, 'demo')
5454

55-
with self.assertRaises(xr.core.extensions.AccessorRegistrationError):
55+
with self.assertWarns('overriding a preexisting attribute'):
5656
@xr.register_dataarray_accessor('demo')
5757
class Foo(object):
5858
pass

0 commit comments

Comments
 (0)