Bo said:
I guess I will go with solution 3. It is evil but it is most close to my
original intention. It leads to most readable code (except for the first
line to do the magic and the last line to return result) and fastest
performance.
Thousands of programs use Python's class attribute access syntax day-in and
day-out and find the performance to be acceptable.
Premature optimization is the root of much evil - the exec + locals() hack if
very dependent on the exact Python version you are using. Expect some hard to
find bugs if you actually use the hack.
And code which relies on an evil hack in order to have the desired effect
doesn't count as readable in my book - I would be very surprised if many people
reading your code knew that updates to locals() can sometimes be made to work by
performing them inside a bare exec statement.
Thank again for everyone's help. I have learned a lot from the posts,
especially the wrapdict class.
It sounds like the dictionaries you are working with actually have some
meaningful content that you want to manipulate.
I'd seriously suggest creating a class which is able to extract the relevant
data from the dictionaries, supports the desired manipulations, and then be
instructed to update the original dictionaries.
For example, an approach based on Michael's dictionary wrapper class
class DataAccessor(object):
# Just a different name for Michael's "wrapbigdict"
def __init__(self, data):
object.__setattr__(self, "_data", data)
def __getattr__(self, attrname):
return self._data[attrname]
def __setattr__(self, attrname, value):
self._data[attrname] = value
class DataManipulator(DataAccessor):
def __init__(self, data):
DataAccessor.__init__(self, data)
def calc_z(self):
self.z = self.x + self.y
In action:
Py> data = DataManipulator(dict(x=1, y=2))
Py> data.z
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 6, in __getattr__
KeyError: 'z'
Py> data.calc_z()
Py> data.z
3
Py> data._data
{'y': 2, 'x': 1, 'z': 3}
The class based approach also gives you access to properties, which can be used
to make that first call to 'z' automatically calculate the desired result
instead of giving a KeyError:
class EnhancedDataManipulator(DataAccessor):
def __init__(self, data):
DataAccessor.__init__(self, data)
class _utils(object):
@staticmethod
def make_prop(name, calculator):
def get(self, _name=name, _calculator=calculator):
try:
return self._data[_name]
except KeyError:
val = _calculator(self)
self._data[_name] = val
return val
def set(self, val, _name=name):
self._data[_name] = val
def delete(self, _name=name):
del self._data[_name]
return property(get, set, delete)
@staticmethod
def calc_z(self):
return self.x + self.y
z = _utils.make_prop('z', _utils.calc_z)
Note the nested _utils class isn't strictly necessary. I just like to use it to
separate out the information which the class uses to construct itself from those
which are provided to clients of the class.
Anyway, the enhanced version in action:
Py> data = EnhancedDataManipulator(dict(x=1,y=2))
Py> data.x
1
Py> data.y
2
Py> data.z
3
Py> data.x = 30
Py> data.z
3
Py> data.z = 10
Py> data.z
10
Py> del data.z
Py> data.z
32
If you want to add more calculated properties to the data manipulator, simply
define additional calculator methods, and define the attribute with make_prop.
Use a class. Giving meaningful form to a collection of attributes is what
they're for, and Python provides many features designed to make that process easier.
Trying to fake it with function namespaces is just going to make trouble for you
in the future.
Cheers,
Nick.