Skip to content

Commit f4c859f

Browse files
fix(chumpy): resolve Ch.r as method, not callable ndarray
materialize and __array__ called value.r() while r was a property returning an ndarray. Use _resolve() internally, restore r() as a method, and handle unpickled instances that store r or x in __dict__ without __init__. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 0a5a8f5 commit f4c859f

1 file changed

Lines changed: 30 additions & 6 deletions

File tree

chumpy/ch.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,24 @@ def _resolve(self) -> np.ndarray:
2626
# Real chumpy Ch instances store the underlying ndarray on attribute ``x``;
2727
# legacy pickles unpickle by restoring ``__dict__`` without calling ``__init__``,
2828
# so try common attribute names before falling back to ``_data``.
29+
r = self.__dict__.get("r")
30+
if isinstance(r, np.ndarray):
31+
return np.asarray(r)
2932
for attr in ("x", "_x", "_data"):
3033
val = self.__dict__.get(attr)
3134
if val is not None:
3235
return np.asarray(val)
33-
if self._data is not None:
34-
return np.asarray(self._data)
36+
data = self.__dict__.get("_data")
37+
if data is not None:
38+
return np.asarray(data)
3539
return np.zeros((), dtype=np.float32)
3640

37-
@property
3841
def r(self) -> np.ndarray:
42+
"""Match real chumpy API (``ch.r()`` returns the underlying array)."""
3943
return self._resolve()
4044

4145
def __array__(self, dtype=None):
42-
arr = self.r()
46+
arr = self._resolve()
4347
if dtype is not None:
4448
arr = arr.astype(dtype, copy=False)
4549
return arr
@@ -49,10 +53,30 @@ class ChArray(np.ndarray):
4953
"""Minimal stand-in for ``chumpy.ch.ChArray``."""
5054

5155

56+
def _unwrap_ch(value, dtype=np.float32) -> np.ndarray:
57+
"""Resolve a chumpy ``Ch`` (method, property, or unpickled ``r``/``x`` attrs) to ndarray."""
58+
if isinstance(value, Ch):
59+
return np.asarray(value._resolve(), dtype=dtype)
60+
r = getattr(value, "r", None)
61+
if isinstance(r, np.ndarray):
62+
return np.asarray(r, dtype=dtype)
63+
if callable(r):
64+
return np.asarray(r(), dtype=dtype)
65+
for attr in ("x", "_x", "_data"):
66+
val = getattr(value, attr, None)
67+
if val is not None:
68+
return np.asarray(val, dtype=dtype)
69+
raise TypeError(f"Cannot materialize chumpy-like object: {type(value)!r}")
70+
71+
5272
def materialize(value, dtype=np.float32) -> np.ndarray:
5373
"""Recursively unwrap ``Ch`` / object arrays from legacy SMAL pickles."""
54-
if isinstance(value, Ch):
55-
return np.asarray(value.r(), dtype=dtype)
74+
if isinstance(value, Ch) or (
75+
type(value).__name__ == "Ch"
76+
and hasattr(value, "r")
77+
and not isinstance(value, (np.ndarray, list, tuple, dict, str, bytes))
78+
):
79+
return _unwrap_ch(value, dtype=dtype)
5680
if isinstance(value, np.ndarray):
5781
if value.dtype == object:
5882
flat = [materialize(x, dtype=dtype) for x in value.ravel()]

0 commit comments

Comments
 (0)