Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/core/computation/engines.py: 54%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

52 statements  

1""" 

2Engine classes for :func:`~pandas.eval` 

3""" 

4 

5from __future__ import annotations 

6 

7import abc 

8from typing import TYPE_CHECKING 

9 

10from pandas.errors import NumExprClobberingError 

11 

12from pandas.core.computation.align import ( 

13 align_terms, 

14 reconstruct_object, 

15) 

16from pandas.core.computation.ops import ( 

17 MATHOPS, 

18 REDUCTIONS, 

19) 

20 

21from pandas.io.formats import printing 

22 

23if TYPE_CHECKING: 

24 from pandas.core.computation.expr import Expr 

25 

26_ne_builtins = frozenset(MATHOPS + REDUCTIONS) 

27 

28 

29def _check_ne_builtin_clash(expr: Expr) -> None: 

30 """ 

31 Attempt to prevent foot-shooting in a helpful way. 

32 

33 Parameters 

34 ---------- 

35 expr : Expr 

36 Terms can contain 

37 """ 

38 names = expr.names 

39 overlap = names & _ne_builtins 

40 

41 if overlap: 

42 s = ", ".join([repr(x) for x in overlap]) 

43 raise NumExprClobberingError( 

44 f'Variables in expression "{expr}" overlap with builtins: ({s})' 

45 ) 

46 

47 

48class AbstractEngine(metaclass=abc.ABCMeta): 

49 """Object serving as a base class for all engines.""" 

50 

51 has_neg_frac = False 

52 

53 def __init__(self, expr) -> None: 

54 self.expr = expr 

55 self.aligned_axes = None 

56 self.result_type = None 

57 self.result_name = None 

58 

59 def convert(self) -> str: 

60 """ 

61 Convert an expression for evaluation. 

62 

63 Defaults to return the expression as a string. 

64 """ 

65 return printing.pprint_thing(self.expr) 

66 

67 def evaluate(self) -> object: 

68 """ 

69 Run the engine on the expression. 

70 

71 This method performs alignment which is necessary no matter what engine 

72 is being used, thus its implementation is in the base class. 

73 

74 Returns 

75 ------- 

76 object 

77 The result of the passed expression. 

78 """ 

79 if not self._is_aligned: 

80 self.result_type, self.aligned_axes, self.result_name = align_terms( 

81 self.expr.terms 

82 ) 

83 

84 # make sure no names in resolvers and locals/globals clash 

85 res = self._evaluate() 

86 return reconstruct_object( 

87 self.result_type, 

88 res, 

89 self.aligned_axes, 

90 self.expr.terms.return_type, 

91 self.result_name, 

92 ) 

93 

94 @property 

95 def _is_aligned(self) -> bool: 

96 return self.aligned_axes is not None and self.result_type is not None 

97 

98 @abc.abstractmethod 

99 def _evaluate(self): 

100 """ 

101 Return an evaluated expression. 

102 

103 Parameters 

104 ---------- 

105 env : Scope 

106 The local and global environment in which to evaluate an 

107 expression. 

108 

109 Notes 

110 ----- 

111 Must be implemented by subclasses. 

112 """ 

113 

114 

115class NumExprEngine(AbstractEngine): 

116 """NumExpr engine class""" 

117 

118 has_neg_frac = True 

119 

120 def _evaluate(self): 

121 import numexpr as ne 

122 

123 # convert the expression to a valid numexpr expression 

124 s = self.convert() 

125 

126 env = self.expr.env 

127 scope = env.full_scope 

128 _check_ne_builtin_clash(self.expr) 

129 return ne.evaluate(s, local_dict=scope) 

130 

131 

132class PythonEngine(AbstractEngine): 

133 """ 

134 Evaluate an expression in Python space. 

135 

136 Mostly for testing purposes. 

137 """ 

138 

139 has_neg_frac = False 

140 

141 def evaluate(self): 

142 return self.expr() 

143 

144 def _evaluate(self) -> None: 

145 pass 

146 

147 

148ENGINES: dict[str, type[AbstractEngine]] = { 

149 "numexpr": NumExprEngine, 

150 "python": PythonEngine, 

151}