Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DEPR: Raise FutureWarning about raising an error in __array__ when copy=False cannot be honored #60395

Open
wants to merge 13 commits into
base: 2.3.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v2.3.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Other enhancements

- The semantics for the ``copy`` keyword in ``__array__`` methods (i.e. called
when using ``np.array()`` or ``np.asarray()`` on pandas objects) has been
updated to work correctly with NumPy >= 2 (:issue:`57739`)
updated to raise FutureWarning with NumPy >= 2 (:issue:`60340`)
- The :meth:`~Series.sum` reduction is now implemented for ``StringDtype`` columns (:issue:`59853`)
-

Expand Down
14 changes: 11 additions & 3 deletions pandas/core/arrays/arrow/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
cast,
)
import unicodedata
import warnings

import numpy as np

Expand All @@ -28,6 +29,7 @@
pa_version_under13p0,
)
from pandas.util._decorators import doc
from pandas.util._exceptions import find_stack_level
from pandas.util._validators import validate_fillna_kwargs

from pandas.core.dtypes.cast import (
Expand Down Expand Up @@ -663,9 +665,15 @@ def __array__(
) -> np.ndarray:
"""Correctly construct numpy arrays when passed to `np.asarray()`."""
if copy is False:
# TODO: By using `zero_copy_only` it may be possible to implement this
raise ValueError(
"Unable to avoid copy while creating an array as requested."
warnings.warn(
"Starting with NumPy 2.0, the behavior of the 'copy' keyword has "
"changed and passing 'copy=False' raises an error when returning "
"a zero-copy NumPy array is not possible. pandas will follow "
"this behavior starting with pandas 3.0.\nThis conversion to "
"NumPy requires a copy, but 'copy=False' was passed. Consider "
"using 'np.asarray(..)' instead.",
FutureWarning,
stacklevel=find_stack_level(),
)
elif copy is None:
# `to_numpy(copy=False)` has the meaning of NumPy `copy=None`.
Expand Down
11 changes: 9 additions & 2 deletions pandas/core/arrays/categorical.py
Original file line number Diff line number Diff line change
Expand Up @@ -1672,8 +1672,15 @@ def __array__(
array(['a', 'b'], dtype=object)
"""
if copy is False:
raise ValueError(
"Unable to avoid copy while creating an array as requested."
warnings.warn(
"Starting with NumPy 2.0, the behavior of the 'copy' keyword has "
"changed and passing 'copy=False' raises an error when returning "
"a zero-copy NumPy array is not possible. pandas will follow "
"this behavior starting with pandas 3.0.\nThis conversion to "
"NumPy requires a copy, but 'copy=False' was passed. Consider "
"using 'np.asarray(..)' instead.",
FutureWarning,
stacklevel=find_stack_level(),
)

ret = take_nd(self.categories._values, self._codes)
Expand Down
12 changes: 10 additions & 2 deletions pandas/core/arrays/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,9 +359,17 @@ def __array__(
# used for Timedelta/DatetimeArray, overwritten by PeriodArray
if is_object_dtype(dtype):
if copy is False:
raise ValueError(
"Unable to avoid copy while creating an array as requested."
warnings.warn(
"Starting with NumPy 2.0, the behavior of the 'copy' keyword has "
"changed and passing 'copy=False' raises an error when returning "
"a zero-copy NumPy array is not possible. pandas will follow this "
"behavior starting with pandas 3.0.\nThis conversion to NumPy "
"requires a copy, but 'copy=False' was passed. Consider using "
"'np.asarray(..)' instead.",
FutureWarning,
stacklevel=find_stack_level(),
)

return np.array(list(self), dtype=object)

if copy is True:
Expand Down
12 changes: 10 additions & 2 deletions pandas/core/arrays/interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from pandas.compat.numpy import function as nv
from pandas.errors import IntCastingNaNError
from pandas.util._decorators import Appender
from pandas.util._exceptions import find_stack_level

from pandas.core.dtypes.cast import (
LossySetitemError,
Expand Down Expand Up @@ -1575,8 +1576,15 @@ def __array__(
objects (with dtype='object')
"""
if copy is False:
raise ValueError(
"Unable to avoid copy while creating an array as requested."
warnings.warn(
"Starting with NumPy 2.0, the behavior of the 'copy' keyword has "
"changed and passing 'copy=False' raises an error when returning "
"a zero-copy NumPy array is not possible. pandas will follow "
"this behavior starting with pandas 3.0.\nThis conversion to "
"NumPy requires a copy, but 'copy=False' was passed. Consider "
"using 'np.asarray(..)' instead.",
FutureWarning,
stacklevel=find_stack_level(),
)

left = self._left
Expand Down
13 changes: 11 additions & 2 deletions pandas/core/arrays/masked.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
)
from pandas.errors import AbstractMethodError
from pandas.util._decorators import doc
from pandas.util._exceptions import find_stack_level
from pandas.util._validators import validate_fillna_kwargs

from pandas.core.dtypes.base import ExtensionDtype
Expand Down Expand Up @@ -604,8 +605,16 @@ def __array__(
if not self._hasna:
# special case, here we can simply return the underlying data
return np.array(self._data, dtype=dtype, copy=copy)
raise ValueError(
"Unable to avoid copy while creating an array as requested."

warnings.warn(
"Starting with NumPy 2.0, the behavior of the 'copy' keyword has "
"changed and passing 'copy=False' raises an error when returning "
"a zero-copy NumPy array is not possible. pandas will follow "
"this behavior starting with pandas 3.0.\nThis conversion to "
"NumPy requires a copy, but 'copy=False' was passed. Consider "
"using 'np.asarray(..)' instead.",
FutureWarning,
stacklevel=find_stack_level(),
)

if copy is None:
Expand Down
11 changes: 9 additions & 2 deletions pandas/core/arrays/period.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,8 +415,15 @@ def __array__(
return np.array(self.asi8, dtype=dtype)

if copy is False:
raise ValueError(
"Unable to avoid copy while creating an array as requested."
warnings.warn(
"Starting with NumPy 2.0, the behavior of the 'copy' keyword has "
"changed and passing 'copy=False' raises an error when returning "
"a zero-copy NumPy array is not possible. pandas will follow "
"this behavior starting with pandas 3.0.\nThis conversion to "
"NumPy requires a copy, but 'copy=False' was passed. Consider "
"using 'np.asarray(..)' instead.",
FutureWarning,
stacklevel=find_stack_level(),
)

if dtype == bool:
Expand Down
11 changes: 9 additions & 2 deletions pandas/core/arrays/sparse/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,8 +562,15 @@ def __array__(
return self.sp_values

if copy is False:
raise ValueError(
"Unable to avoid copy while creating an array as requested."
warnings.warn(
"Starting with NumPy 2.0, the behavior of the 'copy' keyword has "
"changed and passing 'copy=False' raises an error when returning "
"a zero-copy NumPy array is not possible. pandas will follow "
"this behavior starting with pandas 3.0.\nThis conversion to "
"NumPy requires a copy, but 'copy=False' was passed. Consider "
"using 'np.asarray(..)' instead.",
FutureWarning,
stacklevel=find_stack_level(),
)

fill_value = self.fill_value
Expand Down
13 changes: 10 additions & 3 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2151,9 +2151,16 @@ def __array__(
) -> np.ndarray:
if copy is False and not self._mgr.is_single_block and not self.empty:
# check this manually, otherwise ._values will already return a copy
# and np.array(values, copy=False) will not raise an error
raise ValueError(
"Unable to avoid copy while creating an array as requested."
# and np.array(values, copy=False) will not raise a warning
warnings.warn(
"Starting with NumPy 2.0, the behavior of the 'copy' keyword has "
"changed and passing 'copy=False' raises an error when returning "
"a zero-copy NumPy array is not possible. pandas will follow "
"this behavior starting with pandas 3.0.\nThis conversion to "
"NumPy requires a copy, but 'copy=False' was passed. Consider "
"using 'np.asarray(..)' instead.",
FutureWarning,
stacklevel=find_stack_level(),
)
values = self._values
if copy is None:
Expand Down
11 changes: 9 additions & 2 deletions pandas/core/indexes/multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -1314,8 +1314,15 @@ def __array__(self, dtype=None, copy=None) -> np.ndarray:
"""the array interface, return my values"""
if copy is False:
# self.values is always a newly construct array, so raise.
raise ValueError(
"Unable to avoid copy while creating an array as requested."
warnings.warn(
"Starting with NumPy 2.0, the behavior of the 'copy' keyword has "
"changed and passing 'copy=False' raises an error when returning "
"a zero-copy NumPy array is not possible. pandas will follow "
"this behavior starting with pandas 3.0.\nThis conversion to "
"NumPy requires a copy, but 'copy=False' was passed. Consider "
"using 'np.asarray(..)' instead.",
FutureWarning,
stacklevel=find_stack_level(),
)
if copy is True:
# explicit np.array call to ensure a copy is made and unique objects
Expand Down
4 changes: 2 additions & 2 deletions pandas/tests/arrays/sparse/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,8 +500,8 @@ def test_array_interface(arr_data, arr):
# copy=False semantics are only supported in NumPy>=2.
return

# for sparse arrays, copy=False is never allowed
with pytest.raises(ValueError, match="Unable to avoid copy while creating"):
msg = "Starting with NumPy 2.0, the behavior of the 'copy' keyword has changed"
with tm.assert_produces_warning(FutureWarning, match=msg):
np.array(arr, copy=False)

# except when there are actually no sparse filled values
Expand Down
4 changes: 2 additions & 2 deletions pandas/tests/base/test_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,8 +380,8 @@ def test_to_numpy(arr, expected, zero_copy, index_or_series_or_array):
return

if not zero_copy:
with pytest.raises(ValueError, match="Unable to avoid copy while creating"):
# An error is always acceptable for `copy=False`
msg = "Starting with NumPy 2.0, the behavior of the 'copy' keyword has changed"
with tm.assert_produces_warning(FutureWarning, match=msg):
np.array(thing, copy=False)

else:
Expand Down
3 changes: 2 additions & 1 deletion pandas/tests/copy_view/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ def test_dataframe_multiple_numpy_dtypes():
if np_version_gt2:
# copy=False semantics are only supported in NumPy>=2.

with pytest.raises(ValueError, match="Unable to avoid copy while creating"):
msg = "Starting with NumPy 2.0, the behavior of the 'copy' keyword has changed"
with pytest.raises(FutureWarning, match=msg):
arr = np.array(df, copy=False)

arr = np.array(df, copy=True)
Expand Down
6 changes: 2 additions & 4 deletions pandas/tests/extension/base/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,9 @@ def test_array_interface_copy(self, data):
# copy=False semantics are only supported in NumPy>=2.
return

try:
msg = "Starting with NumPy 2.0, the behavior of the 'copy' keyword has changed"
with tm.assert_produces_warning(FutureWarning, match=msg):
result_nocopy1 = np.array(data, copy=False)
except ValueError:
# An error is always acceptable for `copy=False`
return

result_nocopy2 = np.array(data, copy=False)
# If copy=False was given and did not raise, these must share the same data
Expand Down
14 changes: 12 additions & 2 deletions pandas/tests/extension/json/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@
TYPE_CHECKING,
Any,
)
import warnings

import numpy as np

from pandas.util._exceptions import find_stack_level

from pandas.core.dtypes.cast import construct_1d_object_array_from_listlike
from pandas.core.dtypes.common import (
is_bool_dtype,
Expand Down Expand Up @@ -148,8 +151,15 @@ def __ne__(self, other):

def __array__(self, dtype=None, copy=None):
if copy is False:
raise ValueError(
"Unable to avoid copy while creating an array as requested."
warnings.warn(
"Starting with NumPy 2.0, the behavior of the 'copy' keyword has "
"changed and passing 'copy=False' raises an error when returning "
"a zero-copy NumPy array is not possible. pandas will follow "
"this behavior starting with pandas 3.0.\nThis conversion to "
"NumPy requires a copy, but 'copy=False' was passed. Consider "
"using 'np.asarray(..)' instead.",
FutureWarning,
stacklevel=find_stack_level(),
)

if dtype is None:
Expand Down
3 changes: 2 additions & 1 deletion pandas/tests/indexes/multi/test_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ def test_array_interface(idx):
return

# for MultiIndex, copy=False is never allowed
with pytest.raises(ValueError, match="Unable to avoid copy while creating"):
msg = "Starting with NumPy 2.0, the behavior of the 'copy' keyword has changed"
with tm.assert_produces_warning(FutureWarning, match=msg):
np.array(idx, copy=False)


Expand Down
Loading