Assert statements are a convenient way to insert debugging assertions into a program
Assert statements are not for what you think they are for.
Don't use them, instead raise explicit exceptions:
def ackermann_peter_10(m, n):
if not (isinstance(m, int) and m >= 0):
raise TypeError('m should be an integer')
if not (isinstance(n, int) and n >= 0):
raise TypeError('n should be an integer')
stack = []
...
If you are confused about why assert statements are bad here, it is because there can be errors if your code is run out of debug mode, also known as optimised mode, via the -O
flag.
This is because assert statements are ignored when you are not in debug mode.
After reading @TLW's comment,
you may want to know why LBYL is a bad idea in Python,
and why EAFP is better.
Python uses duck typing,
this means that it categorizes mallards and ducks as 'ducks',
where a mallard is not a duck.
I.e. both float
and int
are numbers and different types.
Currently float
s are mallards to your function,
they can work since they're pretty much int
s,
and so should be classed as a 'duck'.
For example 3. + 1
, a simple typo, can cause your function to error on what would be valid input.
To allow float
s you can convert the input to an int
.
The function won't run if the input cannot be converted,
as an error will be raised.
Which is an EAFP approach.
But there is still a mallard! As stated by @TLW:
This function will throw with any user-defined types, when the function itself would work just fine
Your end user creates the following class:
class Num:
def __init__(self, num):
self._num = num
def __add__(self, other):
return Num(self._num + other)
def __iadd__(self, other):
self._num = self._num + other
return self
def __sub__(self, other):
return Num(self._num - other)
def __str__(self):
return 'Num ' + str(self._num)
def __bool__(self):
return bool(self._num)
n = Num(0)
print(n)
print(not not n)
n += 1
print(n)
print(n + 1)
print(n - 1)
print(not not n)
# Errors
print(int(n))
As you can guess, it's a number, a 'duck', But it's not an int
, a duck.
And the above solution of using int
to make it a duck doesn't work.
But it works in your function!
This raises the question, 'what is a 'duck'?'
But figuring out what a 'duck' is can be odd,
for example in this function you would need to do:
try:
m + 1
m - 1
except TypeError:
raise TypeError('m should be a number')
But, when the code is more complex, this could not be a viable option, or not fulfil all the requirements for a 'duck'.
For example, remove __bool__
from Num
, and the function will go into an infinite loop.
Do you know how to catch that? Do you know you should catch that? Are there edge-cases?
A simple way to solve this is to instead use an EAFP approach.
Just hope that it's a 'duck', if it's not your user will notice and fix the input.
def int_(n):
try:
return int(n)
except TypeError:
return n
def ackermann_peter_10(m, n):
m = int_(m)
n = int_(n)
stack = []
...
Otherwise, with @oliverpool's changes, your code seems good.