Repository: pandas-dev/pandas
Pull request #61107 (2025-03-12 01:27:52)

Backport PR #61085 on branch 2.3.x (DEPS: Update NumpyExtensionArray repr for NEP51)
Backport PR #61085: DEPS: Update NumpyExtensionArray repr for NEP51


Patch:
diff --git a/doc/source/whatsnew/v2.3.0.rst b/doc/source/whatsnew/v2.3.0.rst
index db3dcb50ba..fc60789801 100644
--- a/doc/source/whatsnew/v2.3.0.rst
+++ b/doc/source/whatsnew/v2.3.0.rst
@@ -37,6 +37,7 @@ Other enhancements
   updated to raise FutureWarning with NumPy >= 2 (:issue:`60340`)
 - :meth:`Series.str.decode` result now has ``StringDtype`` when ``future.infer_string`` is True (:issue:`60709`)
 - :meth:`~Series.to_hdf` and :meth:`~DataFrame.to_hdf` now round-trip with ``StringDtype``  (:issue:`60663`)
+- Improved ``repr`` of :class:`.NumpyExtensionArray` to account for NEP51 (:issue:`61085`)
 - The :meth:`Series.str.decode` has gained the argument ``dtype`` to control the dtype of the result (:issue:`60940`)
 - The :meth:`~Series.cumsum`, :meth:`~Series.cummin`, and :meth:`~Series.cummax` reductions are now implemented for ``StringDtype`` columns (:issue:`60633`)
 - The :meth:`~Series.sum` reduction is now implemented for ``StringDtype`` columns (:issue:`59853`)
diff --git a/pandas/core/arrays/numpy_.py b/pandas/core/arrays/numpy_.py
index 07fa6254d8..e0031d3db6 100644
--- a/pandas/core/arrays/numpy_.py
+++ b/pandas/core/arrays/numpy_.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 from typing import (
     TYPE_CHECKING,
+    Any,
     Literal,
 )
 
@@ -29,6 +30,8 @@ from pandas.core.construction import ensure_wrapped_if_datetimelike
 from pandas.core.strings.object_array import ObjectStringArrayMixin
 
 if TYPE_CHECKING:
+    from collections.abc import Callable
+
     from pandas._typing import (
         AxisInt,
         Dtype,
@@ -560,3 +563,12 @@ class NumpyExtensionArray(  # type: ignore[misc]
 
             return TimedeltaArray._simple_new(result, dtype=result.dtype)
         return type(self)(result)
+
+    def _formatter(self, boxed: bool = False) -> Callable[[Any], str | None]:
+        # NEP 51: https://github.com/numpy/numpy/pull/22449
+        if self.dtype.kind in "SU":
+            return "'{}'".format
+        elif self.dtype == "object":
+            return repr
+        else:
+            return str


Test patch:
diff --git a/pandas/tests/arrays/numpy_/test_numpy.py b/pandas/tests/arrays/numpy_/test_numpy.py
index 5112ce262f..f21fb4ccfb 100644
--- a/pandas/tests/arrays/numpy_/test_numpy.py
+++ b/pandas/tests/arrays/numpy_/test_numpy.py
@@ -21,7 +21,7 @@ from pandas.arrays import NumpyExtensionArray
         np.array([True, False], dtype=bool),
         np.array([0, 1], dtype="datetime64[ns]"),
         np.array([0, 1], dtype="timedelta64[ns]"),
-    ]
+    ],
 )
 def any_numpy_array(request):
     """
@@ -29,7 +29,7 @@ def any_numpy_array(request):
 
     This excludes string and bytes.
     """
-    return request.param
+    return request.param.copy()
 
 
 # ----------------------------------------------------------------------------
@@ -322,3 +322,30 @@ def test_factorize_unsigned():
     tm.assert_numpy_array_equal(res_codes, exp_codes)
 
     tm.assert_extension_array_equal(res_unique, NumpyExtensionArray(exp_unique))
+
+
+# ----------------------------------------------------------------------------
+# Output formatting
+
+
+def test_array_repr(any_numpy_array):
+    # GH#61085
+    nparray = any_numpy_array
+    arr = NumpyExtensionArray(nparray)
+    if nparray.dtype == "object":
+        values = "['a', 'b']"
+    elif nparray.dtype == "float64":
+        values = "[0.0, 1.0]"
+    elif str(nparray.dtype).startswith("int"):
+        values = "[0, 1]"
+    elif nparray.dtype == "complex128":
+        values = "[0j, (1+2j)]"
+    elif nparray.dtype == "bool":
+        values = "[True, False]"
+    elif nparray.dtype == "datetime64[ns]":
+        values = "[1970-01-01T00:00:00.000000000, 1970-01-01T00:00:00.000000001]"
+    elif nparray.dtype == "timedelta64[ns]":
+        values = "[0 nanoseconds, 1 nanoseconds]"
+    expected = f"<NumpyExtensionArray>\n{values}\nLength: 2, dtype: {nparray.dtype}"
+    result = repr(arr)
+    assert result == expected, f"{result} vs {expected}"