Python number-like class that remembers arithmetic operations?

Posted on

Question :

Python number-like class that remembers arithmetic operations?

I’m wondering if there exists a python module that would allow me to do something like this:

x = MagicNumber()
x.value = 3
y = 2 * (x + 2) ** 2 - 8
print y   # 42
x.value = 2
print y   # 24

So MagicNumber would implement all the special operator methods, and they would all return instances of MagicNumber, while keeping track of what operations are performed. Is there such a class?

EDIT: clarification

I want to use this in a module that should remember a lot of parameters of some arbitrary calculation that the user wishes to perform. So the user will set the parameters and then use them to produce his result. Then if he decides he’d like to alter a parameter, the change is reflected in his result immediately. So a very simplified usage session with only one parameter instance would look like:

p = MyParams()
p.distance = 13.4           # I use __getattr__ and __setattr__ such that
p.speed = 3.14              # __getattr__ returns MagicNumber instances
time = p.distance / p.speed

EDIT 2: more clarification

Okay, I’ll do what I should have done from the start. I’ll provide context.

You are an engineer and you’re to produce a LaTeX document detailing the workings and properties of some prototype gadget. It is a task you’ll do repeatedly for different prototypes. You write a small LaTeX python interface. For each prototype you create a python module that generates the requisite document. In it you type out the LaTeX code while calculating variables as they are needed, so that the calculations are in context. After a while you notice two problems:

  1. The number of variables and parameters makes locals a mess and the variable names are hard to remember. You should group them into categories to keep track of them all.
  2. You sometimes need to redo the same calculation, which is spread over the last couple of chapters and a dozen lines, with one or more parameters changed. You should find some way to avoid code duplication.

Out of this problem comes the original question.

Answer #1:

Something like this?

import operator

MAKE_BINARY  = lambda opfn : lambda self,other : BinaryOp(self, asMagicNumber(other), opfn)
MAKE_RBINARY = lambda opfn : lambda self,other : BinaryOp(asMagicNumber(other), self, opfn)

class MagicNumber(object):
    __add__  = MAKE_BINARY(operator.add)
    __sub__  = MAKE_BINARY(operator.sub)
    __mul__  = MAKE_BINARY(operator.mul)
    __radd__ = MAKE_RBINARY(operator.add)
    __rsub__ = MAKE_RBINARY(operator.sub)
    __rmul__ = MAKE_RBINARY(operator.mul)
    # __div__  = MAKE_BINARY(operator.div)
    # __rdiv__ = MAKE_RBINARY(operator.div)
    __truediv__ = MAKE_BINARY(operator.truediv)
    __rtruediv__ = MAKE_RBINARY(operator.truediv)
    __floordiv__ = MAKE_BINARY(operator.floordiv)
    __rfloordiv__ = MAKE_RBINARY(operator.floordiv)

    def __neg__(self, other):
        return UnaryOp(self, lambda x : -x)

    @property
    def value(self):
        return self.eval()

class Constant(MagicNumber):
    def __init__(self, value):
        self.value_ = value

    def eval(self):
        return self.value_

class Parameter(Constant):
    def __init__(self):
        super(Parameter, self).__init__(0.0)

    def setValue(self, v):
        self.value_ = v

    value = property(fset=setValue, fget=lambda self: self.value_)

class BinaryOp(MagicNumber):
    def __init__(self, op1, op2, operation):
        self.op1 = op1
        self.op2 = op2
        self.opn = operation

    def eval(self):
        return self.opn(self.op1.eval(), self.op2.eval())


class UnaryOp(MagicNumber):
    def __init__(self, op1, operation):
        self.op1 = op1
        self.operation = operation

    def eval(self):
        return self.opn(self.op1.eval())

asMagicNumber = lambda x : x if isinstance(x, MagicNumber) else Constant(x)

And here it is in action:

x = Parameter()
# integer division
y = 2*x*x + 3*x - x//2

# or floating division
# y = 2*x*x + 3*x - x/2

x.value = 10
print(y.value)
# prints 225

x.value = 20
print(y.value)
# prints 850

# compute a series of x-y values for the function
print([(x.value, y.value) for x.value in range(5)])
# prints [(0, 0), (1, 5), (2, 13), (3, 26), (4, 42)]
Answered By: Lauritz V. Thaulow

Answer #2:

You could give sympy, a computer algebra system written in Python, give a try.

E.g.

>>> from sympy import Symbol
>>> x = Symbol('x')
>>> y = 2 * (x + 2) ** 2 - 8
>>> y
2*(x + 2)**2 - 8
>>> y.subs(x,3)
42
>>> y.subs(x,2)
24
Answered By: PaulMcG

Answer #3:

Isn’t this called a function? This may sound like a simple answer, but I mean it sincerely.

def y(x):
    return 2 * (x + 2) ** 2 - 8

Aren’t you thinking in the wrong direction with this one?

To address the clarification:

class MyParams():
    distance = 0.0
    speed = 0.0
    def __call__(self):
        return self.distance / self.speed

p = MyParams()
p.distance = 13.4            # These are properties
p.speed = 3.14               # where __get__ returns MagicNumber instances
time = p()  # 4.26
p.speed = 2.28
time = p()  # 5.88

I guess I’m more in favor of this type of a solution, although I see the benefit in the sympy module. Preference, I guess.

Answered By: phimuemue

Answer #4:

>>> magic = lambda x: eval('2 * (x + 2) ** 2 - 8')
>>> magic(2)
24
>>> magic(3)
42
>>> magic = lambda x: eval('x ** 4')
>>> magic(2)
16
>>> magic(3)
81
Answered By: jro

Answer #5:

I think the difficulty is in “how to keep operators priority” rather than implementing a class.

I suggest to look at a different notation (like Reverse Polish Notation) that may help in getting rid of priority issues…

Answered By: utapyngo

Leave a Reply

Your email address will not be published.