Create a list property in Python - python

I am starting OOP with Python 3 and I find the concept of property really interesting.
I need to encapsulate a private list, but how could I use this paradigm for lists?
Here's my naive try:
class Foo:
""" Naive try to create a list property.. and obvious fail """
def __init__(self, list):
self._list = list
def _get_list(self, i):
print("Accessed element {}".format(i))
return self._list[i]
def _set_list(self, i, new):
print("Set element {} to {}".format(i, new))
self._list[i] = new
list = property(_get_list, _set_list)
This doesn't behave as expected and even makes python crash when I try the following code. This is the fictive behavior I would like Foo to exhibit:
>>> f = Foo([1, 2, 3])
>>> f.list
[1, 2, 3]
>>> f.list[1]
Accessed element 1
2
>>> f.list[1] = 12
Set element 1 to 12
>>> f.list
[1, 12, 3]

import collections
class PrivateList(collections.MutableSequence):
def __init__(self, initial=None):
self._list = initial or []
def __repr__(self):
return repr(self._list)
def __getitem__(self, item):
print("Accessed element {}".format(item))
return self._list[item]
def __setitem__(self, key, value):
print("Set element {} to {}".format(key, value))
self._list[key] = value
def __delitem__(self, key):
print("Deleting element {}".format(key))
del self._list[key]
def __len__(self):
print("Getting length")
return len(self._list)
def insert(self, index, item):
print("Inserting item {} at {}".format(item, index))
self._list.insert(index, item)
class Foo(object):
def __init__(self, a_list):
self.list = PrivateList(a_list)
Then runnning this:
foo = Foo([1,2,3])
print(foo.list)
print(foo.list[1])
foo.list[1] = 12
print(foo.list)
Outputs:
[1, 2, 3]
Accessed element 1
2
Set element 1 to 12
[1, 12, 3]

There are some problems in your code. They might not be the only problems but fixing them would bring you further:
Properties are for new style classes. They are derived from object:
class Foo(object):
The getter (the first argument to property will be called without argument. So _get_list can't have the second argument i. The same applies to _set_list it can only have one argument, not two. (self is implicit and does not count here.)

Related

Why is the set-method bypassed when property is modified?

I'm not completely new into Python but sometimes I still have trouble with Python's logic or the way thinks are interpreted. I have the following Class called TestClass in the file testclass.py which has one property x and corresponding get and set methods.
class TestClass(object):
#property
def x(self):
print('get x')
return self.__x
#x.setter
def x(self, x):
print('set x')
self.__x = x
If I run a simple example, everything works as it should. So get and set methods are called and print their confirmation messages:
>>> from testclass import TestClass
>>> newObject = TestClass()
>>> newObject
<testclass.TestClass object at 0x0298B9D0>
>>> newObject.x = [1, 2, 3, 4]
set x
>>> newObject.x
get x
[1, 2, 3, 4]
My Problem is that if I just want to modify the property by indexing it, the get method is called (what I expect) to get the property from the object, but the set method is bypassed (no set message is printed, but the property is modified):
>>> newObject.x[1] = 99
get x
>>> newObject.x
get x
[1, 99, 3, 4]
For me, this behaviour is not very logical. I'm coming from Matlab (which is not the most elegant language for OOP). The same structure in Matlab would lead to the following procedure:
get method for x is been called to get x
replace the value at the specific index with the new value
call set method to overwrite the old version of x with the new x
This is just a small example. In my code, i need to get into the set method everytime when the property has been modified. Is there a pythonic way for this?
Thanks a lot!
The property.getter controls what the property returns when bound to an instance. Once that value is returned, the getter has no control on what you do with it. In particular, if the object was mutable, then you can update it without passing by the property.setter.
Returning a copy of the data
One simple way to fix that is to make your property return a view on the internal data, by example by returning a copy of the data instead of the data itself.
This will force the user to get the value, update it and then set it back.
import copy
class TestClass(object):
#property
def x(self):
print('get x')
return copy.deepcopy(self.__x)
#x.setter
def x(self, x):
print('set x')
self.__x = copy.deepcopy(x)
Example
newObject = TestClass()
newObject.x = [1, 2, 3, 4]
newObject.x[1] = 99
print(newObject.x)
Output
set x
get x
get x
[1, 2, 3, 4]
Using a controller class
Although, it can be costly to copy your data everytime, so an alternate solution is to wrap it in a controller class.
This can be done in a methodical way by inheriting from collections.MutableSequence which provides the usual methods available for a list based on your implementation of __delitem__, __setitem__, __getitem__, __len__ and insert.
The result will be an object that behaves exactly like a list, but with some hooks to check any mutation is valid.
from collections import MutableSequence
class ListController(MutableSequence):
def __init__(self, data):
self._data = data
def __setitem__(self, key, value):
print('index {} is being set to {}'.format(key, value))
self._data[key] = value
def __delitem__(self, key):
print('index {} is being deleted'.format(key))
del self._data[key]
def __getitem__(self, item):
return self._data[item]
def __len__(self):
return len(self._data)
def __repr__(self):
return repr(self._data)
def insert(self, index, value):
print('item {} is being inserted at index {}'.format(value, index))
self._data.insert(index, value)
class TestClass(object):
def __init__(self):
self.__x = [1, 2, 3, 4]
#property
def x(self):
print('get x')
return ListController(self.__x)
#x.setter
def x(self, x):
print('set x')
self.__x = list(x)
Example
newObject = TestClass()
newObject.x[1] = 99
print(newObject.x)
Output
get x
index 1 is being set to 99
get x
[1, 99, 3, 4]

Second level looping over dictionaries

In building a class with an out line like below I would like the behaviour of the for loops to, if done once: just give the keys as normal an then move on to the next line of code. But if a second loop is set up inside the first loop it would give the keys on the first loop and then ea value in the sequences in the second loop. The problem I can't figure out is how to set up this under iter.
class MyClass():
def __init__(self):
self.cont1 = [1,2,3,4]
self.cont2 = ('a','b','c')
def __iter__(self):
pass # ???????
Something like this:
dct = dict(container1=[5,6,7,8], container2=('a','b','c')
if one loop is used:
for ea in dct:
print(ea)
print("Howdy")
'containter1'
'containter2'
Howdy
If a nest loop is used:
for ea in dct:
print(ea)
for i in dct.get(ea):
print(i)
'container1'
5
6
...
'container2'
a
b
c
To answer your immediate question, you could just copy how dictionaries implement dict.get and dict.__iter__:
class MyClass():
def __init__(self):
self.cont1 = [1,2,3,4]
self.cont2 = ('a','b','c')
def __iter__(self):
for attr in dir(self):
if not attr.startswith('_') and attr != 'get':
yield attr
def get(self, key):
return getattr(self, key)
It's not a very good approach, however. Looking at the attributes of your object at runtime isn't a good idea, because it will break when you subclass and it will add needless complexity. Instead, just use a dictionary internally:
class MyClass():
def __init__(self):
self.container = {
'cont1': [1, 2, 3, 4],
'cont2': ('a', 'b', 'c')
}
def __iter__(self):
return iter(self.container)
def get(self, key):
return self.container.get(key)
You can do this with a second class like this
class MyClass():
def __init__(self):
self.data = [MyClass2({'cont1' : [1,2,3,4]}),MyClass2({'cont2' : ('a','b','c')})]
def __iter__(self):
for item in self.data:
yield item
class MyClass2():
def __init__(self, mydict):
self.d = mydict
def __iter__(self):
for item in self.d.values():
for value in item:
yield value
def __repr__(self):
return(list(self.d.keys())[0])
m = MyClass()
for k in m:
print(k)
for val in k:
print(val)
You cannot do that simply by implementing __iter__. __iter__ should return an iterator, that is, an object that keeps the state of an iteration (the current position in a sequence of items) and has a method next that returns with each invocation the next item in the sequence.
If your object has nested sequences you can implement an iterator that will traverse only the external sequence, or one that will traverse both
the external and the internal sequences - in a depth-first or a breath-first fashion - but it does not make sense to use nested loops on the same iterable:
# iterate over every item in myobj
for x in myobj:
...
# iterate over every item again? not likely what you want!
for y in myobj:
A more likely situation is:
for x in myob:
...
for y in x:
...
How would you feel about this:
class MyClass():
def __init__(self):
self.cont1 = [1,2,3,4]
self.cont2 = ('a','b','c')
self.conts = {'container1':self.cont1, 'container2':self.cont2}
def __iter__(self):
return self.conts.iteritems()
dct = MyClass()
print('One loop')
for mi in dct:
print(mi)
print('='*40)
print('Nested loops')
for name, values in dct:
print(name)
for i in values:
print(i)
Which outputs:
One loop
container1
container2
========================================
Nested loops
container1
1
2
3
4
container2
a
b
c
Update
I don't know that I would really recommend this, but this seems to more closely fit what the OP wants:
class MyIterator(object):
def __init__(self, name, values):
self.vals = iter(values)
self.name = name
def __iter__(self):
return self.vals
def __str__(self):
return self.name
class MyClass():
def __init__(self):
self.cont1 = [1,2,3,4]
self.cont2 = ('a','b','c')
self.conts = [MyIterator('container1', self.cont1),
MyIterator('container2', self.cont2)]
def __iter__(self):
return iter(self.conts)
dct = MyClass()
for mi in dct:
print(mi)
for i in mi:
print(i)
This is the only way I can think of to be able to print the name and then iterate over it as the values list. This works by overriding the __str__ method to change how the object gets "stringified". But as I said earlier, I think you would be better served with the first part of the answer.
Sorry, just realized nauer's answer already showed something like this.

pythonic way to index list of objects

I have a list of objects. Each object has two fields
obj1.status = 2
obj1.timestamp = 19211
obj2.status = 3
obj2.timestamp = 14211
obj_list = [obj1, obj2]
I will keep adding / deleting objects in the list and also changing attributes of objects, for example I may change ob1.status to 5.
Now I have two dicts
dict1 - <status, object>
dict2 - <timestamp, object>
How do I design a simple solution so that whenever I modify/delete/insert elements in the list, the maps get automatically updated. I am interested in a pythonic solution that is elegant and extensible. For example in future, I should be able to easily add another attribute and dict for that as well
Also for simplicity, let us assume all attributes value are different. For example no two objects will have same status
You could override the __setattr__ on the objects to update the indexes whenever you set the values. You can use a weakref dictionary for the indexes so that when you delete objects and are no longer using them, they are automatically removed from the indexes.
import weakref
from bunch import Bunch
class MyObject(object):
indexes = Bunch() # Could just use dict()
def __init__(self, **kwargs):
super(MyObject, self).__init__()
for k, v in kwargs.items():
setattr(self, k, v)
def __setattr__(self, name, value):
try:
index = MyObject.indexes[name]
except KeyError:
index = weakref.WeakValueDictionary()
MyObject.indexes[name] = index
try:
old_val = getattr(self, name)
del index[old_val]
except (KeyError, AttributeError):
pass
object.__setattr__(self, name, value)
index[value] = self
obj1 = MyObject(status=1, timestamp=123123)
obj2 = MyObject(status=2, timestamp=2343)
print MyObject.indexes.status[1]
print obj1.indexes.timestamp[2343]
obj1.status = 5
print obj2.indexes['status'][5]
I used a Bunch here because it allows you to access the indexes using .name notation, but you could just use a dict instead and use the ['name'] syntax.
One approach here would be to create a class level dict for MyObj and define updating behavior using property decorator. Every time an object is changed or added, it is reflected in the respected dictionaries associated with the class.
Edit: as #BrendanAbel points out, using weakref.WeakValueDictionary in place of dict handles object deletion from class level dicts.
from datetime import datetime
from weakref import WeakValueDictionary
DEFAULT_TIME = datetime.now()
class MyObj(object):
"""
A sample clone of your object
"""
timestamps = WeakValueDictionary()
statuses = WeakValueDictionary()
def __init__(self, status=0, timestamp=DEFAULT_TIME):
self._status = status
self._timestamp = timestamp
self.status = status
self.timestamp = timestamp
def __update_class(self):
MyObj.timestamps.update({self.timestamp: self})
MyObj.statuses.update({self.status: self})
def __delete_from_class(self):
maybe_self = MyObj.statuses.get(self.status, None)
if maybe_self is self is not None:
del MyObj.statuses[self.status]
maybe_self = MyObj.timestamps.get(self.timestamp, None)
if maybe_self is self is not None:
del MyObj.timestamps[self.timestamp]
#property
def status(self):
return self._status
#status.setter
def status(self, val):
self.__delete_from_class()
self._status = val
self.__update_class()
#property
def timestamp(self):
return self._timestamp
#timestamp.setter
def timestamp(self, val):
self.__delete_from_class()
self._timestamp = val
self.__update_class()
def __repr__(self):
return "MyObj: status={} timestamp={}".format(self.status, self.timestamp)
obj1 = MyObj(1)
obj2 = MyObj(2)
obj3 = MyObj(3)
lst = [obj1, obj2, obj3]
# In [87]: q.lst
# Out[87]:
# [MyObj: status=1 timestamp=2016-05-27 13:43:38.158363,
# MyObj: status=2 timestamp=2016-05-27 13:43:38.158363,
# MyObj: status=3 timestamp=2016-05-27 13:43:38.158363]
# In [88]: q.MyObj.statuses[1]
# Out[88]: MyObj: status=1 timestamp=2016-05-27 13:43:38.158363
# In [89]: q.MyObj.statuses[1].status = 42
# In [90]: q.MyObj.statuses[42]
# Out[90]: MyObj: status=42 timestamp=2016-05-27 13:43:38.158363
# In [91]: q.MyObj.statuses[1]
# ---------------------------------------------------------------------------
# KeyError Traceback (most recent call last)
# <ipython-input-91-508ab072bfc4> in <module>()
# ----> 1 q.MyObj.statuses[1]
# KeyError: 1
For a collection to be aware of mutation of its elements, there must be some connection between the elements and that collection which can communicate when changes happen. For this reason, we either must bind an instance to a collection or proxy the elements of the collection so that change-communication doesn't leak into the element's code.
A note about the implementation I'm going to present, the proxying method only works if the attributes are changed by direct setting, not inside of a method. A more complex book-keeping system would be necessary then.
Additionally, it assumes that exact duplicates of all attributes won't exist, given that you require the indices be built out of set objects instead of list
from collections import defaultdict
class Proxy(object):
def __init__(self, proxy, collection):
self._proxy = proxy
self._collection = collection
def __getattribute__(self, name):
if name in ("_proxy", "_collection"):
return object.__getattribute__(self, name)
else:
proxy = self._proxy
return getattr(proxy, name)
def __setattr__(self, name, value):
if name in ("_proxy", "collection"):
object.__setattr__(self, name, value)
else:
proxied = self._proxy
collection = self._collection
old = getattr(proxied, name)
setattr(proxy, name, value)
collection.signal_change(proxied, name, old, value)
class IndexedCollection(object):
def __init__(self, items, index_names):
self.items = list(items)
self.index_names = set(index_names)
self.indices = defaultdict(lambda: defaultdict(set))
def __len__(self):
return len(self.items)
def __iter__(self):
for i in range(len(self)):
yield self[i]
def remove(self, obj):
self.items.remove(obj)
self._remove_from_indices(obj)
def __getitem__(self, i):
# Ensure consumers get a proxy, not a raw object
return Proxy(self.items[i], self)
def append(self, obj):
self.items.append(obj)
self._add_to_indices(obj)
def _add_to_indices(self, obj):
for indx in self.index_names:
key = getattr(obj, indx)
self.indices[indx][key].add(obj)
def _remove_from_indices(self, obj):
for indx in self.index_names:
key = getattr(obj, indx)
self.indices[indx][key].remove(obj)
def signal_change(self, obj, indx, old, new):
if indx not in self.index_names:
return
# Tell the container to update its indices for a
# particular attribute and object
self.indices[indx][old].remove(obj)
self.indices[indx][new].add(obj)
I am not sure if this is what you are asking for but ...
Objects:
import operator
class Foo(object):
def __init__(self):
self.one = 1
self.two = 2
f = Foo()
f.name = 'f'
g = Foo()
g.name = 'g'
h = Foo()
h.name = 'h'
name = operator.attrgetter('name')
lists: a initially contains f and b initially contains h
a = [f]
b = [h]
dictionaries: each with one item whose value is one of the lists
d1 = {1:a}
d2 = {1:b}
d1[1] is list a which contains f and f.one is 1
>>> d1
{1: [<__main__.Foo object at 0x03F4CA50>]}
>>> name(d1[1][0])
'f'
>>> name(d1[1][0]), d1[1][0].one
('f', 1)
changing f.one is seen in the dictionary
>>> f.one = '?'
>>> name(d1[1][0]), d1[1][0].one
('f', '?')
>>>
d2[1] is list b which contains h
>>> d2
{1: [<__main__.Foo object at 0x03F59070>]}
>>> name(d2[1][0]), d2[1][0].one
('h', 1)
Add an object to b and it is seen in the dictionary
>>> b.append(g)
>>> b
[<__main__.Foo object at 0x03F59070>, <__main__.Foo object at 0x03F4CAF0>]
>>> d2
{1: [<__main__.Foo object at 0x03F59070>, <__main__.Foo object at 0x03F4CAF0>]}
>>> name(d2[1][1]), d2[1][1].one
('g', 1)

Python make dictionary items accessible as object property

This is mostly syntactic sugar but I'd like to access the items of a dictionary as object properties.
Example:
class CoolThing():
def __init__(self):
self.CoolDict = {'a': 1, 'b': 2}
and I'd like to have
my_cool_thing.a # => 1
my_cool_thing.b # => 2
Edit: some code of a potential solution with a nested structure with dot notation: device.property.field
class Parameters():
def __init__(self, ids, devices):
self._ids = ids
self._devices = devices
for p in self._devices:
p = p[0]
if self.__dict__.get(p.device) is None:
self.__dict__[p.device] = SmartDict()
else:
if self.__dict__[p.device].get(p.property) is None:
self.__dict__[p.device][p.property] = SmartDict()
else:
if self.__dict__[p.device][p.property].get(p.field) is None:
self.__dict__[p.device][p.property][p.field] = ParameterData(p)
class SmartDict():
def __init__(self):
self.__dict__ = {}
def __getitem__(self, k):
return self.__dict__[k]
def __setitem__(self, k, v):
self.__dict__[k] = v
def get(self, k):
return self.__dict__.get(k)
def __len__(self):
return len(self.__dict__)
You want __getattr__ and __setattr__, though you'll have to roll your own class (I'm not aware of any builtins, though namedtuple might work if you don't need to change values much)
class AttrDict(dict):
def __getattr__(self, attr):
return self[attr]
def __setattr__(self, attr, value):
self[attr] = value
If you just want to access a sub-dictionary that way, you just change self to self.cool_dict
class CoolThing:
def __init__(self):
self.cool_dict = {'a': 1, 'b': 2}
def __getattr__(self, attr):
return self.cool_dict[attr]
def __setattr__(self, attr, value):
# Note, you'll have to do this for anything that you want to set
# in __init__.
if attr == 'cool_dict':
super().__setattr__(attr, value)
else:
self.cool_dict[attr] = value
Note that __getattr__ is used after any other lookups fail, but if you want to ensure that your function is called first, you can use __getattribute__
Also note that self.cool_dict does not exist on CoolThing until after __init__ is called. My initial version of this would throw a maximum recursion depth exceeded, because as you created the class it would go to set self.cool_dict in init, call __setattr__, which would try to get self.cool_dict so it could set [attr] = value on it. Naturally it can't find cool_dict yet, and so it will try to call __getattr__ again... which can't find cool_dict and round and round it goes.
Another option would be to use a class-level variable instead, but that's probably not at all what you want :)
CoolDict already exists, it's named __dict__:
>>> class CoolThing(object):
... def __init__(self):
... self.__dict__['a'] = 1
... self.__dict__['b'] = 2
...
>>> thing = CoolThing()
>>> thing.a
1
>>> thing.b
2
>>> thing.c = 3
>>> thing.__dict__
{'a': 1, 'b': 2, 'c': 3}

Setup dictionary lazily

Let's say I have this dictionary in python, defined at the module level (mysettings.py):
settings = {
'expensive1' : expensive_to_compute(1),
'expensive2' : expensive_to_compute(2),
...
}
I would like those values to be computed when the keys are accessed:
from mysettings import settings # settings is only "prepared"
print settings['expensive1'] # Now the value is really computed.
Is this possible? How?
Don't inherit build-in dict. Even if you overwrite dict.__getitem__() method, dict.get() would not work as you expected.
The right way is to inherit abc.Mapping from collections.
from collections.abc import Mapping
class LazyDict(Mapping):
def __init__(self, *args, **kw):
self._raw_dict = dict(*args, **kw)
def __getitem__(self, key):
func, arg = self._raw_dict.__getitem__(key)
return func(arg)
def __iter__(self):
return iter(self._raw_dict)
def __len__(self):
return len(self._raw_dict)
Then you can do:
settings = LazyDict({
'expensive1': (expensive_to_compute, 1),
'expensive2': (expensive_to_compute, 2),
})
I also list sample code and examples here: https://gist.github.com/gyli/9b50bb8537069b4e154fec41a4b5995a
If you don't separe the arguments from the callable, I don't think it's possible. However, this should work:
class MySettingsDict(dict):
def __getitem__(self, item):
function, arg = dict.__getitem__(self, item)
return function(arg)
def expensive_to_compute(arg):
return arg * 3
And now:
>>> settings = MySettingsDict({
'expensive1': (expensive_to_compute, 1),
'expensive2': (expensive_to_compute, 2),
})
>>> settings['expensive1']
3
>>> settings['expensive2']
6
Edit:
You may also want to cache the results of expensive_to_compute, if they are to be accessed multiple times. Something like this
class MySettingsDict(dict):
def __getitem__(self, item):
value = dict.__getitem__(self, item)
if not isinstance(value, int):
function, arg = value
value = function(arg)
dict.__setitem__(self, item, value)
return value
And now:
>>> settings.values()
dict_values([(<function expensive_to_compute at 0x9b0a62c>, 2),
(<function expensive_to_compute at 0x9b0a62c>, 1)])
>>> settings['expensive1']
3
>>> settings.values()
dict_values([(<function expensive_to_compute at 0x9b0a62c>, 2), 3])
You may also want to override other dict methods depending of how you want to use the dict.
Store references to the functions as the values for the keys i.e:
def A():
return "that took ages"
def B():
return "that took for-ever"
settings = {
"A": A,
"B": B,
}
print(settings["A"]())
This way, you only evaluate the function associated with a key when you access it and invoke it. A suitable class which can handle having non-lazy values would be:
import types
class LazyDict(dict):
def __getitem__(self,key):
item = dict.__getitem__(self,key)
if isinstance(item,types.FunctionType):
return item()
else:
return item
usage:
settings = LazyDict([("A",A),("B",B)])
print(settings["A"])
>>>
that took ages
You can make expensive_to_compute a generator function:
settings = {
'expensive1' : expensive_to_compute(1),
'expensive2' : expensive_to_compute(2),
}
Then try:
from mysettings import settings
print next(settings['expensive1'])
I would populate the dictionary values with callables and change them to the result upon reading.
class LazyDict(dict):
def __getitem__(self, k):
v = super().__getitem__(k)
if callable(v):
v = v()
super().__setitem__(k, v)
return v
def get(self, k, default=None):
if k in self:
return self.__getitem__(k)
return default
Then with
def expensive_to_compute(arg):
print('Doing heavy stuff')
return arg * 3
you can do:
>>> settings = LazyDict({
'expensive1': lambda: expensive_to_compute(1),
'expensive2': lambda: expensive_to_compute(2),
})
>>> settings.__repr__()
"{'expensive1': <function <lambda> at 0x000001A0BA2B8EA0>, 'expensive2': <function <lambda> at 0x000001A0BA2B8F28>}"
>>> settings['expensive1']
Doing heavy stuff
3
>>> settings.get('expensive2')
Doing heavy stuff
6
>>> settings.__repr__()
"{'expensive1': 3, 'expensive2': 6}"
I recently needed something similar. Mixing both strategies from Guangyang Li and michaelmeyer, here is how I did it:
class LazyDict(MutableMapping):
"""Lazily evaluated dictionary."""
function = None
def __init__(self, *args, **kargs):
self._dict = dict(*args, **kargs)
def __getitem__(self, key):
"""Evaluate value."""
value = self._dict[key]
if not isinstance(value, ccData):
value = self.function(value)
self._dict[key] = value
return value
def __setitem__(self, key, value):
"""Store value lazily."""
self._dict[key] = value
def __delitem__(self, key):
"""Delete value."""
return self._dict[key]
def __iter__(self):
"""Iterate over dictionary."""
return iter(self._dict)
def __len__(self):
"""Evaluate size of dictionary."""
return len(self._dict)
Let's lazily evaluate the following function:
def expensive_to_compute(arg):
return arg * 3
The advantage is that the function is yet to be defined within the object and the arguments are the ones actually stored (which is what I needed):
>>> settings = LazyDict({'expensive1': 1, 'expensive2': 2})
>>> settings.function = expensive_to_compute # function unknown until now!
>>> settings['expensive1']
3
>>> settings['expensive2']
6
This approach works with a single function only.
I can point out the following advantages:
implements the complete MutableMapping API
if your function is non-deterministic, you can reset a value to re-evaluate
pass in a function to generate the values on the first attribute get:
class LazyDict(dict):
""" Fill in the values of a dict at first access """
def __init__(self, fn, *args, **kwargs):
self._fn = fn
self._fn_args = args or []
self._fn_kwargs = kwargs or {}
return super(LazyDict, self).__init__()
def _fn_populate(self):
if self._fn:
self._fn(self, *self._fn_args, **self._fn_kwargs)
self._fn = self._fn_args = self._fn_kwargs = None
def __getattribute__(self, name):
if not name.startswith('_fn'):
self._fn_populate()
return super(LazyDict, self).__getattribute__(name)
def __getitem__(self, item):
self._fn_populate()
return super(LazyDict, self).__getitem__(item)
>>> def _fn(self, val):
... print 'lazy loading'
... self['foo'] = val
...
>>> d = LazyDict(_fn, 'bar')
>>> d
{}
>>> d['foo']
lazy loading
'bar'
>>>
Alternatively, one can use the LazyDictionary package that creates a thread-safe lazy dictionary.
Installation:
pip install lazydict
Usage:
from lazydict import LazyDictionary
import tempfile
lazy = LazyDictionary()
lazy['temp'] = lambda: tempfile.mkdtemp()

Categories

Resources