diff --git a/doc/source/whatsnew/v0.21.0.txt b/doc/source/whatsnew/v0.21.0.txt index e4fdec4a22f84c..bb1b45d8fd2f8f 100644 --- a/doc/source/whatsnew/v0.21.0.txt +++ b/doc/source/whatsnew/v0.21.0.txt @@ -646,6 +646,7 @@ PyPy Other ^^^^^ +- Bug where some inplace operators were not being wrapped and produced a copy when invoked (:issue:`12962`) - Bug in :func:`eval` where the ``inplace`` parameter was being incorrectly handled (:issue:`16732`) <<<<<<< HEAD - The ``Series`` constructor with no arguments would have an index like ``Index([], dtype='object')`` instead of ``RangeIndex(start=0, stop=0, step=1)`` diff --git a/pandas/core/ops.py b/pandas/core/ops.py index 221f6ff8b92c68..d37acf48ed9c28 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -186,8 +186,10 @@ def add_special_arithmetic_methods(cls, arith_method=None, arith_method : function (optional) factory for special arithmetic methods, with op string: f(op, name, str_rep, default_axis=None, fill_zeros=None, **eval_kwargs) - comp_method : function, optional, + comp_method : function (optional) factory for rich comparison - signature: f(op, name, str_rep) + bool_method : function (optional) + factory for boolean methods - signature: f(op, name, str_rep) use_numexpr : bool, default True whether to accelerate with numexpr, defaults to True force : bool, default False @@ -234,9 +236,16 @@ def f(self, other): __isub__=_wrap_inplace_method(new_methods["__sub__"]), __imul__=_wrap_inplace_method(new_methods["__mul__"]), __itruediv__=_wrap_inplace_method(new_methods["__truediv__"]), - __ipow__=_wrap_inplace_method(new_methods["__pow__"]), )) + __ifloordiv__=_wrap_inplace_method(new_methods["__floordiv__"]), + __imod__=_wrap_inplace_method(new_methods["__mod__"]), + __ipow__=_wrap_inplace_method(new_methods["__pow__"]))) if not compat.PY3: - new_methods["__idiv__"] = new_methods["__div__"] + new_methods["__idiv__"] = _wrap_inplace_method(new_methods["__div__"]) + if bool_method: + new_methods.update( + dict(__iand__=_wrap_inplace_method(new_methods["__and__"]), + __ior__=_wrap_inplace_method(new_methods["__or__"]), + __ixor__=_wrap_inplace_method(new_methods["__xor__"]))) add_methods(cls, new_methods=new_methods, force=force, select=select, exclude=exclude) diff --git a/pandas/tests/frame/test_operators.py b/pandas/tests/frame/test_operators.py index 309c0f0244d7c8..10a9853b8a5b4f 100644 --- a/pandas/tests/frame/test_operators.py +++ b/pandas/tests/frame/test_operators.py @@ -1167,6 +1167,33 @@ def test_inplace_ops_identity(self): assert_frame_equal(df2, expected) assert df._data is df2._data + @pytest.mark.parametrize('op', ['add', 'and', 'div', 'floordiv', 'mod', + 'mul', 'or', 'pow', 'sub', 'truediv', + 'xor']) + def test_inplace_ops_identity2(self, op): + + if compat.PY3 and op == 'div': + return + + df = DataFrame({'a': [1., 2., 3.], + 'b': [1, 2, 3]}) + + operand = 2 + if op in ('and', 'or', 'xor'): + # cannot use floats for boolean ops + df['a'] = [True, False, True] + + df_copy = df.copy() + iop = '__i{}__'.format(op) + op = '__{}__'.format(op) + + # no id change and value is correct + getattr(df, iop)(operand) + expected = getattr(df_copy, op)(operand) + assert_frame_equal(df, expected) + expected = id(df) + assert id(df) == expected + def test_alignment_non_pandas(self): index = ['A', 'B', 'C'] columns = ['X', 'Y', 'Z']