From 2bf6d1841e285f912d19e2a4343fb1bd1e474c6b Mon Sep 17 00:00:00 2001 From: Jake VanderPlas Date: Thu, 4 Dec 2014 14:20:24 -0800 Subject: [PATCH 1/3] update NDFrame __setattr__ to match behavior of __getattr__ --- pandas/core/generic.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index edea0b33e5b4f..d63643c53e6f4 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -1929,11 +1929,12 @@ def __finalize__(self, other, method=None, **kwargs): return self def __getattr__(self, name): - """After regular attribute access, try looking up the name of a the - info. - + """After regular attribute access, try looking up the name This allows simpler access to columns for interactive use. """ + # Note: obj.x will always call obj.__getattribute__('x') prior to + # calling obj.__getattr__('x'). + if name in self._internal_names_set: return object.__getattribute__(self, name) elif name in self._metadata: @@ -1945,12 +1946,24 @@ def __getattr__(self, name): (type(self).__name__, name)) def __setattr__(self, name, value): - """After regular attribute access, try looking up the name of the info + """After regular attribute access, try setting the name This allows simpler access to columns for interactive use.""" + # first try regular attribute access via __getattribute__, so that + # e.g. ``obj.x`` and ``obj.x = 4`` will always reference/modify + # the same attribute. + + try: + object.__getattribute__(self, name) + return object.__setattr__(self, name, value) + except AttributeError: + pass + + # if this fails, go on to more involved attribute setting + # (note that this matches __getattr__, above). if name in self._internal_names_set: object.__setattr__(self, name, value) elif name in self._metadata: - return object.__setattr__(self, name, value) + object.__setattr__(self, name, value) else: try: existing = getattr(self, name) From e7329062651fe2d8a8532cfaa3cf58236e4bbd76 Mon Sep 17 00:00:00 2001 From: Jake VanderPlas Date: Fri, 5 Dec 2014 03:11:07 -0800 Subject: [PATCH 2/3] TST: test setattr when attr and column have same name --- pandas/tests/test_generic.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pandas/tests/test_generic.py b/pandas/tests/test_generic.py index 5e78e8dc44bea..40d6d2151155a 100644 --- a/pandas/tests/test_generic.py +++ b/pandas/tests/test_generic.py @@ -19,6 +19,7 @@ assert_frame_equal, assert_panel_equal, assert_almost_equal, + assert_equal, ensure_clean) import pandas.util.testing as tm @@ -1316,6 +1317,18 @@ def test_tz_convert_and_localize(self): df = DataFrame(index=l0) df = getattr(df, fn)('US/Pacific', level=1) + def test_set_attribute(self): + # Test for consistent setattr behavior when an attribute and a column + # have the same name (Issue #8994) + df = DataFrame({'x':[1, 2, 3]}) + + df.y = 2 + df['y'] = [2, 4, 6] + df.y = 5 + + assert_equal(df.y, 5) + assert_series_equal(df['y'], Series([2, 4, 6])) + class TestPanel(tm.TestCase, Generic): _typ = Panel From 7387a5d42a5db221dbebce1bd8301704a6504bf0 Mon Sep 17 00:00:00 2001 From: Jake VanderPlas Date: Fri, 5 Dec 2014 07:08:10 -0800 Subject: [PATCH 3/3] add whatsnew entry for PR #9004 --- doc/source/whatsnew/v0.15.2.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/doc/source/whatsnew/v0.15.2.txt b/doc/source/whatsnew/v0.15.2.txt index a1a353980f7aa..47a82fdceb17b 100644 --- a/doc/source/whatsnew/v0.15.2.txt +++ b/doc/source/whatsnew/v0.15.2.txt @@ -61,6 +61,23 @@ API changes - Allow equality comparisons of Series with a categorical dtype and object dtype; previously these would raise ``TypeError`` (:issue:`8938`) +- Bug in ``NDFrame``: conflicting attribute/column names now behave consistently between getting and setting. Previously, when both a column and attribute named ``y`` existed, ``data.y`` would return the attribute, while ``data.y = z`` would update the column (:issue:`8994`) + + .. ipython:: python + + data = pd.DataFrame({'x':[1, 2, 3]}) + data.y = 2 + data['y'] = [2, 4, 6] + data.y = 5 # this assignment was inconsistent + + print(data.y) + # prior output : 2 + # 0.15.2 output: 5 + + print(data['y'].values) + # prior output : [5, 5, 5] + # 0.15.2 output: [2, 4, 6] + .. _whatsnew_0152.enhancements: Enhancements