Skip to content

Commit 7d00e0a

Browse files
dcherianilan-gold
andauthored
Add back getattr for ExtensionArrays (#10278)
Co-authored-by: Ilan Gold <ilanbassgold@gmail.com>
1 parent 0535989 commit 7d00e0a

File tree

4 files changed

+29
-2
lines changed

4 files changed

+29
-2
lines changed

doc/whats-new.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ Deprecations
3737
Bug fixes
3838
~~~~~~~~~
3939

40+
- Allow accessing arbitrary attributes on Pandas ExtensionArrays.
41+
By `Deepak Cherian <https://github.com/dcherian>`_.
42+
4043

4144
Documentation
4245
~~~~~~~~~~~~~

xarray/core/extension_array.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from collections.abc import Callable, Sequence
44
from dataclasses import dataclass
5-
from typing import Generic, cast
5+
from typing import Any, Generic, cast
66

77
import numpy as np
88
import pandas as pd
@@ -142,3 +142,10 @@ def __array__(
142142
return np.asarray(self.array, dtype=dtype, copy=copy)
143143
else:
144144
return np.asarray(self.array, dtype=dtype)
145+
146+
def __getattr__(self, attr: str) -> Any:
147+
# with __deepcopy__ or __copy__, the object is first constructed and then the sub-objects are attached (see https://docs.python.org/3/library/copy.html)
148+
# Thus, if we didn't have `super().__getattribute__("array")` this method would call `self.array` (i.e., `getattr(self, "array")`) again while looking for `__setstate__`
149+
# (which is apparently the first thing sought in copy.copy from the under-construction copied object),
150+
# which would cause a recursion error since `array` is not present on the object when it is being constructed during `__{deep}copy__`.
151+
return getattr(super().__getattribute__("array"), attr)

xarray/tests/test_dataset.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ def test_repr(self) -> None:
297297
var1 (dim1, dim2) float64 576B -0.9891 -0.3678 1.288 ... -0.2116 0.364
298298
var2 (dim1, dim2) float64 576B 0.953 1.52 1.704 ... 0.1347 -0.6423
299299
var3 (dim3, dim1) float64 640B 0.4107 0.9941 0.1665 ... 0.716 1.555
300-
var4 (dim1) category 64B 'b' 'c' 'b' 'a' 'c' 'a' 'c' 'a'
300+
var4 (dim1) category 32B 'b' 'c' 'b' 'a' 'c' 'a' 'c' 'a'
301301
Attributes:
302302
foo: bar""".format(
303303
data["dim3"].dtype,

xarray/tests/test_duck_array_ops.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import datetime as dt
4+
import pickle
45
import warnings
56

67
import numpy as np
@@ -1094,3 +1095,19 @@ def test_extension_array_singleton_equality(categorical1):
10941095
def test_extension_array_repr(int1):
10951096
int_duck_array = PandasExtensionArray(int1)
10961097
assert repr(int1) in repr(int_duck_array)
1098+
1099+
1100+
def test_extension_array_attr():
1101+
array = pd.Categorical(["cat2", "cat1", "cat2", "cat3", "cat1"])
1102+
wrapped = PandasExtensionArray(array)
1103+
assert_array_equal(array.categories, wrapped.categories)
1104+
assert array.nbytes == wrapped.nbytes
1105+
1106+
roundtripped = pickle.loads(pickle.dumps(wrapped))
1107+
assert isinstance(roundtripped, PandasExtensionArray)
1108+
assert (roundtripped == wrapped).all()
1109+
1110+
interval_array = pd.arrays.IntervalArray.from_breaks([0, 1, 2, 3], closed="right")
1111+
wrapped = PandasExtensionArray(interval_array)
1112+
assert_array_equal(wrapped.left, interval_array.left, strict=True)
1113+
assert wrapped.closed == interval_array.closed

0 commit comments

Comments
 (0)