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