Moving along through our in-depth Python Exception Handling series, today we'll be looking at the ZeroDivisionError. As you may suspect, the ZeroDivisionError
in Python indicates that the second argument used in a division (or modulo) operation was zero.
Throughout this article we'll examine the ZeroDivisionError
by looking at where it fits within the overall Python Exception Class Hierarchy, then we'll look at some functional sample code illustrating how such errors may be raised in your own code. We'll also see how different numeric types (and common mathematical libraries) produce slightly different results when handling division by zero issues.
The Technical Rundown
All Python exceptions inherit from the BaseException
class, or extend from an inherited class therein. The full exception hierarchy of this error is:
BaseException
Exception
ArithmeticError
ZeroDivisionError
Full Code Sample
Below is the full code sample we'll be using in this article. It can be copied and pasted if you'd like to play with the code yourself and see how everything works.
import decimal
from enum import Enumfrom gw_utility.logging import Logging
from mpmath import mpfclass NumberType(Enum):
"""Specifies number type or library used for calculating values."""
INTEGER = 1
FLOAT = 2
DECIMAL = 3
MPMATH = 4def main():
Logging.line_separator("FRACTION TEST", 40, '+')divide_test(5, 25)
Logging.line_separator("WHOLE NUMBER TEST", 40, '+')
divide_test(25, 5)
Logging.line_separator("DIVIDE BY ZERO TEST", 40, '+')
divide_test(5, 0)
def divide_test(denominator, numerator):
"""Perform division tests using all different numeric types and mathematic libraries.:param denominator: Denominator.
:param numerator: Numerator.
"""
Logging.line_separator('as int')
Logging.log(divide(denominator, numerator))Logging.line_separator('as float')
Logging.log(divide(denominator, numerator, NumberType.FLOAT))Logging.line_separator('as decimal.Decimal')
Logging.log(divide(denominator, numerator, NumberType.DECIMAL))Logging.line_separator('as mpmath.mpf')
Logging.log(divide(denominator, numerator, NumberType.MPMATH))def divide(numerator, denominator, lib: NumberType = NumberType.INTEGER):
"""Get result of division of numerator and denominator, using passed numeric type or library.:param numerator: Numerator.
:param denominator: Denominator.
:param lib: Type of numeric value or library to use for calculation.
:return: Division result.
"""
try:
if lib == NumberType.INTEGER:
# Divide using standard integer.
return numerator / denominator
elif lib == NumberType.FLOAT:
# Convert to floats before division.
return float(numerator) / float(denominator)
elif lib == NumberType.DECIMAL:
# Divide the decimal.Decimal value.
return decimal.Decimal(numerator) / decimal.Decimal(denominator)
elif lib == NumberType.MPMATH:
# Divide using the mpmath.mpf (real float) value.
return mpf(numerator) / mpf(denominator)
else:
# Divide using standard integer (default).
return numerator / denominator
except ZeroDivisionError as error:
# Output expected ZeroDivisionErrors.
Logging.log_exception(error)
except Exception as exception:
# Output unexpected Exceptions.
Logging.log_exception(exception, False)if __name__ == "__main__":
main()
This code sample also uses the Logging
utility class, the source of which can be found here on GitHub.
When Should You Use It?
The appearance of a ZeroDivisionError
is never really surprising -- it just indicates that, somewhere in your code, a calculation took place and the denominator where zero. Thus, we'll dive right into the sample code to look at how these errors slightly different depending on exactly what types of numeric values we're using.
We start with the NumeerType(Enum)
, which we'll use throughout the code to differentiate between the various numeric types and mathematical libraries we'll be using, including int
, float
, decimal.Decimal
, and mpmath.mpf
:
class NumberType(Enum):
"""Specifies number type or library used for calculating values."""
INTEGER = 1
FLOAT = 2
DECIMAL = 3
MPMATH = 4
Our divide(numerator, denominator, lib: NumberType = NumberType.INTEGER)
method is where the majority of our logic and calculations take place:
def divide(numerator, denominator, lib: NumberType = NumberType.INTEGER):
"""Get result of division of numerator and denominator, using passed numeric type or library.
:param numerator: Numerator.
:param denominator: Denominator.
:param lib: Type of numeric value or library to use for calculation.
:return: Division result.
"""
try:
if lib == NumberType.INTEGER:
# Divide using standard integer.
return numerator / denominator
elif lib == NumberType.FLOAT:
# Convert to floats before division.
return float(numerator) / float(denominator)
elif lib == NumberType.DECIMAL:
# Divide the decimal.Decimal value.
return decimal.Decimal(numerator) / decimal.Decimal(denominator)
elif lib == NumberType.MPMATH:
# Divide using the mpmath.mpf (real float) value.
return mpf(numerator) / mpf(denominator)
else:
# Divide using standard integer (default).
return numerator / denominator
except ZeroDivisionError as error:
# Output expected ZeroDivisionErrors.
Logging.log_exception(error)
except Exception as exception:
# Output unexpected Exceptions.
Logging.log_exception(exception, False)
We start by checking for the lib
parameter value, which determines what type the numerator
and denominator
should be converted to before the calculation is performed. We also catch possible errors and exceptions within this method.
The divide_test(denominator, numerator)
method performs a series of calls to the divide(...)
method above, ensuring we test each of the four different numeric types at least once for each set of passed denominator
and numerator
pairs:
def divide_test(denominator, numerator):
"""Perform division tests using all different numeric types and mathematic libraries.:param denominator: Denominator.
:param numerator: Numerator.
"""
Logging.line_separator('as int')
Logging.log(divide(denominator, numerator))Logging.line_separator('as float')
Logging.log(divide(denominator, numerator, NumberType.FLOAT))Logging.line_separator('as decimal.Decimal')
Logging.log(divide(denominator, numerator, NumberType.DECIMAL))
Logging.line_separator('as mpmath.mpf')
Logging.log(divide(denominator, numerator, NumberType.MPMATH))
Alright. Everything is setup so now we'll perform a few basic tests within our main()
method:
def main():
Logging.line_separator("FRACTION TEST", 40, '+')divide_test(5, 25)
Logging.line_separator("WHOLE NUMBER TEST", 40, '+')
divide_test(25, 5)
Logging.line_separator("DIVIDE BY ZERO TEST", 40, '+')
divide_test(5, 0)
Nothing fancy going on here. We want to perform tests that should result in a fractional (decimal) number, a whole (integer) number, and an attempt to divide by zero. Executing the code above produces the following output:
++++++++++++ FRACTION TEST +++++++++++++
---------------- as int ----------------
0.2
--------------- as float ---------------
0.2
---------- as decimal.Decimal ----------
0.2
------------ as mpmath.mpf -------------
0.2++++++++++ WHOLE NUMBER TEST +++++++++++
---------------- as int ----------------
5.0
--------------- as float ---------------
5.0
---------- as decimal.Decimal ----------
5
------------ as mpmath.mpf -------------
5.0
+++++++++ DIVIDE BY ZERO TEST ++++++++++
---------------- as int ----------------
[EXPECTED] ZeroDivisionError: division by zero
None
--------------- as float ---------------
[EXPECTED] ZeroDivisionError: float division by zero
None
---------- as decimal.Decimal ----------
[EXPECTED] DivisionByZero: [<class 'decimal.DivisionByZero'>]
None
------------ as mpmath.mpf -------------
[EXPECTED] ZeroDivisionError:
None
The fraction test isn't too surprising; every type test produced the exact same result of 0.2
. The whole number test shows a slight difference in how the decimal
library handles the result, truncating the insignificant digit (likely because it attempts to convert to an int
after calculation, whereas other methods retain a float
value).
What's most interesting is the divide by zero results. Performing the calculation using plain ints
results in a ZeroDivisionError
with a division by zero
message, while the same calculation with float
values changes the message to float division by zero
. The decimal
library has its own exception type of DivisionByZero
, which inherits from the built-in ZeroDivisionError
(we know this because our except ZeroDivisionError
statement caught it). Finally, mpmath.mpf
values are effectively floats
, except we see that the resulting ZeroDivisionError
doesn't include an error message of any kind (i.e. the error
value's args
tuple is empty).
Airbrake's robust error monitoring software provides real-time error monitoring and automatic exception reporting for all your development projects. Airbrake's state of the art web dashboard ensures you receive round-the-clock status updates on your application's health and error rates. No matter what you're working on, Airbrake easily integrates with all the most popular languages and frameworks. Plus, Airbrake makes it easy to customize exception parameters, while giving you complete control of the active error filter system, so you only gather the errors that matter most.
Check out Airbrake's error monitoring software today with a free 14-day trial, and see for yourself why so many of the world's best engineering teams use Airbrake to revolutionize their exception handling practices!