Skip to content

Commit 1dfd051

Browse files
committed
Replaced substring-based constructor detection with regex matching for real calls
1 parent 538ced3 commit 1dfd051

1 file changed

Lines changed: 20 additions & 7 deletions

File tree

src/blosc2/lazyexpr.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,16 @@ def _get_result(expression, chunk_operands, ne_args, where=None, indices=None, _
250250
# (will be evaluated via the array interface)
251251
additional_funcs = sorted((numpy_funcs | numpy_ufuncs) - set(blosc2_funcs))
252252
functions = blosc2_funcs + additional_funcs
253+
_constructor_call_patterns = {name: re.compile(rf"\b{re.escape(name)}\s*\(") for name in constructors}
254+
255+
256+
def _has_constructor_call(expression: str, constructor: str) -> bool:
257+
return _constructor_call_patterns[constructor].search(expression) is not None
258+
259+
260+
def _find_constructor_call(expression: str, constructor: str) -> re.Match | None:
261+
return _constructor_call_patterns[constructor].search(expression)
262+
253263

254264
relational_ops = ["==", "!=", "<", "<=", ">", ">="]
255265
logical_ops = ["&", "|", "^", "~"]
@@ -2856,7 +2866,7 @@ def shape(self):
28562866
return None
28572867

28582868
# Operands shape can change, so we always need to recompute this
2859-
if any(constructor in self.expression for constructor in constructors):
2869+
if any(_has_constructor_call(self.expression, constructor) for constructor in constructors):
28602870
# might have an expression with pure constructors
28612871
opshapes = {k: v if not hasattr(v, "shape") else v.shape for k, v in self.operands.items()}
28622872
_shape = infer_shape(self.expression, opshapes) # infer shape, includes constructors
@@ -3225,8 +3235,11 @@ def find_args(expr):
32253235
return expr[idx:i], i + 1
32263236
raise ValueError("Unbalanced parenthesis in expression")
32273237

3228-
# Find the index of the first parenthesis after the constructor
3229-
idx = expression.find(f"{constructor}")
3238+
# Find the index of the first constructor call.
3239+
match = _find_constructor_call(expression, constructor)
3240+
if match is None:
3241+
raise ValueError(f"Constructor '{constructor}' not found in expression: {expression}")
3242+
idx = match.start()
32303243
# Find the arguments of the constructor function
32313244
try:
32323245
args, idx2 = find_args(expression[idx + len(constructor) :])
@@ -3294,16 +3307,16 @@ def _compute_expr(self, item, kwargs):
32943307

32953308
return chunked_eval(lazy_expr.expression, lazy_expr.operands, item, **kwargs)
32963309

3297-
if any(constructor in self.expression for constructor in constructors):
3310+
if any(_has_constructor_call(self.expression, constructor) for constructor in constructors):
32983311
expression = self.expression
32993312
newexpr = expression
33003313
newops = self.operands.copy()
33013314
# We have constructors in the expression (probably coming from a string lazyexpr)
33023315
# Let's replace the constructors with the actual NDArray objects
33033316
for constructor in constructors:
3304-
if constructor not in newexpr:
3317+
if not _has_constructor_call(newexpr, constructor):
33053318
continue
3306-
while constructor in newexpr:
3319+
while _has_constructor_call(newexpr, constructor):
33073320
# Get the constructor function and replace it by an NDArray object in the operands
33083321
# Find the constructor call and its arguments
33093322
value, constexpr = self._eval_constructor(newexpr, constructor, newops)
@@ -4082,7 +4095,7 @@ def lazyexpr(
40824095
operands = seek_operands(operand_set, local_dict, global_dict, _frame_depth=_frame_depth)
40834096
else:
40844097
# No operands found in the expression. Maybe a constructor?
4085-
constructor = any(constructor in expression for constructor in constructors)
4098+
constructor = any(_has_constructor_call(expression, constructor) for constructor in constructors)
40864099
if not constructor:
40874100
raise ValueError("No operands nor constructors found in the expression")
40884101
# _new_expr will take care of the constructor, but needs an empty dict in operands

0 commit comments

Comments
 (0)