Skip to content

Commit 4419907

Browse files
Joe Jevnikjorisvandenbossche
Joe Jevnik
authored andcommitted
[Backport pandas-dev#14359] BUG: block mutation of read-only array in series
Author: Joe Jevnik <[email protected]> Closes pandas-dev#14359 from llllllllll/series-setitem and squashes the following commits: 9925327 [Joe Jevnik] BUG: fix a bug in Series.__setitem__ that allowed the mutatation of read-only arrays (cherry picked from commit 5cf6d94)
1 parent 6400cdd commit 4419907

File tree

5 files changed

+61
-7
lines changed

5 files changed

+61
-7
lines changed

doc/source/whatsnew/v0.19.1.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Bug Fixes
4646
- Bug in ``RangeIndex.intersection`` when result is a empty set (:issue:`14364`).
4747
- Bug in union of differences from a ``DatetimeIndex`; this is a regression in 0.19.0 from 0.18.1 (:issue:`14323`)
4848

49+
- Bug in ``Series.__setitem__`` which allowed mutating read-only arrays (:issue:`14359`).
4950

5051

5152
- Source installs from PyPI will now work without ``cython`` installed, as in previous versions (:issue:`14204`)
@@ -62,5 +63,4 @@ Bug Fixes
6263
- Bug in ``DataFrame.to_json`` where ``lines=True`` and a value contained a ``}`` character (:issue:`14391`)
6364
- Bug in ``df.groupby`` causing an ``AttributeError`` when grouping a single index frame by a column and the index level (:issue`14327`)
6465

65-
- Bug in ``pd.pivot_table`` may raise ``TypeError`` or ``ValueError`` when ``index`` or ``columns``
66-
is not scalar and ``values`` is not specified (:issue:`14380`)
66+
- Bug in ``pd.pivot_table`` may raise ``TypeError`` or ``ValueError`` when ``index`` or ``columns`` is not scalar and ``values`` is not specified (:issue:`14380`)

pandas/lib.pyx

+9-3
Original file line numberDiff line numberDiff line change
@@ -975,7 +975,9 @@ def astype_intsafe(ndarray[object] arr, new_dtype):
975975
if is_datelike and checknull(v):
976976
result[i] = NPY_NAT
977977
else:
978-
util.set_value_at(result, i, v)
978+
# we can use the unsafe version because we know `result` is mutable
979+
# since it was created from `np.empty`
980+
util.set_value_at_unsafe(result, i, v)
979981

980982
return result
981983

@@ -986,7 +988,9 @@ cpdef ndarray[object] astype_unicode(ndarray arr):
986988
ndarray[object] result = np.empty(n, dtype=object)
987989

988990
for i in range(n):
989-
util.set_value_at(result, i, unicode(arr[i]))
991+
# we can use the unsafe version because we know `result` is mutable
992+
# since it was created from `np.empty`
993+
util.set_value_at_unsafe(result, i, unicode(arr[i]))
990994

991995
return result
992996

@@ -997,7 +1001,9 @@ cpdef ndarray[object] astype_str(ndarray arr):
9971001
ndarray[object] result = np.empty(n, dtype=object)
9981002

9991003
for i in range(n):
1000-
util.set_value_at(result, i, str(arr[i]))
1004+
# we can use the unsafe version because we know `result` is mutable
1005+
# since it was created from `np.empty`
1006+
util.set_value_at_unsafe(result, i, str(arr[i]))
10011007

10021008
return result
10031009

pandas/src/util.pxd

+14-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,12 @@ cdef inline object get_value_at(ndarray arr, object loc):
7070

7171
return get_value_1d(arr, i)
7272

73-
cdef inline set_value_at(ndarray arr, object loc, object value):
73+
cdef inline set_value_at_unsafe(ndarray arr, object loc, object value):
74+
"""Sets a value into the array without checking the writeable flag.
75+
76+
This should be used when setting values in a loop, check the writeable
77+
flag above the loop and then eschew the check on each iteration.
78+
"""
7479
cdef:
7580
Py_ssize_t i, sz
7681
if is_float_object(loc):
@@ -87,6 +92,14 @@ cdef inline set_value_at(ndarray arr, object loc, object value):
8792

8893
assign_value_1d(arr, i, value)
8994

95+
cdef inline set_value_at(ndarray arr, object loc, object value):
96+
"""Sets a value into the array after checking that the array is mutable.
97+
"""
98+
if not cnp.PyArray_ISWRITEABLE(arr):
99+
raise ValueError('assignment destination is read-only')
100+
101+
set_value_at_unsafe(arr, loc, value)
102+
90103
cdef inline int is_contiguous(ndarray arr):
91104
return cnp.PyArray_CHKFLAGS(arr, cnp.NPY_C_CONTIGUOUS)
92105

pandas/tests/series/test_indexing.py

+34
Original file line numberDiff line numberDiff line change
@@ -1947,6 +1947,40 @@ def test_multilevel_preserve_name(self):
19471947
self.assertEqual(result.name, s.name)
19481948
self.assertEqual(result2.name, s.name)
19491949

1950+
def test_setitem_scalar_into_readonly_backing_data(self):
1951+
# GH14359: test that you cannot mutate a read only buffer
1952+
1953+
array = np.zeros(5)
1954+
array.flags.writeable = False # make the array immutable
1955+
series = Series(array)
1956+
1957+
for n in range(len(series)):
1958+
with self.assertRaises(ValueError):
1959+
series[n] = 1
1960+
1961+
self.assertEqual(
1962+
array[n],
1963+
0,
1964+
msg='even though the ValueError was raised, the underlying'
1965+
' array was still mutated!',
1966+
)
1967+
1968+
def test_setitem_slice_into_readonly_backing_data(self):
1969+
# GH14359: test that you cannot mutate a read only buffer
1970+
1971+
array = np.zeros(5)
1972+
array.flags.writeable = False # make the array immutable
1973+
series = Series(array)
1974+
1975+
with self.assertRaises(ValueError):
1976+
series[1:3] = 1
1977+
1978+
self.assertTrue(
1979+
not array.any(),
1980+
msg='even though the ValueError was raised, the underlying'
1981+
' array was still mutated!',
1982+
)
1983+
19501984
if __name__ == '__main__':
19511985
import nose
19521986
nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'],

setup.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,8 @@ def pxd(name):
476476
'pandas/src/period_helper.c']},
477477
index={'pyxfile': 'index',
478478
'sources': ['pandas/src/datetime/np_datetime.c',
479-
'pandas/src/datetime/np_datetime_strings.c']},
479+
'pandas/src/datetime/np_datetime_strings.c'],
480+
'pxdfiles': ['src/util']},
480481
algos={'pyxfile': 'algos',
481482
'pxdfiles': ['src/util'],
482483
'depends': _pxi_dep['algos']},

0 commit comments

Comments
 (0)