5

Suppose I have the code:

a = 2
b = a + 2
a = 3

The question is: how to keep b updated on each change in a? E.g., after the above code I would like to get: print(b) to be 5, not 4.

Of course, b can be a function of a via def, but, say, in IPython it's more comfortable to have simple variables. Are there way to do so? Maybe via SymPy or other libraries?

Anton Tarasenko
  • 8,099
  • 11
  • 66
  • 91
  • Since functions are objects, why is defining a function not comfortable? – Martijn Pieters Aug 05 '13 at 18:00
  • 1
    this does not sound like a good idea ... – Joran Beasley Aug 05 '13 at 18:09
  • 1
    if you want to [avoid parenthesis in function call](http://stackoverflow.com/questions/7698603/defining-python-function-without-the-brackets), you will need to [update Python](http://stackoverflow.com/questions/214881/can-you-add-new-statements-to-pythons-syntax) – rook Aug 05 '13 at 18:24

8 Answers8

8

You can do a lambda, which is basically a function... The only malus is that you have to do b() to get the value instead of just b

>>> a = 2
>>> b = lambda: a + 2
>>> b()
4
>>> a = 3
>>> b()
5
Maxime Lorant
  • 34,607
  • 19
  • 87
  • 97
4

Fair warning: this is a hack only suitable for experimentation and play in a Python interpreter environment. Do not feed untrusted input into this code.

You can define b as an instance of the following class:

class Expression(object):
    def __init__(self, expression):
        self.expression = expression
    def __repr__(self):
        return repr(eval(self.expression))
    def __str__(self):
        return str(eval(self.expression))

Instances of this object will evaluate the expression automatically when printed or echoed in a Python interpreter. Expressions only support references to global names.

Demo:

>>> a = 5
>>> b = Expression('a + 5')
>>> b
10
>>> a = 20
>>> b
25
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
2

If you want a sympy solution you can do:

>>> from sympy import Symbol
>>> a = Symbol('a')
>>> b = Symbol('b')
>>> c = (a + 3) * b
>>> c
b*(a + 3)
>>> c.subs({'a': 3, 'b': 4})
24

Or you can even create your own evaluate function:

>>> def e(exp):
        vars = globals()
        data = {}
        for atom in exp.atoms():
            if atom.is_Symbol:
                if atom.name in vars:
                    data[atom.name] = vars[atom.name]
        return exp.subs(data)
>>> a = 44
>>> b = 33
>>> e(c)
>>> 1551
>>> a = 1
>>> e(c)
>>> 132
Viktor Kerkez
  • 45,070
  • 12
  • 104
  • 85
1

You can't do it on the same way like Java or C. However, you can wrap the variable with your own custom class in order to achieve your goal.

Look at the following example:

class ReferenceVariable:
    def __init__(self, value):
        self.value = value

    def get(self):
        return self.value

    def set(self, val):
        self.value = val

a = ReferenceVariable(3)
b = a
a.set(a.get()+4)
print(b.get()) // Prints 7
barak1412
  • 972
  • 7
  • 17
1

It looks like you want a property.

class MyClazz(object):
    def __init__(self, a):
        self.a = a

    @property
    def b(self):
        return self.a + 2

if __name__ == '__main__':
    c = MyClazz(2)
    print(c.a) # prints 2
    print(c.b) # prints 4
    c.a = 10
    print(c.b) # prints 12

Take a look at the documentation for using property as a decorator, for details on how to add setters and so on if you want that. Since you didn't specify how b is being set, I just hard-coded it, but it would be trivial to make the b-specific part customizable too; something like:

class MyClazz(object):
    def __init__(self, a, b_part=2):
        self.a = a
        self._b = b_part

    @property
    def b(self):
        return self.a + self._b
Henry Keiter
  • 16,863
  • 7
  • 51
  • 80
1

Part of what you say you want sounds a lot like how cells in most spreadsheet programs work, so I suggest you use something like what is in this highly rated ActiveState recipe. It's so short and simple, I'll reproduce it here and apply it to your trivial example:

class SpreadSheet(object):
    _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, cos, pi, sqrt
SpreadSheet.tools.update(sin=sin, cos=cos, pi=pi, sqrt=sqrt, len=len)

ss = SpreadSheet()
ss['a'] = '2'
ss['b'] = 'a + 2'
print ss['b'] # 4
ss['a'] = '3'
print ss['b'] # 5

Many of the recipe's comments describe improvements, some significant, so I'd also suggest reading them.

martineau
  • 119,623
  • 25
  • 170
  • 301
1

I got this idea, after I worked with Qt. It use object property, rather then variable:

from types import FunctionType

class MyObject:
   def __init__(self,name):
      self.name= name
      self._a_f=None
      self._a=None

   @property
   def a(self):
      if self._a_f is not None:
        self._a= self._a_f()
      return self._a
   @a.setter
   def a(self,v):
     if type(v) is FunctionType:
         self._a_f=v
     else:
         self._a_f=None
         self._a=v

o1,o2,o3=map(MyObject,'abc')

o1.a = lambda: o2.a + o3.a
o2.a = lambda: o3.a * 2
o3.a = 10
print( o1.a ) #print 30

o2.a = o1.a + o3.a #this will unbind o3.a from o2.a, setting it to 40
print( o1.a ) #print 50

But what if you want to know when o1.a changed? That's what my first desire was, but implementation is hard. Even if it probably answer other question, here have some example:

class MyObject(metaclass=HaveBindableProperties):
    a= BindableProperty()
    someOther= BindableProperty()


o1,o2,o3=map(MyObject,'abc')

o1.a = lambda: o2.a + o3.a
o2.a = lambda: o3.a * 2

@o1.subscribe_a
def printa():
    print( o1.a )

o3.a = 1 #prints 3

printa.unsubscribe() #to remove subscribtion
Arpegius
  • 5,817
  • 38
  • 53
0

While I am clearly very late to this party, I thought this might be helpful to someone.

from random import randint

x = randint(1, 10)

for sample in range(10):
    print(f"\nsample: {sample+1}")
    print(f"assigned = {x}")
    print(f"embedded = {randint(1, 10)}")

Output:

sample: 1
assigned = 2
embedded = 2

sample: 2
assigned = 2
embedded = 10

sample: 3
assigned = 2
embedded = 5

sample: 4
assigned = 2
embedded = 3

sample: 5
assigned = 2
embedded = 1

sample: 6
assigned = 2
embedded = 1

sample: 7
assigned = 2
embedded = 10

sample: 8
assigned = 2
embedded = 7

sample: 9
assigned = 2
embedded = 4

sample: 10
assigned = 2
embedded = 10

As we can see, if we call the function then it will recalculate. This is a slight variation on the suggested lambda in the accepted answer, as that effectively creates the function within the variable.

James Geddes
  • 742
  • 3
  • 10
  • 35