Reputation: 1231
I have a list of dicts that looks roughly like this:
input_data = {...}
my_list = [
{"a": Class1(input_data)},
{"b": Class2(input_data)},
{"c": Class3(input_data)}
]
I want to be able to maintain a list like the one above but without all of the classes being instantiated until I need them. Sometimes I'll need to go through all of them, however most times I'll just need to access one of them. As it is now, they all get instantiated and make my runtime longer even if I'm only using one of dicts in the list.
How can I prevent the classes from being instantiated until I need them?
Upvotes: 1
Views: 428
Reputation: 10306
It sounds like you need a generator. A generator creates each element as needed when you're iterating over it (the downside is, you can't iterate over the generator again; once you've "used" an element, it's gone). range
in Python is a (somewhat special) example of a generator. I'm not sure how you're going to pull out the single/few items you need from your list, though - will you just need the first few, or do you access them by index? Here's a generator example you could use if you're just going to use the first items from your list:
class C1:
def __init__(*args):
print("C1 init")
class C2:
def __init__(*args):
print("C2 init")
class C3:
def __init__(*args):
print("C3 init")
classes = [C1, C2, C3]
keys = 'abc'
data = [3, 5]
gen = ({keys[i]: classes[i](*data)} for i in range(len(keys)))
# each time you want an element out:
new_dict = next(gen)
If you want to be able to get the items out by index instead, other answers will probably work better.
Upvotes: -2
Reputation: 53029
You could subclass dict
along the lines (bare minimum, you may have to override more methods for a smooth experience):
>>> class delayed_dict(dict):
... def __init__(self, backstore):
... super().__init__()
... self._backstore=backstore
... def __getitem__(self, key):
... if not key in self:
... fun, *args = self._backstore[key]
... self[key] = fun(*args)
... return super().__getitem__(key)
Applied to your example:
>>> input_data = (1, 2, 3)
>>> Class1 = list
>>> Class2 = tuple
>>> Class3 = dict.fromkeys
>>>
>>> my_list = [
... {"a": (Class1, input_data)},
... {"b": (Class2, input_data)},
... {"c": (Class3, input_data)}
... ]
>>>
>>> lazy = list(map(delayed_dict, my_list))
>>>
>>> lazy
[{}, {}, {}]
>>>
>>> x = lazy[1]['b']
>>> x is lazy[1]['b']
True
>>> x
(1, 2, 3)
>>> lazy
[{}, {'b': (1, 2, 3)}, {}]
Upvotes: 0
Reputation: 13750
You can turn them into functions that return the desired object and which you only evaluate as needed.
my_list = [
{"a": lambda: Class1(input_data)},
{"b": lambda: Class2(input_data)},
{"c": lambda: Class3(input_data)}
]
c1 = my_list[0]["a"]()
Upvotes: 0
Reputation: 57033
I feel that you are looking for a dictionary with multiple items, not a list of single-item dictionaries.
You can initialize the dictionary with class constructors rather than class objects and create objects later:
input_data = {...}
my_dict = {
"a": Class1,
"b": Class2,
"c": Class3
}
def get_item(d, key, data = input_data):
if isinstance(d[key], type):
d[key] = d[key](data)
return d[key]
Later:
get_item(my_dict, "a") # Returns a Class1 instance
Perhaps you can even derive a new "smart" dictionary from dict
and redefine its __getitem__
to instantiate objects when they are accessed for the first time.
Upvotes: 1
Reputation: 13498
I would just put the class and its input in a tuple:
input_data = {...}
my_list = [
{"a": (Class1, input_data)},
{"b": (Class2, input_data)},
{"c": (Class3, input_data)}
]
Then when I want to instantiate:
tup = my_list[0]["a"]
instantiated_class = tup[0](tup[1])
Upvotes: 3