Calling a function for its side effects using a list comprehension

Question:

I wrote something like this:

def fetch_data(stock_name: str):
    data = get_data(stock_name) # get the data using API 
    print(data)

target_list = ["TESLA", "APPLE", "GOOGLE"]
[fetch_data(stock_name) for stock_name in target_list if len(stock_name) <= 5]

Is it appropriate to use list comprehension and run the fetch_data multiple times by filtering the list first, is there any better or readable way to achieve it?

fetch_data does not return anything.

Asked By: tung

||

Answers:

Turning my comments into an answer, as I think there will be lasting value here.

TL;DR

Keep your code’s intent clear by:

  • using a comprehension when the point is to build a list/dict/set,
  • using a loop when you just need to iterate over something.

Comprehensions are:

  • (probably) faster than a loop to build a list/dict/set,
  • possibly slower than a loop just to iterate.

But let the profiler tell you what and how to optimize.

Style and readability considerations

It is generally poor style to use a comprehension in place of a loop. It makes the code less readable because it makes the intent less clear. The purpose of comprehensions is to construct lists/dicts/sets, you should avoid using them when what you really mean is just "loop over this".

When I see a comprehension in code, my mind sees "oh, I need a list, and here’s the code to build it". If you’re using a comprehension for side effects, I’ll have to ponder for a while what that code is supposed to be doing.

Note that when the comprehension gets too complicated and becomes hard to write and read, as sometimes happens, I just go back to a (sometimes nested) loop when that makes the code clearer.

Performance considerations

In the comments, you suggested using a comprehension might increase the speed, but I doubt that’s true.

When you use a comprehension, you’re actually building a list of all the return codes, possibly just a list of Nones, before throwing away that list.

For the purpose of building a list, when that is your goal, a comprehension is probably faster than a loop with append() calls, but if it’s just for the side effects, it’s doing unnecessary work building a list you don’t want.

For real performance gains, use a profiler

But, my most important recommendation about performance is that you should avoid trying to optimize without doing profiling first. Intuition about speed is often off, and you might end up making changes that don’t actually make a difference or make the code even slower. If you want to optimize, run a profiler on your code and let that tell you which parts of your code need optimization. Then make changes and let the profiler tell you if you actually got an improvement.

But even when you profile, don’t sacrifice readability for a trivial speed gain, you’ll regret it later when you or someone else tries to maintain the code. When I end up with less readable code because it’s significantly faster, I usually leaves comments in the code to that effect, to spell out the intent of my code and why it is actually worth doing it in that unusual way.

Back to your code

I would definitely change your code to this:

def fetch_data(stock_name: str):
    data = get_data(stock_name) # get the data using API 
    print(data)

target_list = ["TESLA", "APPLE", "GOOGLE"]
for stock_name in target_list:
    if len(stock_name) <= 5:
        fetch_data(stock_name)

That’s a nice self-explanatory loop.

Answered By: joanis