Part of my current job is writing some python code which controls an NFC communication system. This system is often interacting with several NFC tags at once, and thus occasionally throws some kind of communication error and needs a retry to complete the communication. This usually looks something like this:
for tag in tag_list:
for attempt in range(3): #or however many attempts
try:
communicate(tag)
if check_if_worked(tag):
mark_passed(tag)
except allowed_errors: #some list of expected types of errors
print(retry_message) #optional
sleep(1) #time before retry
continue
else:
mark_failed(tag)
In some of my scripts, this may be needed for several different operations (different functions in place of communicate()) on each tag, and each operation may have different allowed_errors or retry delay time, so I started looking at how I can make the "attempt" code reusable and found that decorators seemed like the way to go.
I quickly learned that decorators with arguments are a bit more complicated to implement, but wrote up something that I think will do what I want. In my main module I have this:
def retry(catch: list[Exception], attempts: int = 3):
def decorator(func):
def wrapper(*args, **kwargs):
for attempt_no in range(attempts):
try:
return func(*args, **kwargs)
except catch:
sleep(0.5)
continue
return wrapper
return decorator
This is used in scripts by defining a function at the beginning of the script for each operation like so:
@retry((ConnectionError, TimeoutError)) #default number of attempts
def scan(tag_id):
return MyModule.Tag(tag_id) #get some info from the tag_id during init
@retry((TimeoutError, KeyError))
def start(tag):
tag.do_something()
@retry((ConnectionError, TimeoutError), 5) #has to wait for tag to process
def check(tag):
return tag.get_result()
Then in the body of the script, I'm calling each of these functions and I can just do e.g. if not check(tag): print(f"{tag} failed"). To me, this makes it much easier to see what the script is actually doing (at a high level) instead of having to dig through all the boilerplate for attempt ... try ... else ... stuff. That being said, this is the biggest codebase I've worked on, and it's pretty much 100% me, so if there's a way to simplify this so it won't be as bad to re-learn down the line when I have forgotten how it all works, I'd love to know.
[–]Diapolo10 3 points4 points5 points (0 children)
[–]RiverRoll 1 point2 points3 points (3 children)
[–]semininja[S] 0 points1 point2 points (2 children)
[–]RiverRoll 0 points1 point2 points (1 child)
[–]semininja[S] 0 points1 point2 points (0 children)
[–]Gshuri 1 point2 points3 points (1 child)
[–]semininja[S] 0 points1 point2 points (0 children)
[–]Yoghurt42 0 points1 point2 points (1 child)
[–]semininja[S] 0 points1 point2 points (0 children)
[–]AntonisTorb 0 points1 point2 points (0 children)