@@ -116,7 +116,7 @@ def ne_evaluate(expression, local_dict=None, **kwargs):
116
116
constructors = ("arange" , "linspace" , "fromiter" , "zeros" , "ones" , "empty" , "full" , "frombuffer" )
117
117
# Note that, as reshape is accepted as a method too, it should always come last in the list
118
118
constructors += ("reshape" ,)
119
- reducers = ("sum" , "prod" , "min" , "max" , "std" , "mean" , "var" , "any" , "all" )
119
+ reducers = ("sum" , "prod" , "min" , "max" , "std" , "mean" , "var" , "any" , "all" , "slice" )
120
120
121
121
functions = [
122
122
"sin" ,
@@ -541,7 +541,7 @@ def compute_smaller_slice(larger_shape, smaller_shape, larger_slice):
541
541
542
542
# Define the patterns for validation
543
543
validation_patterns = [
544
- r"[\;\[\: ]" , # Flow control characters
544
+ r"[\;]" , # Flow control characters
545
545
r"(^|[^\w])__[\w]+__($|[^\w])" , # Dunder methods
546
546
r"\.\b(?!real|imag|(\d*[eE]?[+-]?\d+)|(\d*[eE]?[+-]?\d+j)|\d*j\b|(sum|prod|min|max|std|mean|var|any|all|where)"
547
547
r"\s*\([^)]*\)|[a-zA-Z_]\w*\s*\([^)]*\))" , # Attribute patterns
@@ -551,7 +551,20 @@ def compute_smaller_slice(larger_shape, smaller_shape, larger_slice):
551
551
_blacklist_re = re .compile ("|" .join (validation_patterns ))
552
552
553
553
# Define valid method names
554
- valid_methods = {"sum" , "prod" , "min" , "max" , "std" , "mean" , "var" , "any" , "all" , "where" , "reshape" }
554
+ valid_methods = {
555
+ "sum" ,
556
+ "prod" ,
557
+ "min" ,
558
+ "max" ,
559
+ "std" ,
560
+ "mean" ,
561
+ "var" ,
562
+ "any" ,
563
+ "all" ,
564
+ "where" ,
565
+ "reshape" ,
566
+ "slice" ,
567
+ }
555
568
valid_methods |= {"int8" , "int16" , "int32" , "int64" , "uint8" , "uint16" , "uint32" , "uint64" }
556
569
valid_methods |= {"float32" , "float64" , "complex64" , "complex128" }
557
570
valid_methods |= {"bool" , "str" , "bytes" }
@@ -579,7 +592,7 @@ def validate_expr(expr: str) -> None:
579
592
raise ValueError (f"'{ expr } ' is not a valid expression." )
580
593
581
594
# Check for invalid characters not covered by the tokenizer
582
- invalid_chars = re .compile (r"[^\w\s+\-*/%().,=<>!&|~^]" )
595
+ invalid_chars = re .compile (r"[^\w\s+\-*/%()[] .,=<>!&|~^]" )
583
596
if invalid_chars .search (skip_quotes ) is not None :
584
597
invalid_chars = invalid_chars .findall (skip_quotes )
585
598
raise ValueError (f"Expression { expr } contains invalid characters: { invalid_chars } " )
@@ -731,6 +744,41 @@ def visit_Call(self, node):
731
744
return newexpression , newoperands
732
745
733
746
747
+ def convert_to_slice (expression ):
748
+ """
749
+ Takes expression and converts all instances of [] to .slice(....)
750
+
751
+ Parameters
752
+ ----------
753
+ expression: str
754
+
755
+ Returns
756
+ -------
757
+ new_expr : str
758
+ """
759
+
760
+ new_expr = ""
761
+ skip_to_char = 0
762
+ for i , expr_i in enumerate (expression ):
763
+ if i < skip_to_char :
764
+ continue
765
+ if expr_i == "[" :
766
+ k = expression [i :].find ("]" ) # start checking from after [
767
+ slice_convert = expression [i : i + k + 1 ] # include [ and ]
768
+ slicer = eval (f"np.s_{ slice_convert } " )
769
+ slicer = (slicer ,) if isinstance (slicer , slice ) else slicer # standardise to tuple
770
+ if any (isinstance (el , str ) for el in slicer ): # handle fields
771
+ raise ValueError ("Cannot handle fields for slicing lazy expressions." )
772
+ slicer = str (slicer )
773
+ # use slice so that lazyexpr uses blosc arrays internally
774
+ # (and doesn't decompress according to getitem syntax)
775
+ new_expr += ".slice(" + slicer + ")"
776
+ skip_to_char = i + k + 1
777
+ else :
778
+ new_expr += expr_i
779
+ return new_expr
780
+
781
+
734
782
class TransformNumpyCalls (ast .NodeTransformer ):
735
783
def __init__ (self ):
736
784
self .replacements = {}
@@ -2543,6 +2591,7 @@ def find_args(expr):
2543
2591
def _compute_expr (self , item , kwargs ): # noqa: C901
2544
2592
if any (method in self .expression for method in reducers ):
2545
2593
# We have reductions in the expression (probably coming from a string lazyexpr)
2594
+ # Also includes slice
2546
2595
_globals = get_expr_globals (self .expression )
2547
2596
lazy_expr = eval (self .expression , _globals , self .operands )
2548
2597
if not isinstance (lazy_expr , blosc2 .LazyExpr ):
@@ -2775,6 +2824,7 @@ def save(self, urlpath=None, **kwargs):
2775
2824
def _new_expr (cls , expression , operands , guess , out = None , where = None , ne_args = None ):
2776
2825
# Validate the expression
2777
2826
validate_expr (expression )
2827
+ expression = convert_to_slice (expression )
2778
2828
if guess :
2779
2829
# The expression has been validated, so we can evaluate it
2780
2830
# in guessing mode to avoid computing reductions
0 commit comments