Skip to content

Commit 5886bc6

Browse files
committed
Merge pull request #715 from shoyer/setattr-hard-fail
Error when attempting to assign using attribute style syntax
2 parents 4be7088 + cad4722 commit 5886bc6

File tree

6 files changed

+41
-1
lines changed

6 files changed

+41
-1
lines changed

doc/whats-new.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ Bug fixes
6666

6767
- Fixes for several issues found on ``DataArray`` objects with the same name
6868
as one of their coordinates (see :ref:`v0.7.0.breaking` for more details).
69+
- Attempting to assign a ``Dataset`` or ``DataArray`` variable/attribute using
70+
attribute-style syntax (e.g., ``ds.foo = 42``) now raises an error rather
71+
than silently failing (:issue:`656`, :issue:`714`).
6972

7073
- ``DataArray.to_masked_array`` always returns masked array with mask being an array
7174
(not a scalar value) (:issue:`684`)

xray/core/common.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,10 @@ def _get_axis_num(self, dim):
117117

118118

119119
class AttrAccessMixin(object):
120-
"""Mixin class that allow getting keys with attribute access
120+
"""Mixin class that allows getting keys with attribute access
121121
"""
122+
_initialized = False
123+
122124
@property
123125
def _attr_sources(self):
124126
"""List of places to look-up items for attribute-style access"""
@@ -134,6 +136,20 @@ def __getattr__(self, name):
134136
raise AttributeError("%r object has no attribute %r" %
135137
(type(self).__name__, name))
136138

139+
def __setattr__(self, name, value):
140+
if self._initialized:
141+
try:
142+
# Allow setting instance variables if they already exist
143+
# (e.g., _attrs). We use __getattribute__ instead of hasattr
144+
# to avoid key lookups with attribute-style access.
145+
self.__getattribute__(name)
146+
except AttributeError:
147+
raise AttributeError(
148+
"cannot set attribute %r on a %r object. Use __setitem__ "
149+
"style assignment (e.g., `ds['name'] = ...`) instead to "
150+
"assign variables." % (name, type(self).__name__))
151+
object.__setattr__(self, name, value)
152+
137153
def __dir__(self):
138154
"""Provide method name lookup and completion. Only provide 'public'
139155
methods.

xray/core/dataarray.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ def __init__(self, data, coords=None, dims=None, name=None,
213213
self._variable = variable
214214
self._coords = coords
215215
self._name = name
216+
self._initialized = True
216217

217218
__default = object()
218219

xray/core/dataset.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ def __init__(self, data_vars=None, coords=None, attrs=None,
209209
self._set_init_vars_and_dims(data_vars, coords, compat)
210210
if attrs is not None:
211211
self.attrs = attrs
212+
self._initialized = True
212213

213214
def _add_missing_coords_inplace(self):
214215
"""Add missing coordinates to self._variables
@@ -370,6 +371,7 @@ def _construct_direct(cls, variables, coord_names, dims, attrs,
370371
obj._dims = dims
371372
obj._attrs = attrs
372373
obj._file_obj = file_obj
374+
obj._initialized = True
373375
return obj
374376

375377
__default_attrs = object()

xray/test/test_dataarray.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1558,3 +1558,12 @@ def test_real_and_imag(self):
15581558
array = DataArray(1 + 2j)
15591559
self.assertDataArrayIdentical(array.real, DataArray(1))
15601560
self.assertDataArrayIdentical(array.imag, DataArray(2))
1561+
1562+
def test_setattr_raises(self):
1563+
array = DataArray(0, coords={'scalar': 1}, attrs={'foo': 'bar'})
1564+
with self.assertRaisesRegexp(AttributeError, 'cannot set attr'):
1565+
array.scalar = 2
1566+
with self.assertRaisesRegexp(AttributeError, 'cannot set attr'):
1567+
array.foo = 2
1568+
with self.assertRaisesRegexp(AttributeError, 'cannot set attr'):
1569+
array.other = 2

xray/test/test_dataset.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2289,3 +2289,12 @@ def test_real_and_imag(self):
22892289

22902290
expected_im = Dataset({'x': ((), 2, attrs)}, attrs=attrs)
22912291
self.assertDatasetIdentical(ds.imag, expected_im)
2292+
2293+
def test_setattr_raises(self):
2294+
ds = Dataset({}, coords={'scalar': 1}, attrs={'foo': 'bar'})
2295+
with self.assertRaisesRegexp(AttributeError, 'cannot set attr'):
2296+
ds.scalar = 2
2297+
with self.assertRaisesRegexp(AttributeError, 'cannot set attr'):
2298+
ds.foo = 2
2299+
with self.assertRaisesRegexp(AttributeError, 'cannot set attr'):
2300+
ds.other = 2

0 commit comments

Comments
 (0)