Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Warn instead of erroring when overriding attributes with accessors #1149

Merged
merged 3 commits into from
Dec 6, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions doc/internals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Extending xarray
np.random.seed(123456)

xarray is designed as a general purpose library, and hence tries to avoid
including overly domain specific methods. But inevitably, the need for more
including overly domain specific functionality. But inevitably, the need for more
domain specific logic arises.

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

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

To resolve this dilemma, xarray has the experimental
If you simply want the ability to call a function with the syntax of a
method call, then the builtin :py:meth:`~xarray.DataArray.pipe` method (copied
from pandas) may suffice.

To resolve this issue for more complex cases, xarray has the
:py:func:`~xarray.register_dataset_accessor` and
:py:func:`~xarray.register_dataarray_accessor` decorators for adding custom
"accessors" on xarray objects. Here's how you might use these decorators to
Expand All @@ -90,14 +94,17 @@ defined that returns an instance of your class:
return GeoAccessor(self)

However, using the register accessor decorators is preferable to simply adding
your own ad-hoc property (i.e., ``Dataset.geo = property(...)``), for two
your own ad-hoc property (i.e., ``Dataset.geo = property(...)``), for several
reasons:

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

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

Expand All @@ -115,7 +122,9 @@ Back in an interactive IPython session, we can use these properties:

The intent here is that libraries that extend xarray could add such an accessor
to implement subclass specific functionality rather than using actual subclasses
or patching in a large number of domain specific methods.
or patching in a large number of domain specific methods. For further reading
on ways to write new accessors and the philosophy behind the approach, see
:issue:`1080`.

To help users keep things straight, please `let us know
<https://github.com/pydata/xarray/issues>`_ if you plan to write a new accessor
Expand Down
5 changes: 5 additions & 0 deletions doc/whats-new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ Enhancements
- New top-level functions :py:func:`~xarray.full_like`,
:py:func:`~xarray.zeros_like`, and :py:func:`~xarray.ones_like`
By `Guido Imperiale <https://github.com/crusaderky>`_.
- Overriding a preexisting attribute with
:py:func:`~xarray.register_dataset_accessor` or
:py:func:`~xarray.register_dataarray_accessor` now issues a warning instead of
raising an error (:issue:`1082`).
By `Stephan Hoyer <https://github.com/shoyer>`_.

Bug fixes
~~~~~~~~~
Expand Down
21 changes: 13 additions & 8 deletions xarray/core/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
from __future__ import division
from __future__ import print_function
import traceback
import warnings

from .dataarray import DataArray
from .dataset import Dataset
from .pycompat import PY2


class AccessorRegistrationError(Exception):
"""Exception for conflicts in accessor registration."""
class AccessorRegistrationWarning(Warning):
"""Warning for conflicts in accessor registration."""


class _CachedAccessor(object):
Expand Down Expand Up @@ -43,10 +44,12 @@ def __get__(self, obj, cls):
def _register_accessor(name, cls):
def decorator(accessor):
if hasattr(cls, name):
raise AccessorRegistrationError(
'cannot register accessor %r under name %r for type %r '
'because an attribute with that name already exists.'
% (accessor, name, cls))
warnings.warn(
'registration of accessor %r under name %r for type %r is '
'overriding a preexisting attribute with the same name.'
% (accessor, name, cls),
AccessorRegistrationWarning,
stacklevel=2)
setattr(cls, name, _CachedAccessor(name, accessor))
return accessor
return decorator
Expand All @@ -58,7 +61,8 @@ def register_dataarray_accessor(name):
Parameters
----------
name : str
Name under which the accessor should be registered.
Name under which the accessor should be registered. A warning is issued
if this name conflicts with a preexisting attribute.

Examples
--------
Expand Down Expand Up @@ -105,7 +109,8 @@ def register_dataset_accessor(name):
Parameters
----------
name : str
Name under which the property should be registered.
Name under which the accessor should be registered. A warning is issued
if this name conflicts with a preexisting attribute.

See also
--------
Expand Down
2 changes: 1 addition & 1 deletion xarray/test/test_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def foo(self):
del xr.Dataset.demo
assert not hasattr(xr.Dataset, 'demo')

with self.assertRaises(xr.core.extensions.AccessorRegistrationError):
with self.assertWarns('overriding a preexisting attribute'):
@xr.register_dataarray_accessor('demo')
class Foo(object):
pass
Expand Down