Say we have two classes, Class A with a custom error which is thrown frequently and it is part of its functionality.
#a.py
class AError(Exception):
"""This exception flags a functional error"""
pass
class A(object):
def work(self):
"""Throws AError when it is tired"""
raise AError() #This exception is raised eventually, business code removed for clarity
Class B, which uses class A to perform some operations.
#b.py
import a
class B(object):
def make_him_work(self, afected):
try:
afected.work()
except a.AError:
pass #This was expected, here will go some business logic
This works great, but it becomes an issue when I have different types of A. Ideally, I'd like to fully decouple A from B so I can pass any class like A that satisfies the same interface, but I cannot due to the exception(as it is not part of the interface itself)
In C++ I would have a header file with the definition of my Interface plus the exceptions which the concrete classes will implement. How is this usually solved in Python? Or said another way, what is the most pythonic approach?
I thought the following options:
1. create module with exceptions and maybe a base class/metaclass (the C++/Java way)
#common.py
class AErrorBase(Exception):
pass
class AIface(object):
def work(self):
raise NotImplemented()
.
#a.py
import common
class AError(common.AErrorBase):
pass
class A(common.AIface):
def work(self):
"""Throws AError when it is tired"""
raise AError()
.
#b.py
import common
class B(object):
def make_him_work(self, afected):
try:
afected.work()
except common.AErrorBase:
pass #This was expected
2. pass exception as an argument
#a.py
class AError(Exception):
pass
class A(object):
def work(self):
"""Throws AError when it is tired"""
raise AError()
.
#b.py
class B(object):
def make_him_work(self, afected, ex_type):
try:
afected.work()
except ex_type:
pass #This was expected
3. Exception as an attribute of the Class so it becomes part of the interface.
#a.py
class A(object):
def work(self):
"""Throws AError when it is tired"""
raise AError()
class AError(Exception):
pass
.
#b.py
class B(object):
def make_him_work(self, afected):
try:
afected.work()
except afected.AError:
pass #This was expected
4. Dont use exception, just a return code. !C days are back!
Any other option? What do you find more "pythonic"?
Edit: Added comments to clarify the purpose of the exception. It needs to be handled in B
Note: This might perfectly be that I am approaching the problem with my old C++ background, I just want to know how do you apply IoC in python when we have exceptions. Feel free to say all my approaches are garbage and I should do it in another way
My class would look like this:
class A(object):
def can_work(self):
"returns True if an A can work otherwise False (e.g. is an A is tired)"
return ...
def work(self):
assert not self._is_tired, "Test if self.can_work() first!"
...
This way you allow the users of A to test whether they should use work.
The assertion is useful for debugging and making sure you or others did not forget about the Interface.
Class B will use A as follows:
class B(object):
def make_him_work(self, afected):
if afected.can_work():
afected.work()
Related
I know python likes to play it nice and loose with types, but sometimes you want a plugin type interface, and want to discover before production that someone has missed something. I found abcmeta - so did the following:
class Abstract_Base(metaclass=abc.ABCMeta):
#abc.abstractmethod
def a():
pass
#abc.abstractmethod
def b():
pass
class Inheritor_One(Abstract_Base):
def a():
pass
but when I do python -m flake8.... it has no problem with that. Is there any way of writing it such that someone not overriding an abstract method will go bang at linting time?
Pylint raises abstract-method for your example:
W0223: Method 'b' is abstract in class 'Abstract_Base' but is not overridden (abstract-method)
If you actually want Inheritor_One to be an abstract class you can disable the warning locally in this class and still have the warning when you use the abstract class later on:
import abc
class Abstract_Base(metaclass=abc.ABCMeta):
#abc.abstractmethod
def a(self):
pass
#abc.abstractmethod
def b(self):
pass
class Inheritor_One(Abstract_Base):
# pylint: disable=abstract-method
def a(self):
pass
I have an abstract class that has multiple #abstractmethod that raises NotImplementedError("Need to implement this").
How do I set up a test-case using python's builtin unittest?
I tried using #patch.multibyte but it is not working.
I dunno what you are trying to achieve by using #patch.multibyte, but if your goal is to test that you have to implement abstract methods in your concrete classes it's just a matter of using assertRaises.
Let's pretend to have an abstract class MyAbstractClass in module my_api.py:
import abc
class MyAbstractClass(abc.ABC):
#abc.abstractmethod
def method_1(self):
pass
Then you write the tests in my_api_tests.py:
from unittest import TestCase
from my_api import MyAbstractClass
class MyConcreteClassWithoutImplementations(MyAbstractClass):
pass
class MyConcreteClassWithImplementations(MyAbstractClass):
def method_1(self):
return 1
class MyAbstractClassTest(TestCase):
def test_cannot_instantiate_concrete_classes_if_abstract_method_are_not_implemented(self):
self.assertRaises(TypeError, lambda: MyConcreteClassWithoutImplementations())
def test_can_instantiate_concrete_classes_if_abstract_method_are_implemented(self):
error = None
try:
my_object = MyConcreteClassWithImplementations()
self.assertEqual(my_object.method_1(), 1)
except TypeError as e:
error = e
self.assertIsNone(error)
...but you are actually testing Python's API, not your own code, so such tests are not useful... you have to test your own business logic ;)
In Python, should we document in the docstrings exceptions that can be raised in other functions/classes besides the ones that are raised in the body of current function/method?
Obs.: I'm considering the Google Python docstring style https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html
It has been a long time since I don't play with Java, but there you would explicitly say what kind of exceptions that your method could raise with the "throws" keyword.
Eg.:
class MyException(Exception):
pass
class A(object):
def foo(self):
"""This class does foo
Returns:
Int: The number of foo.
Raises:
MyException - In case something happen
"""
if True:
raise MyException
return 0
class B(object):
def __init__(self):
self._a = A()
def bar(self):
"""This class does bar
Returns:
Int: number of bar
Raises:
MyException ????? Should this be here?
"""
return self._a.foo()
Yes, you should document that bar() (and foo()) can raise a MyException. This way it's immediately obvious for anyone who's about to use bar() that which exceptions can occur when calling it.
I have an Interface class which defines the requirements to an active "in-use" class:
class Portfolio(ABC):
#abstractmethod
def update_portfolio(self):
raise NotImplementedError
#abstractmethod
def update_from_fill(self):
raise NotImplementedError
#abstractmethod
def check_signal(self, signal_event):
raise NotImplementedError
The methods update_portfolio and update_from_fill are both methods which will be the same in 99% of the required cases. Only the check_signal method will vary. Therefore, to avoid having to write the same code again and again, I have defined a base class with default methods for update_portfolio and update_from_fill:
class BaseBacktestPortfolio(Portfolio):
def __init__(self, ...):
...
def update_portfolio(self, ...):
...
def update_from_fill(self, ...):
...
Then, finally, I have a class inheriting from the BacktestPortfolio class which specifies the correct implementation of the check_signal method:
class USBacktestPortfolio(BaseBacktestPortfolio):
def check_signal(self, ...):
...
Now, the problem is that my editor complains about the BacktestPortfolio classing not having all the required abstract methods. I could ignore this, of course, but the perfect scenario would be if I could make sure that it is not possible to instantiate an object form the BacktestPortfolio class.
Is this possible? And/or is there a more correct way to implement a structure like this?
I could ignore this, of course, but the perfect scenario would be if I could make sure that it is not possible to instantiate an object from the BacktestPortfolio class.
That is the case in your example already:
>>> BaseBacktestPortfolio.mro()
[__main__.BaseBacktestPortfolio, __main__.Portfolio, abc.ABC, object]
>>> BaseBacktestPortfolio()
TypeError: Can't instantiate abstract class BaseBacktestPortfolio with abstract methods check_signal
Since ABC and ABCMeta are just regular types, their features are inherited. This includes their guards against instantiating incomplete classes. Your BaseBacktestPortfolio already is an abstract class.
The warning from your IDE/linter/... exists specifically to warn you that instantiating BaseBacktestPortfolio is not possible.
You can make the BaseBacktestPortfolio also as Abstract class.
from abc import ABC, abstractmethod
class Portfolio(ABC):
#abstractmethod
def update_portfolio(self):
pass
#abstractmethod
def update_from_fill(self):
pass
#abstractmethod
def check_signal(self, signal_event):
pass
class BaseBacktestPortfolio(Portfolio, ABC):
def update_portfolio(self):
print("updated portfolio")
def update_from_fill(self):
print("update from fill")
#abstractmethod
def check_signal(self):
pass
class USBacktestPortfolio(BaseBacktestPortfolio):
def check_signal(self):
print("checked signal")
Also notice that you don't need raise NotImplementedError inside abstract method. You can just pass. Its more Pythonic :)
I find myself often wanting to structure my exception classes like this:
# legends.py
class Error(Exception): pass
class Rick(object):
class Error(Error): pass
class GaveYouUp(Error): pass
class LetYouDown(Error): pass
class Michael(object):
class Error(Error): pass
class BlamedItOnTheSunshine(Error): pass
class BlamedItOnTheMoonlight(Error): pass
I have only seen this pattern used in Django (DoesNotExist) and it makes so much sense. Is there anything I'm missing, why most people seem to favor top-level Exceptions?
edit
I would use these classes for versatile granularity, e.g:
import legends
try:
do_stuff()
except legends.Michael.Error:
blame_it_on_the_boogie()
except legends.Rick.GaveYouUp:
let_you_down()
except legends.Error:
pass
except Exception as e:
raise Hell()
This is the exact pattern used by Django for certain ORM-related exceptions.
The advantage is that you can have an except clause which checks against a type accessed through an instance:
rick = Rick()
try:
rick.roll()
except rick.GaveYouUp:
never()
except rick.LetYouDown:
never_ever()
This doesn't look that useful here, but if rick were a function parameter, then it would potentially be rather useful.
This is also extremely useful in writing generic code which raises the exceptions:
GoddamStar(object):
def sing(self,tune):
raise self.Error()
class Rick(GoddamStar):
class Error(Error): pass
class GaveYouUp(Error): pass
class LetYouDown(Error): pass
class Michael(GoddamStar):
class Error(Error): pass
class BlamedItOnTheSunshine(Error): pass
class BlamedItOnTheMoonlight(Error): pass
rick = Rick()
try:
rick.sing()
except Rick.GaveYouUp:
never()
except Michael.Error:
never_ever()
Django's exceptions generally all derive from global base classes, so that you can also have a catch-all clause which still switches on a type of exception, in case your rick is of an unknown (or otherwise unprovided for) class.
The reason why this isn't much more common is that (a) it doesn't work in early-bound languages, which attract most of the book writers (b) it's moderately rare that this is useful to the user, and so application writers likely figure they aren't going to need it.
If you want to raise e.g. BlamedItOnTheSunshine outside of Micheal you would have to call it by raise Micheal.BlamedItOnTheSunshine('error text').
e.g.:
class A:
class E(Exception): pass
def __init__(self): raise A('error in A')
class B:
def __init__(self): raise A.E('error in B')
in this Example A and B are not related, but if you have a relation like:
class Interpret(object):
class LetsYouDown(Exception): pass
def __init__(self): raise self.LetsYouDown("I'm not Rick!")
class Michael(Interpret):
class BlameItOnTheSunshine(Exception): pass
def __init__(self): raise self.BlameItOnTheSunshine("It's not the Moon!")
class Rick(Interpret):
class NeverEver(Exception): pass
def __init__(self): print "Never Ever!"
and want now something like:
try:
for superstar in [Interpret, Michael, Rick]:
star_in_show = superstar()
except superstar.LetsYouDown:
print "Where's Rick?"
except superstar.BlameItOnTheSunshine:
print "Must be Michael!"
you will get an Error i would call a Liskov's Principle violation.
So one of the main reason's (polymorphism) for using OOP is somewhat compromised. But it
doesn't necesarrily mean you can't or shouldn't use it. Just be aware of the limitations.
i hope that cleared my initial cryptical reservations up.