123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 |
- """
- Engine classes for :func:`~pandas.eval`
- """
- from __future__ import annotations
- import abc
- from typing import TYPE_CHECKING
- from pandas.errors import NumExprClobberingError
- from pandas.core.computation.align import (
- align_terms,
- reconstruct_object,
- )
- from pandas.core.computation.ops import (
- MATHOPS,
- REDUCTIONS,
- )
- from pandas.io.formats import printing
- if TYPE_CHECKING:
- from pandas.core.computation.expr import Expr
- _ne_builtins = frozenset(MATHOPS + REDUCTIONS)
- def _check_ne_builtin_clash(expr: Expr) -> None:
- """
- Attempt to prevent foot-shooting in a helpful way.
- Parameters
- ----------
- expr : Expr
- Terms can contain
- """
- names = expr.names
- overlap = names & _ne_builtins
- if overlap:
- s = ", ".join([repr(x) for x in overlap])
- raise NumExprClobberingError(
- f'Variables in expression "{expr}" overlap with builtins: ({s})'
- )
- class AbstractEngine(metaclass=abc.ABCMeta):
- """Object serving as a base class for all engines."""
- has_neg_frac = False
- def __init__(self, expr) -> None:
- self.expr = expr
- self.aligned_axes = None
- self.result_type = None
- def convert(self) -> str:
- """
- Convert an expression for evaluation.
- Defaults to return the expression as a string.
- """
- return printing.pprint_thing(self.expr)
- def evaluate(self) -> object:
- """
- Run the engine on the expression.
- This method performs alignment which is necessary no matter what engine
- is being used, thus its implementation is in the base class.
- Returns
- -------
- object
- The result of the passed expression.
- """
- if not self._is_aligned:
- self.result_type, self.aligned_axes = align_terms(self.expr.terms)
- # make sure no names in resolvers and locals/globals clash
- res = self._evaluate()
- return reconstruct_object(
- self.result_type, res, self.aligned_axes, self.expr.terms.return_type
- )
- @property
- def _is_aligned(self) -> bool:
- return self.aligned_axes is not None and self.result_type is not None
- @abc.abstractmethod
- def _evaluate(self):
- """
- Return an evaluated expression.
- Parameters
- ----------
- env : Scope
- The local and global environment in which to evaluate an
- expression.
- Notes
- -----
- Must be implemented by subclasses.
- """
- class NumExprEngine(AbstractEngine):
- """NumExpr engine class"""
- has_neg_frac = True
- def _evaluate(self):
- import numexpr as ne
- # convert the expression to a valid numexpr expression
- s = self.convert()
- env = self.expr.env
- scope = env.full_scope
- _check_ne_builtin_clash(self.expr)
- return ne.evaluate(s, local_dict=scope)
- class PythonEngine(AbstractEngine):
- """
- Evaluate an expression in Python space.
- Mostly for testing purposes.
- """
- has_neg_frac = False
- def evaluate(self):
- return self.expr()
- def _evaluate(self) -> None:
- pass
- ENGINES: dict[str, type[AbstractEngine]] = {
- "numexpr": NumExprEngine,
- "python": PythonEngine,
- }
|