1414import random
1515import logging
1616import ast
17+ import types
1718
1819from traceback import format_exception_only
1920from collections import namedtuple , OrderedDict
2021from itertools import chain , count
22+ from typing import List , Dict , Any # pylint: disable=unused-import
2123
2224import numpy as np
2325
@@ -632,9 +634,6 @@ def send_report(self):
632634 report .plural ("Constructed feature{s}" , len (items )), items )
633635
634636
635-
636-
637-
638637def freevars (exp , env ):
639638 """
640639 Return names of all free variables in a parsed (expression) AST.
@@ -838,46 +837,76 @@ def bind_variable(descriptor, env):
838837 (descriptor, (instance -> value) | (table -> value list))
839838 """
840839 if not descriptor .expression .strip ():
841- return ( descriptor , lambda _ : float ("nan" ))
840+ return descriptor , FeatureFunc ( "nan" , [], { "nan" : float ("nan" )} )
842841
843842 exp_ast = ast .parse (descriptor .expression , mode = "eval" )
844843 freev = unique (freevars (exp_ast , []))
845844 variables = {sanitized_name (v .name ): v for v in env }
846845 source_vars = [(name , variables [name ]) for name in freev
847846 if name in variables ]
848847
849- values = []
848+ values = {}
850849 if isinstance (descriptor , DiscreteDescriptor ):
851850 values = [sanitized_name (v ) for v in descriptor .values ]
852- return descriptor , FeatureFunc (exp_ast , source_vars , values )
851+ values = {name : i for i , name in enumerate (values )}
852+ return descriptor , FeatureFunc (descriptor .expression , source_vars , values )
853853
854854
855- def make_lambda (expression , args , values ):
856- def make_arg (name ):
857- if sys .version_info >= (3 , 0 ):
858- return ast .arg (arg = name , annotation = None )
859- else :
860- return ast .Name (id = name , ctx = ast .Param (), lineno = 1 , col_offset = 0 )
855+ def make_lambda (expression , args , env = {}):
856+ # type: (ast.Expression, List[str], Dict[str, Any]) -> types.FunctionType
857+ """
858+ Create an lambda function from a expression AST.
861859
860+ Parameters
861+ ----------
862+ expression : ast.Expression
863+ The body of the lambda.
864+ args : List[str]
865+ A list of positional argument names
866+ env : Dict[str, Any]
867+ Extra environment to capture in the lambda's closure.
868+
869+ Returns
870+ -------
871+ func : types.FunctionType
872+ """
873+ # lambda *{args}* : EXPRESSION
862874 lambda_ = ast .Lambda (
863875 args = ast .arguments (
864- args = [make_arg (arg ) for arg in args + values ],
876+ args = [ast . arg (arg = arg , annotation = None ) for arg in args ],
865877 varargs = None ,
866878 varargannotation = None ,
867879 kwonlyargs = [],
868880 kwarg = None ,
869881 kwargannotation = None ,
870- defaults = [ast . Num ( i ) for i in range ( len ( values )) ],
882+ defaults = [],
871883 kw_defaults = []),
872884 body = expression .body ,
873885 )
874886 lambda_ = ast .copy_location (lambda_ , expression .body )
875- exp = ast .Expression (body = lambda_ , lineno = 1 , col_offset = 0 )
876- ast .dump (exp )
887+ # lambda **{env}** : lambda *{args}*: EXPRESSION
888+ outer = ast .Lambda (
889+ args = ast .arguments (
890+ args = [ast .arg (arg = name , annotation = None ) for name in env ],
891+ varargs = None ,
892+ varargannotation = None ,
893+ kwonlyargs = [],
894+ kwarg = None ,
895+ kwargannotation = None ,
896+ defaults = [],
897+ kw_defaults = [],
898+ ),
899+ body = lambda_ ,
900+ )
901+ exp = ast .Expression (body = outer , lineno = 1 , col_offset = 0 )
877902 ast .fix_missing_locations (exp )
878903 GLOBALS = __GLOBALS .copy ()
879904 GLOBALS ["__builtins__" ] = {}
880- return eval (compile (exp , "<lambda>" , "eval" ), GLOBALS )
905+ fouter = eval (compile (exp , "<lambda>" , "eval" ), GLOBALS )
906+ assert isinstance (fouter , types .FunctionType )
907+ finner = fouter (** env )
908+ assert isinstance (finner , types .FunctionType )
909+ return finner
881910
882911
883912__ALLOWED = [
@@ -934,11 +963,26 @@ def make_arg(name):
934963
935964
936965class FeatureFunc :
937- def __init__ (self , expression , args , values ):
966+ """
967+ Parameters
968+ ----------
969+ expression : str
970+ An expression string
971+ args : List[Tuple[str, Orange.data.Variable]]
972+ A list of (`name`, `variable`) tuples where `name` is the name of
973+ a variable as used in `expression`, and `variable` is the variable
974+ instance used to extract the corresponding column/value from a
975+ Table/Instance.
976+ extra_env : Dict[str, Any]
977+ Extra environment specifying constant values to be made available
978+ in expression. It must not shadow names in `args`
979+ """
980+ def __init__ (self , expression , args , extra_env = {}):
938981 self .expression = expression
939982 self .args = args
940- self .values = values
941- self .func = make_lambda (expression , [name for name , _ in args ], values )
983+ self .extra_env = dict (extra_env )
984+ self .func = make_lambda (ast .parse (expression , mode = "eval" ),
985+ [name for name , _ in args ], self .extra_env )
942986
943987 def __call__ (self , instance , * _ ):
944988 if isinstance (instance , Orange .data .Table ):
@@ -947,6 +991,12 @@ def __call__(self, instance, *_):
947991 args = [instance [var ] for _ , var in self .args ]
948992 return self .func (* args )
949993
994+ def __reduce__ (self ):
995+ return type (self ), (self .expression , self .args , self .extra_env )
996+
997+ def __repr__ (self ):
998+ return "{0.__name__}{1!r}" .format (* self .__reduce__ ())
999+
9501000
9511001def unique (seq ):
9521002 seen = set ()
@@ -958,7 +1008,7 @@ def unique(seq):
9581008 return unique_el
9591009
9601010
961- def main (argv = None ):
1011+ def main (argv = None ): # pragma: no cover
9621012 from AnyQt .QtWidgets import QApplication
9631013 if argv is None :
9641014 argv = sys .argv
@@ -981,5 +1031,6 @@ def main(argv=None):
9811031 w .saveSettings ()
9821032 return 0
9831033
1034+
9841035if __name__ == "__main__" :
9851036 sys .exit (main ())
0 commit comments