Repository: pandas-dev/pandas

Issue:
__idiv__ creates a copy?
#### Code Sample, a copy-pastable example if possible

```
import pandas as pd
mylist = [pd.Series([1,2,3])]

for m in mylist:
    m /= 2.0
    print '***printing m***'
    print m
print
print '***mylist[0] - should be same as m, but isnt***'
print mylist[0]
```
#### observed output:

```
***printing m***
0    0.5
1    1.0
2    1.5
dtype: float64

***mylist[0] - should be same as m, but isnt***
0    1
1    2
2    3
dtype: int64
```
#### Expected Output

```
***printing m***
0    1
1    2
2    3
dtype: float64

***mylist[0] - should be same as m, but isnt***
0    1
1    2
2    3
dtype: int64
```
#### output of `pd.show_versions()`
## INSTALLED VERSIONS

commit: None
python: 2.7.11.final.0
python-bits: 64
OS: Darwin
OS-release: 14.5.0
machine: x86_64
processor: i386
byteorder: little
LC_ALL: None
LANG: None

pandas: 0.18.0
nose: None
pip: 8.1.1
setuptools: 20.6.7
Cython: None
numpy: 1.11.0
scipy: None
statsmodels: None
xarray: None
IPython: None
sphinx: None
patsy: None
dateutil: 2.5.2
pytz: 2016.3
blosc: None
bottleneck: None
tables: None
numexpr: None
matplotlib: None
openpyxl: None
xlrd: None
xlwt: None
xlsxwriter: None
lxml: None
bs4: None
html5lib: None
httplib2: None
apiclient: None
sqlalchemy: None
pymysql: None
psycopg2: None
jinja2: None
boto: None



Discussion:
I don't think the in-place ops (`__itruediv__`) are handled properly. This _should_ work with the compute happen, then the hanlder that sets inplace called (e.g. in `core/ops.py`). Also doubt many tests on this.
Of course this will work if the dtype doesn't change. When it changes is the issue.

You are welcome to debug!

oh good point - how should this be handled when the dtype changes?

it _should_ just work, but i don't think the correct methods are being called


Resolving patch:
diff --git a/pandas/core/ops.py b/pandas/core/ops.py
--- 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)


Test patch:
diff --git a/pandas/tests/frame/test_operators.py b/pandas/tests/frame/test_operators.py
--- a/pandas/tests/frame/test_operators.py
+++ b/pandas/tests/frame/test_operators.py
@@ -1161,6 +1161,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']