I was trying to implement sanity check for every class in my Python project CLArena. It is to check the sanity of the properties of a class instance after the __init__()
method initialises them.
The naive thought is to implement a sanity_check()
method for every class like below:
class A:
def __init__(self, a):
self.a = a
self.sanity_check()
def sanity_check(self):
if not isinstance(self.a, int):
raise TypeError('Property `a` should be an integer.')
class B(A):
def __init__(self, a, b):
super().__init__(a)
self.b = b
self.sanity_check()
def sanity_check(self):
if not isinstance(self.b, int):
raise TypeError('Property `b` should be an integer.')
However, if you run this code snippet:
= A(1)
a = B(1, 2) b
you will get an error that self.b
is not defined. This is because the sanity_check()
method called in class A is actually overridden in class B, where self.b
is checked when it’s not defined at the time of calling class A yet.
For this problem, one solution is to move the sanity check for the parent A to the sanity_check()
method of B:
class A:
def __init__(self, a):
self.a = a
# we don't have `self.sanity_check()` here.
def sanity_check(self):
if not isinstance(self.a, int):
raise TypeError('Property `a` should be an integer.')
class B(A):
def __init__(self, a, b):
super().__init__(a)
self.b = b
self.sanity_check()
def sanity_check(self):
super().sanity_check() # call the parent A's sanity check
if not isinstance(self.b, int):
raise TypeError('Property `b` should be an integer.')
This way has another problem that the sanity_check()
method of A is not called when you create an instance of A.
Another way is put sanity check in parent’s __init__()
method, and called super().__init__(a)
at the end of B’s __init__()
method. This way solves the problem that A’s sanity check is not called when creating an instance of A. It also solves the problem that B’s sanity check is called before A’s sanity check.
class A:
def __init__(self, a):
self.a = a
self.sanity_check() # sanity check is called here
def sanity_check(self):
if not isinstance(self.a, int):
raise TypeError('Property `a` should be an integer.')
class B(A):
def __init__(self, a, b):
self.b = b
super().__init__(a) # call the parent A's __init__ at the end
def sanity_check(self):
super().sanity_check() # call the parent A's sanity check
if not isinstance(self.b, int):
raise TypeError('Property `b` should be an integer.')
However, I don’t prefer this way as its logic is too confusing. For example, we don’t see the sanity check in the __init__()
method of B, which leads to the confusion that the sanity check is not called when creating an instance of B (while it’s called inside the super()__init__(a)
). And after all, B is the child class of A, so logically __init__()
of A should be called ahead of those for B.
Finally, I came up with two solutions that I think are better for clarity. For the first one, we name the sanity check method uniquely associated with the class:
class A:
def __init__(self, a):
self.a = a
self.sanity_check_A()
def sanity_check_A(self):
if not isinstance(self.a, int):
raise TypeError('Property `a` should be an integer.')
class B(A):
def __init__(self, a, b):
super().__init__(a)
self.b = b
self.sanity_check_B()
def sanity_check_B(self):
if not isinstance(self.b, int):
raise TypeError('Property `b` should be an integer.')
This avoid the confusion caused by method overriding, and all the codes are clear and easy to understand. It sacrifices the conciseness for naming variables, but I think it’s worth it.
The second solution is to call the sanity check in a way concerning the class name:
class A:
def __init__(self, a):
self.a = a
self)
A.sanity_check(
def sanity_check(self):
if not isinstance(self.a, int):
raise TypeError('Property `a` should be an integer.')
class B(A):
def __init__(self, a, b):
super().__init__(a)
self.b = b
self)
B.sanity_check(
def sanity_check(self):
if not isinstance(self.b, int):
raise TypeError('Property `b` should be an integer.')
Except for the slightly longer code of calling the sanity check, this solution is also clear and easy to understand. I am currently using this way in my project.
Back to top