I'm writing some boilerplate code, which has some abstract classes from which others will inherit. Not every method needs to run, so the abstract class handles exceptions, using @handleError as a decorator.
I'll use dogs to illustrate (the actual problem is a lot more convoluted)
- Here is the exception handling decorator (if can't run, return None)
def handleError(f):
def handleProblems(*args, **kwargs):
try:
return f(*args, **kwargs)
except Exception:
return None
return handleProblems
- Animal class, which will be inherited from
class Animal:
def __init__(self, eat_time, animal_sound):
self.sound = animal_sound
self.eat_time = eat_time
@handleError # <-- if the user messes up "eat", keep going!
def eat(self):
print('Eats for %0.0f seconds' % self.eat_time)
def speak(self):
print(self.sound)
def eat_and_speak(self):
self.eat()
self.speak()
- So now both "good dogs" and "messed up dogs" run to the end. (the "bad dog" will just say "woof", of course)
# good dog
dog = Animal(4, 'woof')
dog.eat_and_speak()
# messed up dog
dog = Animal('four', 'woof')
dog.eat_and_speak()
> 'Eats for 4 seconds'
> 'woof'
> 'woof' . # <-- yay! the messed up dog barked!
Which is exactly what we want, but we also want to allow users to inherit. For instance, by doing this:
class Dog(Animal):
def eat(self):
print('Dog eats for %0.0f seconds' % self.eat_time)
But alas, now only good dogs are allowed:
# good dog
dog = Dog(4, 'woof')
dog.eat_and_speak() # <-- works fine
# bad dog
dog = Dog('four', 'woof')
dog.eat_and_speak() # <-- raises ValueError (as 'four' isn't float)
The bad dog raises a value error, as it no longer has a decorator.
One way around this is to use a private method with a decorator:
class Animal:
def __init__(self, eat_time, animal_sound):
self.sound = animal_sound
self.eat_time = eat_time
def eat(self):
print('Eats for %0.0f seconds' % self.eat_time)
def speak(self):
print(self.sound)
@handleError
def _eat(self):
self.eat()
def eat_and_speak(self):
self._eat()
self.speak()
Which now allows the following bad dog to have the exception handled:
class Dog(Animal):
def eat(self):
print('Dog eats for %0.0f seconds' % self.eat_time)
dog = Dog('four', 'woof')
dog.eat_and_speak()
> 'woof'
However this seems hacky and ugly.
Question: what is the best way for classes to inherit error handling from an abstract class?
Note: The real use case is for some scraper boilerplate code where some of the methods are expected to have errors, but the scraper should keep running while returning None. The owners of the individual scrapers will inherit from this class, which hopefully would handle this for them.