Reputation: 6554
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
Upvotes: 2
Views: 14005
Reputation: 1
A self-referential dictionary would almost match your second attempt (from 7 years ago) and would play nicely with any other code that uses this dictionary. In other words it will still look and act like a duck.
class MyDict(dict):
def __getitem__(self,key):
value=dict.__getitem__(self,key)
if callable(value):
value=value(self)
return value
mydict=MyDict({'mass':1.0,'volume':0.5})
mydict['density'] = lambda mydict: mydict['mass']/mydict['volume']
print(mydict['density'])
2.0
mydict['mass']=2.0
print(mydict['density'])
4.0
Upvotes: 0
Reputation: 15345
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.
Upvotes: 3
Reputation: 7353
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.
Upvotes: 1
Reputation: 28288
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.
Upvotes: 3
Reputation: 258408
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.
Upvotes: -2
Reputation: 9058
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.
Upvotes: 6
Reputation: 281795
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.
Upvotes: 4
Reputation: 76842
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)
Upvotes: 9