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

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

53 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.expressions import normalize_numexpr_result 

17from pandas.core.computation.ops import ( 

18 MATHOPS, 

19 REDUCTIONS, 

20) 

21 

22from pandas.io.formats import printing 

23 

24if TYPE_CHECKING: 

25 from pandas.core.computation.expr import Expr 

26 

27_ne_builtins = frozenset(MATHOPS + REDUCTIONS) 

28 

29 

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

31 """ 

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

33 

34 Parameters 

35 ---------- 

36 expr : Expr 

37 Terms can contain 

38 """ 

39 names = expr.names 

40 overlap = names & _ne_builtins 

41 

42 if overlap: 

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

44 raise NumExprClobberingError( 

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

46 ) 

47 

48 

49class AbstractEngine(metaclass=abc.ABCMeta): 

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

51 

52 has_neg_frac = False 

53 

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

55 self.expr = expr 

56 self.aligned_axes = None 

57 self.result_type = None 

58 self.result_name = None 

59 

60 def convert(self) -> str: 

61 """ 

62 Convert an expression for evaluation. 

63 

64 Defaults to return the expression as a string. 

65 """ 

66 return printing.pprint_thing(self.expr) 

67 

68 def evaluate(self) -> object: 

69 """ 

70 Run the engine on the expression. 

71 

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

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

74 

75 Returns 

76 ------- 

77 object 

78 The result of the passed expression. 

79 """ 

80 if not self._is_aligned: 

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

82 self.expr.terms 

83 ) 

84 

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

86 res = self._evaluate() 

87 return reconstruct_object( 

88 self.result_type, 

89 res, 

90 self.aligned_axes, 

91 self.expr.terms.return_type, 

92 self.result_name, 

93 ) 

94 

95 @property 

96 def _is_aligned(self) -> bool: 

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

98 

99 @abc.abstractmethod 

100 def _evaluate(self): 

101 """ 

102 Return an evaluated expression. 

103 

104 Notes 

105 ----- 

106 Must be implemented by subclasses. 

107 """ 

108 

109 

110class NumExprEngine(AbstractEngine): 

111 """NumExpr engine class""" 

112 

113 has_neg_frac = True 

114 

115 def _evaluate(self): 

116 import numexpr as ne 

117 

118 # convert the expression to a valid numexpr expression 

119 s = self.convert() 

120 

121 env = self.expr.env 

122 scope = env.full_scope 

123 _check_ne_builtin_clash(self.expr) 

124 return normalize_numexpr_result(ne.evaluate(s, local_dict=scope)) 

125 

126 

127class PythonEngine(AbstractEngine): 

128 """ 

129 Evaluate an expression in Python space. 

130 

131 Mostly for testing purposes. 

132 """ 

133 

134 has_neg_frac = False 

135 

136 def evaluate(self): 

137 return self.expr() 

138 

139 def _evaluate(self) -> None: 

140 pass 

141 

142 

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

144 "numexpr": NumExprEngine, 

145 "python": PythonEngine, 

146}