Python "deferred calculation" programming

499 views Asked by At

This is a two part python question. The first part is about aesthetics and the second is about implementation. A sample of the code I've got working so far is attached at the bottom.

The problem.

I am trying to design a python module / class which enables me to do lazy evaluation / deferred calculations. After setting up these calculations, I would like to be able to identify exactly why the answer is as it is and also amend the inputs of the functions and recalculate the output without having to redefine the intermediary variables .

In essence, I would like to be able to do something as close to possible as this:

>>> a=3
>>> b=5
>>> c=a+b
>>> c
8
>>> a = 6
>>> c
11

and stuff like

>>> c.calculation
'a [ 6 ]  + b [ 5 ] = 11'

Now I'm not that fussed about circular dependencies (yet), and the closest I have got to above is:

a = constant(2)
b = constant(3)
c = a + b
d = a * b

and that gives me stuff like:

c is 5 [ 2 <built-in function __add__> 3 ]
d is 6 [ 2 <built-in function __mul__> 3 ]

which is not perfect, but it's getting close. Then I change the values of a and b in order to recalculate the values of c,d

I know that I cannot use an assignment to amend the value of a,b without completely overwritting it, so stuff like a = 2, b = 3 is right out.

Q1: Aesthetics: What would the best way to write this so that it's the most obvious (pythonic?) way for the client to use?

Next, if you do look at the code below, you will see I've just implemented the operators for add and mul, but I would like to reproduce this for (preferably) all of the built in operators.

Q2: Implementation: What is the best way for me to code this ?

Ultimately, I would like to be able to do some matrix products / sums using this library.

I understand that it would be a memory hog - but for me, getting the explanation of "how did this number end up being x" is more important to me than memory usage / cpu cycles.

import operator

class operable(object):
    def __add__(self,other):
        if isinstance(other,operable):
            d = calculation()
            d.operator = operator.__add__
            d.operands = [self,other]
            return d

    def __mul__(self,other):
        if isinstance(other,operable):
            d = calculation()
            d.operator = operator.__mul__
            d.operands = [self,other]
            return d

class calculation(operable):
    def __init__(self):
        self.operands = []
        self.operator = None

    @property
    def value(self):
        return reduce(self.operator, [x.value for x in self.operands])

    @property
    def calculation(self):
        return (" %s " % str(self.operator)).join([x.__repr__() for x in self.operands])

    def __repr__(self):
        return "%d [ %s ] " % ( self.value, self.calculation )

class constant(operable):
    def __init__(self, x = 0):
        self._value = x

    def __repr__(self):
        return "%d" %( self.value)

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

    @value.setter
    def value(self,new_val):
        self._value = new_val


def test_1():
    a = constant(2)
    b = constant(3)

    c = a + b
    d = a * b

    z = a + b + c + d

    print "c is",c
    print "d is",d
    print "z is ",z

    b.value = 5

    print "c is now",c
    print "d is now",d
    print "z is now ",z



if __name__ == "__main__":
    test_1()
1

There are 1 answers

0
greg On

I quite like Raymond Hettinger's approach to a similar problem:

class SpreadSheet:
    _cells = {}
    tools = {}
    def __setitem__(self, key, formula):
        self._cells[key] = formula
    def getformula(self, key):
        return self._cells[key]
    def __getitem__(self, key ):
        return eval(self._cells[key], SpreadSheet.tools, self)

>>> from math import sin, pi
>>> SpreadSheet.tools.update(sin=sin, pi=pi, len=len)
>>> ss = SpreadSheet()
>>> ss['a1'] = '5'
>>> ss['a2'] = 'a1*6'
>>> ss['a3'] = 'a2*7'
>>> ss['a3']
210
>>> ss['b1'] = 'sin(pi/4)'
>>> ss['b1']
0.70710678118654746
>>> ss.getformula('b1')
'sin(pi/4)'

Taken from: http://code.activestate.com/recipes/355045-spreadsheet/