Let’s say I have a function download()
that takes an optional on_progress
callback. Inside the function, I iterate over some data and call on_progress(i)
if the callback is provided. My current implementation looks like this:
def download(on_progress: Callable | None):
data = requests.get("exampleurl")
for i in data.iter_content():
if on_progress:
on_progress(i)
However, the if on_progress
check inside the loop feels inefficient and repetitive. Is there a more Pythonic way to avoid this check on every iteration while still allowing the on_progress
callback to be optional?
Let’s say I have a function download()
that takes an optional on_progress
callback. Inside the function, I iterate over some data and call on_progress(i)
if the callback is provided. My current implementation looks like this:
def download(on_progress: Callable | None):
data = requests.get("exampleurl.com")
for i in data.iter_content():
if on_progress:
on_progress(i)
However, the if on_progress
check inside the loop feels inefficient and repetitive. Is there a more Pythonic way to avoid this check on every iteration while still allowing the on_progress
callback to be optional?
3 Answers
Reset to default 3There are a lot of approaches, but one way would be to use a no-op function as the default argument:
def _NO_OP(x: str) -> None:
pass
def download(on_progress: Callable[[str], None] | None):
data = requests.get("exampleurl.com")
if on_progress is None:
on_progress = _NO_OP
for i in data.iter_content():
on_progress(i)
Of course, from an efficiency standpoint, probably this is slightly less efficient in the case where there isn't a on_progress
callable passed by the caller, because a cheap if
condition probably costs less than a function call (even if the function itself is just a no-op). Although the code is cleaner.
Frankly, efficiency is really not going to be an issue. I would frankly just keep what you have, maybe change it to if on_progress is not None: ...
You can create a wrapper function that adds progress monitoring only when needed:
from collections.abc import Callable
def normal_behaviour(*args):
'''
Implement here what always needs to be done
'''
def progress_wrapper(func, on_progress):
def with_progress(args):
on_progress(args)
return func(args)
if not on_progress:
return func
return with_progress
def download(on_progress: Callable | None=None):
data = requests.get('http://example.com')
for i in data.iter_content():
progress_wrapper(normal_behaviour, on_progress)(i)
In the above example, if you want to measure progress the normal loop behaviour is wrapped with a progress measuring function. If not, the normal function is returned and there won't be any checks within the loop.
You can eliminate the check inside the loop by using a default value for on_progress
(like a no-op function). This way, the loop doesn't need to check whether on_progress
is provided:
def download(on_progress: Callable | None = lambda x: None):
data = requests.get("http://example.com")
for i in data.iter_content():
on_progress(i)
In this version, on_progress
defaults to a no-op function (lambda x: None)
, so the function always calls it without needing the if check.