views:

278

answers:

7

Hi all - first post, so play nice!

I have a fairly basic question about Python dictionaries. I would like to have some dict value that updates when another variable is changed (or at least recalculated when it is next called) - eg:

mass = 1.0
volume = 0.5
mydict = {'mass':mass,'volume':volume}
mydict['density'] = mydict['mass']/mydict['volume']

So in this case, mydict['density'] just returns 2.0. If I change mydict['mass'] = 2.0, the density will not be updated. Fine - I can kind of understand why - the density is defined by the values when they were passed to the declaration. So I thought maybe I could approach this with a lambda expression, eg (apologies for the horrid code!):

mydict['density_calc'] = lambda x,y: x/y
mydict['density'] = mydict['density_calc'](mydict['mass'],mydict['volume'])

But again, this only returns the original density, despite changing mydict['mass']. As a final attempt, I tried this:

def density(mass,volume): return mass/volume
mydict['density_calc'] = lambda x,y: density(x,y)
mydict['density'] = mydict['density_calc'](mydict['mass'],mydict['volume'])

Again, no dice. This seems like a really simple problem to solve, so apologies in advance, but if anyone could help me out, I'd be very appreciative!

Cheers,

Dave

+3  A: 

Short answer is you can't.

The assigned expression is evaluated strictly when it's encountered so any changes to the terms later on don't matter.

You could solve this with a custom dict class that overwrites the getitem and setitem methods and does something special (i.e. computes the value) for some of the keys (say density and a finite number of others).

class MyDict(dict):
    def __getitem__(self, key):
            if key == "density":
                    return self["mass"] / self["volume"]
            return dict.__getitem__(self, key)

d = MyDict()
d["mass"] = 2.0
d["volume"] = 4.0
d["density"]    # 0.5

Have a look at this.

Cheers and good luck.

scvalex
I was going to write something similar. +1
ConcernedOfTunbridgeWells
+8  A: 

Seems like an abuse of a dictionary structure. Why not create a simple class?

class Entity(object):
    def __init__(self, mass, volume):
        self.mass = mass
        self.volume = volume
    def _density(self):
        return self.mass / self.volume
    density = property(_density)
FogleBird
+4  A: 

A class would do this better:

class Thing:
    def __init__(self, mass, volume):
        self._mass = mass
        self._volume = volume
        self._update_density()

    def _update_density(self):
        self._density = self._mass / self._volume

    def get_density(self):
        return self._density

    density = property(get_density)

    # You're unlikely to need these, but to demonstrate the point:
    def set_mass(self, mass):
        self._mass = mass
        self._update_density()

    def set_volume(self, volume):
        self._volume = volume
        self._update_density()

brick = Thing(mass=2, volume=0.8)
print brick.density
# Prints 2.5

brick.set_mass(4)
print brick.density
# Prints 5.0

I'm taking you at your word that you want the density updated when you set the other values - an easier way would be to simply calculate it on the fly when asked for it:

    def get_density(self):
        return self._mass / self._volume

Then you wouldn't need _update_density() at all.

RichieHindle
Strange that you know about properties but go through all the trouble of updating the density whenever something changes. Premature optimization? Too much code.
FogleBird
Oh, just now saw your note at the bottom. Why not do that in the first place, though?
FogleBird
I answered the question as asked, and then suggested a simpler way. Maybe Dave has a good reason for wanting the value updated when something changes - like his real problem has a value that's expensive to calculate or something.
RichieHindle
+5  A: 

You would be best off creating a class for this in this case and using a dynamic property. e.g.:


class Body(object):
    def __init__(self):
        self.mass=1.0
        self.volume=0.5

    @property
    def density(self):
        return self.mass/self.volume

This will give you mass, volume and density properties, where density is calculated based on the other two values. e.g.


b=Body()
b.mass=1
b.volume=0.5
print b.density # should be 2

b.mass=2.0
print b.density # should be 4

However if you are wedded to using a dictionary you should probably extend it and override the __getitem__ and __setitem__ "magic" methods to and detect when mass has changed or else when density is being accessed and recalculate as needed.

John Montgomery
A: 

Here's a quick hack on how to subclass dict to meet your specs, but probably not to meet Python's specs for a dictionary:

class MyDict(dict):
    def __getitem__(self, key):
        if key == 'density':
           return self['mass'] / self['volume']
        else:
            return dict.__getitem__(self,key)
    def keys(self):
        return ['density'] + dict.keys(self)

x = MyDict()
x['mass'] = 1.0
x['volume'] = 0.5

print x

print x.keys()

print x['density']

x['mass'] = 2.0

print x['density']

which prints

{'volume': 0.5, 'mass': 1.0}
['density', 'volume', 'mass']
2.0
4.0

But that doesn't account for dict.iterkeys(), among other things.

Mark Rushakoff
This may be closest to answering the OP, but it's awful.
FogleBird
Thanks everyone for the helpful replies. I should perhaps have been more explicit about my requirements. I need to 'carry' the dictionary around - it gets passed through lots of code and duplicated etc - many of the variables are computationally expensive to compute. I was hoping not to create a new class (I work with parallel python, which I've found can be a PITA for handling classes sometimes) but I think I'll use this solution. Cheers!
Dave
@Dave, you should rethink your bias against classes. Computationally expensive is the reason WHY we use classes. A class is expected to have code that will avoid needless calculations. A class's attributes already are a dictionary. Overloading a dictionary like this violates other people's expectations by hiding a "special" key that's different from all others.
S.Lott
+3  A: 

The problem is that dictionaries aren't the right tool for your problem. Your question is like "how can I hammer a nail in with a screwdriver" ;-)

Dictionaries are supposed to map keys to values and don't care about other values in the dictionary. What you want is a class

class PhysicalObject:
    def __init__(self, mass, volume):
         self.mass = float(mass)
         self.volume = float(volume)
    def setMass(self, mass):
         self.mass = float(mass)
    def setVolume(self, volume):
         self.volume = float(volume)
    def getDensity(self):
         return self.mass/float(self.volume)

 v = PhysicalObject(1.0, 2.0)
 print v.getDensity() # prints 0.5
 v.setMass(2.0)
 v.setVolume(1.0)
 print v.getDensity() # prints 2.0

This example recalculates the density every time you want to get it, buy you can also calculate it in the setMass and setVolume functions.

Otto Allmendinger
+1  A: 

This is so easy:

>>> mydict = {'mass':100, 'volume': 2, 'density': lambda: mydict['mass']/mydict['volume']}
>>> mydict['density']()
50
>>> mydict['mass']=200
>>> mydict['density']()
100
>>> mydict['volume'] = 4
>>> mydict['density']()
50

My lambda references mydict and later on retrieves the newest data hold in mydict, so sparing me the trouble of passing the mass and volume to the function each time I want to call it, like in your solution:

def density(mass,volume): return mass/volume
mydict['density_calc'] = lambda x,y: density(x,y)
mydict['density'] = mydict['density_calc'](mydict['mass'],mydict['volume'])

However mydict['density'] is a function, not the value itself.

Adrian Panasiuk
Hmm - I think this was close to what I was aiming for, but I don't want to have to run the function. Much of my code relies on the variables in mydict, and when I'm writing new stuff, it's inevitable I'll forget that some keys need to be executed, some can just be referenced. Thanks for the solution though!
Dave