lambdas
Peter Otten
__peter__ at web.de
Tue Jun 15 02:06:10 EDT 2010
Thomas Jollans wrote:
> On 06/15/2010 12:06 AM, Craig Yoshioka wrote:
>> I'm trying to write a class factory to create new classes dynamically at
>> runtime from simple 'definition' files that happen to be written in
>> python as well. I'm using a class factory since I couldn't find a way to
>> use properties with dynamically generated instances, for example:
>>
>> I would prefer this, but it doesn't work:
>>
>> class Status(object):
>> pass
>>
>> def makeStatus(object):
>> def __init__(self,definitions):
>> for key,function in definitions:
>> setattr(self,key,property(function))
>>
>> this works (and it's fine by me):
>>
>> def makeStatus(definitions):
>> class Status(object):
>> pass
>> for key,function in definitions:
>> setattr(Status,key,property(function))
>> return Status()
>>
>> but I would also like the functions to only be evaluated when necessary
>> since some may be costly, so I want to do the following:
>>
>> def makeStatus(definitions):
>> class Status(object):
>> pass
>> for key,function,data in definitions:
>> setattr(Status,key,property(lambda x: function(data)))
>> return Status()
>>
>> but all my properties now act as if they were invoked with the same data
>> even though each one should have been a new lambda function with it's own
>> associated data. It seems Python is 'optimizing'? all the lambdas to
>> the same object even though that's clearly not what I want to do. Anyone
>> have any suggestions as to:
>>
>> 1) why
>
> (I'm not 100% sure about this)
> I think that when Python encounters "function(data)" while executing any
> one of the lambdas, looks in the scope of the factory function, and uses
> the last value data had there - which has since changed. This old trick
> might help: (if it doesn't, my analysis was wrong)
>
>> 2) what I should do
>
> setattr(Status, key, property(lambda x, d=data: function(d)))
>
>
>> 3) a better way in which to implement this pattern
>
> how about this:
>
> class Status(object):
> def __init__(self, definitions):
> """definitions must be a { key: function, ... } mapping"""
> self.functions = definitions
> self.cache = {}
>
> def __getattribute__(self, name):
> if name in self.cache:
> return self.cache[name]
> elif name in self.functions:
> self.cache[name] = self.functions[name]()
> return self.cache[name]
> else:
> return super(Status, self).__getattribute__(name)
>
> This doesn't use properties (why should it?) and proposes a different
> format for the definitions: a dict instead of a sequence of tuples.
> dict([(a,b), (c,d)]) == {a: b, c: d}, of course, so that's no problem.
>
> Have fun,
> Thomas
An alternative implementation of the above idea:
>>> from functools import partial
>>> def get_alpha(data): return 2**data
...
>>> def get_beta(data): return data + 42
...
>>> definitions = [("alpha", get_alpha, 10), ("beta", get_beta, 20)]
>>> class Status(object):
... definitions = dict((k, partial(v, d)) for k, v, d in definitions)
... def __getattr__(self, name):
... if name not in self.definitions:
... raise AttributeError
... print "calculating", name
... value = self.definitions[name]()
... setattr(self, name, value)
... return value
...
>>> st = Status()
>>> st.alpha
calculating alpha
1024
>>> st.alpha
1024
>>> st.beta
calculating beta
62
>>> st.beta
62
>>> st.gamma
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in __getattr__
AttributeError
>>> del st.beta
>>> st.beta
calculating beta
62
This has the advantage that there is no overhead for attributes whose value
has already been calculated.
Peter
More information about the Python-list
mailing list