Last active
April 6, 2026 14:41
-
-
Save ptmcg/c7c7f958790940e27800bc6421df34ef to your computer and use it in GitHub Desktop.
Deferred evaluation of arithmetic expressions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Source - https://stackoverflow.com/a/7844038 | |
| # Posted by PaulMcG, modified by community. See post 'Timeline' for change history | |
| # Retrieved 2026-04-04, License - CC BY-SA 4.0 | |
| """ | |
| Module of classes to implement deferred evaluation of arithmetic expressions. | |
| Based on https://stackoverflow.com/a/7844038 | |
| Example: | |
| >>> x = Parameter() | |
| >>> y = -x + 2*x*x + 3*x - x/2 | |
| >>> x.value = 10 | |
| >>> y.value | |
| 215.0 | |
| >>> x.value = 20 | |
| >>> y.value | |
| 830.0 | |
| >>> [(x.value, y.value) for x.value in range(5)] | |
| [(0, 0.0), (1, 3.5), (2, 11.0), (3, 22.5), (4, 38.0)] | |
| """ | |
| from __future__ import annotations | |
| import operator | |
| from abc import ABC, abstractmethod | |
| type Numeric = int | float | |
| def as_expression(term: Expr | Numeric) -> Expr: | |
| """ | |
| Ensure the term is an expression by converting numeric values to Constant. | |
| """ | |
| return term if isinstance(term, Expr) else Constant(term) | |
| def make_binary(opfn): | |
| """ | |
| Create a wrapper for a binary operation. | |
| """ | |
| def wrapper(obj: Expr, other) -> Expr: | |
| return BinaryOp(opfn, obj, as_expression(other)) | |
| return wrapper | |
| def make_rbinary(opfn): | |
| """ | |
| Create a wrapper for a reflected binary operation. | |
| """ | |
| def wrapper(obj: Expr, other) -> Expr: | |
| return BinaryOp(opfn, as_expression(other), obj) | |
| return wrapper | |
| class Expr(ABC): | |
| """ | |
| Abstract base class for expressions supporting arithmetic operators. | |
| """ | |
| # binary operations | |
| __add__ = make_binary(operator.add) | |
| __sub__ = make_binary(operator.sub) | |
| __mul__ = make_binary(operator.mul) | |
| __mod__ = make_binary(operator.mod) | |
| __truediv__ = make_binary(operator.truediv) | |
| __floordiv__ = make_binary(operator.floordiv) | |
| __pow__ = make_binary(operator.pow) | |
| __radd__ = make_rbinary(operator.add) | |
| __rsub__ = make_rbinary(operator.sub) | |
| __rmul__ = make_rbinary(operator.mul) | |
| __rmod__ = make_rbinary(operator.mod) | |
| __rtruediv__ = make_rbinary(operator.truediv) | |
| __rfloordiv__ = make_rbinary(operator.floordiv) | |
| __rpow__ = make_rbinary(operator.pow) | |
| # unary operation | |
| def __neg__(self) -> UnaryOp: | |
| """ | |
| Negate the expression. | |
| """ | |
| return UnaryOp(operator.neg, self) | |
| @property | |
| def value(self) -> Numeric: | |
| """ | |
| Property to get the evaluated value of the expression. | |
| """ | |
| return self._eval() | |
| @abstractmethod | |
| def _eval(self) -> Numeric: | |
| """ | |
| Evaluate and return an intermediate value. | |
| """ | |
| class Constant(Expr): | |
| """ | |
| An expression that represents a constant value. | |
| Evaluating a Constant merely requires returning its defined value. | |
| """ | |
| def __init__(self, value: Numeric = 0): | |
| self._value = value | |
| def _eval(self) -> Numeric: | |
| return self._value | |
| value = property(_eval, doc="The constant value.") | |
| class Parameter(Constant): | |
| """ | |
| A constant expression whose value can be updated after definition. | |
| """ | |
| @property | |
| def value(self) -> Numeric: | |
| return self._value | |
| @value.setter | |
| def value(self, v: Numeric) -> None: | |
| self._value = v | |
| class BinaryOp(Expr): | |
| """ | |
| An expression representing a binary operation. | |
| """ | |
| def __init__(self, operation, op1, op2): | |
| """ | |
| Initialize the binary operation with an operator and two operands. | |
| """ | |
| self.opn = operation | |
| self.op1 = op1 | |
| self.op2 = op2 | |
| def _eval(self) -> Numeric: | |
| """ | |
| Evaluate the binary operation by evaluating its operands and applying the operator. | |
| """ | |
| return self.opn(self.op1.value, self.op2.value) | |
| class UnaryOp(Expr): | |
| """ | |
| An expression representing a unary operation. | |
| """ | |
| def __init__(self, operation, op1): | |
| """ | |
| Initialize the unary operation with an operator and one operand. | |
| """ | |
| self.opn = operation | |
| self.op1 = op1 | |
| def _eval(self) -> Numeric: | |
| """ | |
| Evaluate the unary operation by evaluating its operand and applying the operator. | |
| """ | |
| return self.opn(self.op1.value) | |
| if __name__ == '__main__': | |
| x = Parameter() | |
| y: Expr = -x + 2*x*x + 3*x - x/2 | |
| x.value = 10 | |
| print(y.value) | |
| # prints 215 | |
| x.value = 20 | |
| print(y.value) | |
| # prints 830 | |
| # compute a series of x-y values for the function | |
| print([(x.value, y.value) for x.value in range(5)]) | |
| # prints [(0, 0.0), (1, 3.5), (2, 11.0), (3, 22.5), (4, 38.0)] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment