I am communicating with some hardware over an I2C bus.
Every so often, there is an exception as the bus is busy so I am currently handling this with a loop that retries, but there’s a lot of code repetition, I want to tidy it up.
So, I tried to write a decorator function to pass various functions to
As an example, this command initialises the bus:
i2c = busio.I2C(board.SCL, board.SDA)
This is the decorator I have written:
def try_decorator(func, name):
MAX_TRIES = 5
i = 1
n = name
while True:
try:
func()
except Exception as e:
print(f'retry {i} for {n}')
i += 1
if i == MAX_TRIES:
print(f'max retries of {MAX_TRIES} reached for {n}, with error: {e}')
break
continue
break
And then I try to pass it the line mentioned above as a partial:
i2c = try_decorator(partial(busio.I2C, board.SCL, board.SDA), 'i2c init')
but I get an exception when I then try and pass the i2c to the next function: 'NoneType' object has no attribute 'try_lock'
which I think means the partial isn’t actually initialising the bus properly. I also tried as a lambda and as a function but get similar results.
What can I do to fix this and keep my code as straightforward as possible?
Also, instead of passing a string as name
as an arg (to track exceptions), I tried using func.name in the decorator when I tried as a function instead of a partial but it gives an error that seems as if it is trying to get the name of the I2C object.
Edit: I have updated my code as per BigBro’s answer to below but I still get an error when passing the i2c object to my next function (I don’t get this error if I run without the decorator).
def retry(f):
MAX_TRIES = 5
def wrapped(*args, **kwargs):
for i in range(MAX_TRIES):
try:
res = f(*args, **kwargs)
return res
except IOError as e:
print(f'retry {i} for {f.__name__}, with error: {e}')
print(f'max retries of {MAX_TRIES} reached for {f.__name__}, with error: {e}')
return wrapped
i2c = retry(busio.I2C(board.SCL, board.SDA))
drv = adafruit_drv2605.DRV2605(i2c)
'function' object has no attribute 'try_lock'
Advertisement
Answer
The simple answer is that you forgot to return the result of the function in try_decorator
(and therefore return None
). Replace func()
with res = func()
and then return res
at the end.
However your function is not really a decorator, as it doesn’t actually return a function, and can’t be used as @try_decorator
.
I would suggest something like:
import random
MAX_RETRIES = 10
def retry_decorator(f):
def wrapped(*args, **kwargs): # Allows arg to be passed to the function instead of using partial
for i in range(MAX_RETRIES):
try:
res = f(*args, **kwargs)
return res
except Exception as e:
print(f'Function {f.__name__} failed {i}/{MAX_RETRIES}: {e}') # f.__name__ is what you're looking for
print(f'Function {f.__name__} didn't work after {MAX_RETRIES} retries: {e}')
return wrapped
@retry_decorator
def fail_randomly():
i = random.randint(0, 10)
if i < 7:
raise RuntimeError('Random error')
print('Sucess!')
fail_randomly()
output
Function fail_randomly failed 0/10: Random error
Function fail_randomly failed 1/10: Random error
Function fail_randomly failed 2/10: Random error
Function fail_randomly failed 3/10: Random error
Function fail_randomly failed 4/10: Random error
Sucess!
One last thing, I suggest being more specific in what exception you catch otherwise things might become hard to debug ;)