Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Caching values in Python list comprehensions

I'm using the following list comprehension:

resources = [obj.get("file") for obj in iterator if obj.get("file") != None]

Is there a way to "cache" the value of obj.get("file") when it's checked in the if statement so that it doesn't have to call get again on obj when it generates the return list?

like image 772
Kai Avatar asked Jun 09 '09 18:06

Kai


People also ask

Are Python list comprehensions fast?

List comprehensions are faster than for loops to create lists. But, this is because we are creating a list by appending new elements to it at each iteration. This is slow. Side note: It would even be worse if it was a Numpy Array and not a list.

Are list comprehensions memory efficient than generator comprehensions?

In terms of syntax, the only difference is that you use parenthesis instead of square brackets. The type of data returned by list comprehensions and generator expressions differs. The main advantage of generator over a list is that it take much less memory. We can check how much memory is taken by both types using sys.

How do you cache a result in Python?

You can use the article's URL as the key and its content as the value. Save this code to a caching.py file, install the requests library, then run the script: $ pip install requests $ python caching.py Getting article... Fetching article from server...

Does Python list comprehension maintain order?

Yes, the list comprehension preserves the order of the original iterable (if there is one). If the original iterable is ordered (list, tuple, file, etc.), that's the order you'll get in the result. If your iterable is unordered (set, dict, etc.), there are no guarantees about the order of the items.


4 Answers

resources = filter(None, (obj.get("file") for obj in iterator))

See the documentation for filter for how to provide your own evaluation function. Passing None for the function (as above) filters out all values which aren't true.

If obj.get() returns an object which has a weird __nonzero__ method then you'd need to pass lambda obj: obj != None to get exactly the same result as your original code.

like image 171
Jon-Eric Avatar answered Oct 01 '22 07:10

Jon-Eric


Try something like this:

resources = filter( lambda x: x is not None, [obj.get("file") for ob jin iterator])
like image 20
SingleNegationElimination Avatar answered Oct 01 '22 07:10

SingleNegationElimination


If you want to stay with list / iterator comprehensions instead of using filter you can simply use:

resources = [file_obj
             for file_obj in (obj.get("file") for obj in iterator)
             if file_obj is not None]
like image 37
nikow Avatar answered Oct 01 '22 07:10

nikow


Create a temporary dict to hold values. Then, create a function that uses this dict as a cache, and use that function in the list comprehension, like so:

obj_cache = {}

def cache_get (target, key):
    if (target, key) not in obj_cache: obj_cache[(target, key)] = target.get(key)
    return obj_cache[(target, key)]

resources = [cache_get(obj, "file") for obj in iterator if cache_get(obj, "file") != None]

Also, you probably already know this (and if so, please disregard this answer), but unless obj.get("file") is making a database call, opening a file, making a request over a network, or doing something else potentially expensive, calling it twice per iteration instead of once is probably harmless, since you're only adding O(n) to your cost.

like image 44
jakeboxer Avatar answered Oct 01 '22 07:10

jakeboxer