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 6 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
13 changes: 10 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,14 @@ 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 on NumPy 2.0, the behavior of the 'copy' keyword has changed "
"and passing 'copy=False' raises an error when a zero-copy NumPy array "
"is not possible, Pandas will follow this behavior starting with "
"version 3.0. This conversion to NumPy requires a copy, but "
"'copy=False' was passed. Consider using 'np.asarray(..)' instead.",
KevsterAmp marked this conversation as resolved.
Show resolved Hide resolved
FutureWarning,
stacklevel=find_stack_level(),
)
elif copy is None:
# `to_numpy(copy=False)` has the meaning of NumPy `copy=None`.
Expand Down
10 changes: 8 additions & 2 deletions pandas/core/arrays/categorical.py
Original file line number Diff line number Diff line change
Expand Up @@ -1672,8 +1672,14 @@ 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 on NumPy 2.0, the behavior of the 'copy' keyword has changed "
"and passing 'copy=False' raises an error when a zero-copy NumPy array "
"is not possible, Pandas will follow this behavior starting with "
"version 3.0. This 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 on NumPy 2.0, the behavior of the 'copy' keyword has "
"changed and passing 'copy=False' raises an error when a zero-copy "
"NumPy array is not possible, Pandas will follow this behavior "
"starting with version 3.0. This 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
11 changes: 9 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,14 @@ 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 on NumPy 2.0, the behavior of the 'copy' keyword has changed "
"and passing 'copy=False' raises an error when a zero-copy NumPy array "
"is not possible, Pandas will follow this behavior starting with "
"version 3.0. This 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
12 changes: 10 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,15 @@ 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 on NumPy 2.0, the behavior of the 'copy' keyword has changed "
"and passing 'copy=False' raises an error when a zero-copy NumPy array "
"is not possible, Pandas will follow this behavior starting with "
"version 3.0. This 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
10 changes: 8 additions & 2 deletions pandas/core/arrays/period.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,8 +415,14 @@ 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 on NumPy 2.0, the behavior of the 'copy' keyword has changed "
"and passing 'copy=False' raises an error when a zero-copy NumPy array "
"is not possible, Pandas will follow this behavior starting with "
"version 3.0. This 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
10 changes: 8 additions & 2 deletions pandas/core/arrays/sparse/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,8 +562,14 @@ 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 on NumPy 2.0, the behavior of the 'copy' keyword has changed "
"and passing 'copy=False' raises an error when a zero-copy NumPy array "
"is not possible, Pandas will follow this behavior starting with "
"version 3.0. This 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
12 changes: 9 additions & 3 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2151,9 +2151,15 @@ 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 on NumPy 2.0, the behavior of the 'copy' keyword has changed "
"and passing 'copy=False' raises an error when a zero-copy NumPy array "
"is not possible, Pandas will follow this behavior starting with "
"version 3.0. This 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
10 changes: 8 additions & 2 deletions pandas/core/indexes/multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -1314,8 +1314,14 @@ 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 on NumPy 2.0, the behavior of the 'copy' keyword has changed "
"and passing 'copy=False' raises an error when a zero-copy NumPy array "
"is not possible, Pandas will follow this behavior starting with "
"version 3.0. This 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
8 changes: 6 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,12 @@ 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 on NumPy 2.0, the behavior of the 'copy' keyword has changed "
"and passing 'copy=False' raises an error when a zero-copy NumPy array "
"is not possible, Pandas will follow this behavior starting with "
"version 3.0. This conversion to NumPy requires a copy, but "
"'copy=False' was passed. Consider using 'np.asarray(..)' instead."
with tm.assert_produces_warning(FutureWarning, match=msg):
np.array(arr, copy=False)

# except when there are actually no sparse filled values
Expand Down
8 changes: 6 additions & 2 deletions pandas/tests/base/test_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,8 +380,12 @@ 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 on NumPy 2.0, the behavior of the 'copy' keyword has changed "
"and passing 'copy=False' raises an error when a zero-copy NumPy array "
"is not possible, Pandas will follow this behavior starting with "
"version 3.0. This conversion to NumPy requires a copy, but "
"'copy=False' was passed. Consider using 'np.asarray(..)' instead."
with tm.assert_produces_warning(FutureWarning, match=msg):
np.array(thing, copy=False)

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

try:
msg = "Starting on NumPy 2.0, the behavior of the 'copy' keyword has changed "
"and passing 'copy=False' raises an error when a zero-copy NumPy array "
"is not possible, Pandas will follow this behavior starting with "
"version 3.0. This conversion to NumPy requires a copy, but "
"'copy=False' was passed. Consider using 'np.asarray(..)' instead."
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
13 changes: 11 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,14 @@ 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 on NumPy 2.0, the behavior of the 'copy' keyword has changed "
"and passing 'copy=False' raises an error when a zero-copy NumPy array "
"is not possible, Pandas will follow this behavior starting with "
"version 3.0. This 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
7 changes: 6 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,12 @@ 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 on NumPy 2.0, the behavior of the 'copy' keyword has changed "
"and passing 'copy=False' raises an error when a zero-copy NumPy array "
"is not possible, Pandas will follow this behavior starting with "
"version 3.0. This conversion to NumPy requires a copy, but "
"'copy=False' was passed. Consider using 'np.asarray(..)' instead."
with tm.assert_produces_warning(FutureWarning, match=msg):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine to just test a small part of the message, just so we assert the correct message is raised. Something like

msg = "Pandas will follow this behavior starting with version 3.0"
with tm.assert_produces_warning(FutureWarning, match=msg):
    ...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And for the other CI errors, essentially you will have to replace all occurrences of with pytest.raises(ValueError, match="Unable to avoid copy while creating") with this assert_produces_warning

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, will work on this in a couple of days. Been too focused on AoC this December 😆

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it alright if I match the first part of the message?

    msg = "Starting with NumPy 2.0, the behavior of the 'copy' keyword"
    with tm.assert_produces_warning(FutureWarning, match=msg):
        np.array(arr, copy=False)

Using

msg = "Pandas will follow this behavior starting with version 3.0"

is failing

np.array(idx, copy=False)


Expand Down
Loading