Skip to content

Commit 4ff1f3e

Browse files
API / CoW: constructing Series from Series creates lazy copy (with default copy=False) (#49524)
1 parent 1fc2213 commit 4ff1f3e

File tree

4 files changed

+84
-4
lines changed

4 files changed

+84
-4
lines changed

doc/source/whatsnew/v2.0.0.rst

+4
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ Copy-on-Write improvements
116116
returning multiple times an identical, cached Series object). This ensures that those
117117
Series objects correctly follow the Copy-on-Write rules (:issue:`49450`)
118118

119+
- The :class:`Series` constructor will now create a lazy copy (deferring the copy until
120+
a modification to the data happens) when constructing a Series from an existing
121+
Series with the default of ``copy=False`` (:issue:`50471`)
122+
119123
Copy-on-Write can be enabled through
120124

121125
.. code-block:: python

pandas/core/series.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -426,10 +426,14 @@ def __init__(
426426
elif isinstance(data, Series):
427427
if index is None:
428428
index = data.index
429+
if using_copy_on_write():
430+
data = data._mgr.copy(deep=False)
431+
else:
432+
data = data._mgr
429433
else:
430434
data = data.reindex(index, copy=copy)
431435
copy = False
432-
data = data._mgr
436+
data = data._mgr
433437
elif is_dict_like(data):
434438
data, index = self._init_dict(data, index, dtype)
435439
dtype = None
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import numpy as np
2+
3+
from pandas import Series
4+
5+
# -----------------------------------------------------------------------------
6+
# Copy/view behaviour for Series / DataFrame constructors
7+
8+
9+
def test_series_from_series(using_copy_on_write):
10+
# Case: constructing a Series from another Series object follows CoW rules:
11+
# a new object is returned and thus mutations are not propagated
12+
ser = Series([1, 2, 3], name="name")
13+
14+
# default is copy=False -> new Series is a shallow copy / view of original
15+
result = Series(ser)
16+
17+
# the shallow copy still shares memory
18+
assert np.shares_memory(ser.values, result.values)
19+
20+
if using_copy_on_write:
21+
assert result._mgr.refs is not None
22+
23+
if using_copy_on_write:
24+
# mutating new series copy doesn't mutate original
25+
result.iloc[0] = 0
26+
assert ser.iloc[0] == 1
27+
# mutating triggered a copy-on-write -> no longer shares memory
28+
assert not np.shares_memory(ser.values, result.values)
29+
else:
30+
# mutating shallow copy does mutate original
31+
result.iloc[0] = 0
32+
assert ser.iloc[0] == 0
33+
# and still shares memory
34+
assert np.shares_memory(ser.values, result.values)
35+
36+
# the same when modifying the parent
37+
result = Series(ser)
38+
39+
if using_copy_on_write:
40+
# mutating original doesn't mutate new series
41+
ser.iloc[0] = 0
42+
assert result.iloc[0] == 1
43+
else:
44+
# mutating original does mutate shallow copy
45+
ser.iloc[0] = 0
46+
assert result.iloc[0] == 0
47+
48+
49+
def test_series_from_series_with_reindex(using_copy_on_write):
50+
# Case: constructing a Series from another Series with specifying an index
51+
# that potentially requires a reindex of the values
52+
ser = Series([1, 2, 3], name="name")
53+
54+
# passing an index that doesn't actually require a reindex of the values
55+
# -> without CoW we get an actual mutating view
56+
for index in [
57+
ser.index,
58+
ser.index.copy(),
59+
list(ser.index),
60+
ser.index.rename("idx"),
61+
]:
62+
result = Series(ser, index=index)
63+
assert np.shares_memory(ser.values, result.values)
64+
result.iloc[0] = 0
65+
if using_copy_on_write:
66+
assert ser.iloc[0] == 1
67+
else:
68+
assert ser.iloc[0] == 0
69+
70+
# ensure that if an actual reindex is needed, we don't have any refs
71+
# (mutating the result wouldn't trigger CoW)
72+
result = Series(ser, index=[0, 1, 2, 3])
73+
assert not np.shares_memory(ser.values, result.values)
74+
if using_copy_on_write:
75+
assert result._mgr.refs is None or result._mgr.refs[0] is None

pandas/tests/copy_view/test_indexing.py

-3
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,3 @@ def test_dataframe_add_column_from_series():
883883
df.loc[2, "new"] = 100
884884
expected_s = Series([0, 11, 12])
885885
tm.assert_series_equal(s, expected_s)
886-
887-
888-
# TODO add tests for constructors

0 commit comments

Comments
 (0)