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 ;)