Skip to content

Commit 9d0db60

Browse files
dkammjreback
authored andcommitted
BUG: wrap all supported inplace methods to avoid making a copy (#12962) (#17589)
1 parent d2b1668 commit 9d0db60

File tree

3 files changed

+40
-3
lines changed

3 files changed

+40
-3
lines changed

doc/source/whatsnew/v0.21.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -646,4 +646,5 @@ PyPy
646646

647647
Other
648648
^^^^^
649+
- Bug where some inplace operators were not being wrapped and produced a copy when invoked (:issue:`12962`)
649650
- Bug in :func:`eval` where the ``inplace`` parameter was being incorrectly handled (:issue:`16732`)

pandas/core/ops.py

+12-3
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,10 @@ def add_special_arithmetic_methods(cls, arith_method=None,
186186
arith_method : function (optional)
187187
factory for special arithmetic methods, with op string:
188188
f(op, name, str_rep, default_axis=None, fill_zeros=None, **eval_kwargs)
189-
comp_method : function, optional,
189+
comp_method : function (optional)
190190
factory for rich comparison - signature: f(op, name, str_rep)
191+
bool_method : function (optional)
192+
factory for boolean methods - signature: f(op, name, str_rep)
191193
use_numexpr : bool, default True
192194
whether to accelerate with numexpr, defaults to True
193195
force : bool, default False
@@ -234,9 +236,16 @@ def f(self, other):
234236
__isub__=_wrap_inplace_method(new_methods["__sub__"]),
235237
__imul__=_wrap_inplace_method(new_methods["__mul__"]),
236238
__itruediv__=_wrap_inplace_method(new_methods["__truediv__"]),
237-
__ipow__=_wrap_inplace_method(new_methods["__pow__"]), ))
239+
__ifloordiv__=_wrap_inplace_method(new_methods["__floordiv__"]),
240+
__imod__=_wrap_inplace_method(new_methods["__mod__"]),
241+
__ipow__=_wrap_inplace_method(new_methods["__pow__"])))
238242
if not compat.PY3:
239-
new_methods["__idiv__"] = new_methods["__div__"]
243+
new_methods["__idiv__"] = _wrap_inplace_method(new_methods["__div__"])
244+
if bool_method:
245+
new_methods.update(
246+
dict(__iand__=_wrap_inplace_method(new_methods["__and__"]),
247+
__ior__=_wrap_inplace_method(new_methods["__or__"]),
248+
__ixor__=_wrap_inplace_method(new_methods["__xor__"])))
240249

241250
add_methods(cls, new_methods=new_methods, force=force, select=select,
242251
exclude=exclude)

pandas/tests/frame/test_operators.py

+27
Original file line numberDiff line numberDiff line change
@@ -1167,6 +1167,33 @@ def test_inplace_ops_identity(self):
11671167
assert_frame_equal(df2, expected)
11681168
assert df._data is df2._data
11691169

1170+
@pytest.mark.parametrize('op', ['add', 'and', 'div', 'floordiv', 'mod',
1171+
'mul', 'or', 'pow', 'sub', 'truediv',
1172+
'xor'])
1173+
def test_inplace_ops_identity2(self, op):
1174+
1175+
if compat.PY3 and op == 'div':
1176+
return
1177+
1178+
df = DataFrame({'a': [1., 2., 3.],
1179+
'b': [1, 2, 3]})
1180+
1181+
operand = 2
1182+
if op in ('and', 'or', 'xor'):
1183+
# cannot use floats for boolean ops
1184+
df['a'] = [True, False, True]
1185+
1186+
df_copy = df.copy()
1187+
iop = '__i{}__'.format(op)
1188+
op = '__{}__'.format(op)
1189+
1190+
# no id change and value is correct
1191+
getattr(df, iop)(operand)
1192+
expected = getattr(df_copy, op)(operand)
1193+
assert_frame_equal(df, expected)
1194+
expected = id(df)
1195+
assert id(df) == expected
1196+
11701197
def test_alignment_non_pandas(self):
11711198
index = ['A', 'B', 'C']
11721199
columns = ['X', 'Y', 'Z']

0 commit comments

Comments
 (0)