Skip to content

Commit 3aa7eee

Browse files
dcherianclaude
andcommitted
Defer obj.cf.bounds in _getitem until needed
drop_bounds is a no-op unless the caller passed a scalar key, so list-key calls (ds.cf[[name]]) never need it. Inline the lookup so list-key paths skip the full-dataset bounds scan entirely. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
1 parent c0a2cec commit 3aa7eee

1 file changed

Lines changed: 25 additions & 19 deletions

File tree

‎cf_xarray/accessor.py‎

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,9 +1077,14 @@ def _getitem(
10771077
"""
10781078

10791079
obj = accessor._obj
1080-
all_bounds = obj.cf.bounds if isinstance(obj, Dataset) else {}
10811080
kind = str(type(obj).__name__)
10821081
scalar_key = isinstance(key, Hashable)
1082+
# obj.cf.bounds is expensive; only compute it when scalar lookup on a
1083+
# Dataset actually needs to drop bounds variables.
1084+
if not isinstance(obj, DataArray) and scalar_key:
1085+
all_bounds = obj.cf.bounds
1086+
else:
1087+
all_bounds = {}
10831088

10841089
key_iter: Iterable[Hashable]
10851090
if isinstance(key, Hashable): # using scalar_key breaks mypy type narrowing
@@ -1125,29 +1130,30 @@ def check_results(names, key):
11251130
grid_mapping_names = []
11261131
grid_mapping_names.append("grid_mapping")
11271132

1128-
standard_names = accessor.standard_names
11291133
custom_criteria = ChainMap(*OPTIONS["custom_criteria"])
11301134

1131-
# Fast path: every key is a direct variable name and matches no CF special key
1132-
reserved: set[Hashable] = set(_AXIS_NAMES).union(
1133-
_COORD_NAMES,
1134-
_GEOMETRY_TYPES,
1135-
("geometry",),
1136-
measures,
1137-
grid_mapping_names,
1138-
custom_criteria,
1139-
cf_role_criteria,
1140-
)
1135+
# Fast path: when every key is just a plain variable in the Dataset (no CF
1136+
# special meaning), skip the per-key classification loop below. Test the
1137+
# cheap predicates first and only build the reserved-name set / consult
1138+
# accessor.standard_names (a full attrs scan) if those could pass — the
1139+
# common ds.cf["X"] / ds.cf["longitude"] paths must not pay that cost.
11411140
fast_path = (
11421141
isinstance(obj, Dataset)
11431142
and not skip
1144-
and all(
1145-
k in obj._variables
1146-
and k not in reserved
1147-
and standard_names.get(k, [k]) == [k]
1148-
for k in key_iter
1149-
)
1143+
and all(k in obj._variables for k in key_iter)
11501144
)
1145+
if fast_path:
1146+
reserved: set[Hashable] = set(_AXIS_NAMES).union(
1147+
_COORD_NAMES,
1148+
_GEOMETRY_TYPES,
1149+
("geometry",),
1150+
measures,
1151+
grid_mapping_names,
1152+
custom_criteria,
1153+
cf_role_criteria,
1154+
)
1155+
standard_names = accessor.standard_names
1156+
fast_path = all(k not in reserved and k not in standard_names for k in key_iter)
11511157

11521158
varnames: list[Hashable]
11531159
coords: list[Hashable]
@@ -2071,7 +2077,7 @@ def cell_measures(self) -> dict[str, list[Hashable]]:
20712077
]
20722078
as_dataset = self._maybe_to_dataset().reset_coords()
20732079

2074-
keys = {}
2080+
keys: dict[str, str] = {}
20752081
for attr in set(all_attrs):
20762082
try:
20772083
keys.update(parse_cell_methods_attr(attr))

0 commit comments

Comments
 (0)