Python numbers Module

In this guide, you'll explore Python's numbers module, which defines numeric abstract base classes. Learn its features and examples for type hierarchy.

The numbers module in Python provides a hierarchy of abstract base classes (ABCs) to define and test numeric types. This module is useful for creating your own numeric types and ensuring that they conform to the behavior expected of numbers.

Table of Contents

  1. Introduction
  2. Key Classes in numbers
    • Number
    • Complex
    • Real
    • Rational
    • Integral
  3. Examples
    • Checking Numeric Types
    • Creating Custom Numeric Types
  4. Real-World Use Case
  5. Conclusion
  6. References

Introduction

The numbers module defines a set of abstract base classes that describe the various categories of numbers in Python. These categories range from the most general, Number, to the most specific, Integral. These classes provide a way to define numeric types in a consistent manner and to check if an object is of a particular numeric type.

Key Classes in numbers

Number

The most general ABC for numbers. All other numeric ABCs are derived from this class. It is not intended to be instantiated directly.

import numbers

print(isinstance(5, numbers.Number))  # True
print(isinstance(3.14, numbers.Number))  # True

Output:

True
True

Complex

Represents complex numbers. All numbers in Python (including integers and floats) are complex numbers.

import numbers

print(isinstance(5, numbers.Complex))  # True
print(isinstance(3.14, numbers.Complex))  # True
print(isinstance(1+2j, numbers.Complex))  # True

Output:

True
True
True

Real

Represents real numbers. This includes both integers and floating-point numbers, but not complex numbers.

import numbers

print(isinstance(5, numbers.Real))  # True
print(isinstance(3.14, numbers.Real))  # True
print(isinstance(1+2j, numbers.Real))  # False

Output:

True
True
False

Rational

Represents rational numbers. These are numbers that can be expressed as the quotient of two integers. In Python, this includes integers and fractions.

from fractions import Fraction
import numbers

print(isinstance(5, numbers.Rational))  # True
print(isinstance(Fraction(1, 3), numbers.Rational))  # True
print(isinstance(3.14, numbers.Rational))  # False

Output:

True
True
False

Integral

Represents integral numbers. These are whole numbers, including both positive and negative integers, as well as zero.

import numbers
print(isinstance(5, numbers.Integral))  # True
print(isinstance(-3, numbers.Integral))  # True
print(isinstance(3.14, numbers.Integral))  # False

Output:

True
True
False

Examples

Checking Numeric Types

Use the isinstance function to check if a value is an instance of a particular numeric type.

import numbers

print(isinstance(5, numbers.Number))  # True
print(isinstance(5, numbers.Complex))  # True
print(isinstance(5, numbers.Real))  # True
print(isinstance(5, numbers.Rational))  # True
print(isinstance(5, numbers.Integral))  # True
print(isinstance(3.14, numbers.Integral))  # False

Output:

True
True
True
True
True
False

Creating Custom Numeric Types

You can create custom numeric types by subclassing one of the abstract base classes provided by the numbers module.

import numbers

class MyNumber(numbers.Real):
    def __init__(self, value):
        self.value = value

    def __repr__(self):
        return f"MyNumber({self.value})"

    def __float__(self):
        return float(self.value)

    def __int__(self):
        return int(self.value)

    def __hash__(self):
        return hash(self.value)

    def __trunc__(self):
        return int(self.value)

    def __floor__(self):
        return int(self.value // 1)

    def __ceil__(self):
        return int(-(-self.value // 1))

    def __round__(self, ndigits=None):
        return MyNumber(round(self.value, ndigits))

    def __add__(self, other):
        if isinstance(other, MyNumber):
            return MyNumber(self.value + other.value)
        return MyNumber(self.value + other)

    def __sub__(self, other):
        if isinstance(other, MyNumber):
            return MyNumber(self.value - other.value)
        return MyNumber(self.value - other)

    def __mul__(self, other):
        if isinstance(other, MyNumber):
            return MyNumber(self.value * other.value)
        return MyNumber(self.value * other)

    def __truediv__(self, other):
        if isinstance(other, MyNumber):
            return MyNumber(self.value / other.value)
        return MyNumber(self.value / other)

    def __floordiv__(self, other):
        if isinstance(other, MyNumber):
            return MyNumber(self.value // other.value)
        return MyNumber(self.value // other)

    def __mod__(self, other):
        if isinstance(other, MyNumber):
            return MyNumber(self.value % other.value)
        return MyNumber(self.value % other)

    def __pow__(self, exponent):
        if isinstance(exponent, MyNumber):
            return MyNumber(self.value ** exponent.value)
        return MyNumber(self.value ** exponent)

    def __abs__(self):
        return MyNumber(abs(self.value))

    def __neg__(self):
        return MyNumber(-self.value)

    def __pos__(self):
        return MyNumber(+self.value)

    def __eq__(self, other):
        if isinstance(other, MyNumber):
            return self.value == other.value
        return self.value == other

    def __lt__(self, other):
        if isinstance(other, MyNumber):
            return self.value < other.value
        return self.value < other

    def __le__(self, other):
        if isinstance(other, MyNumber):
            return self.value <= other.value
        return self.value <= other

    def __gt__(self, other):
        if isinstance(other, MyNumber):
            return self.value > other.value
        return self.value > other

    def __ge__(self, other):
        if isinstance(other, MyNumber):
            return self.value >= other.value
        return self.value >= other

    def __radd__(self, other):
        return self.__add__(other)

    def __rsub__(self, other):
        return MyNumber(other).__sub__(self)

    def __rmul__(self, other):
        return self.__mul__(other)

    def __rtruediv__(self, other):
        return MyNumber(other).__truediv__(self)

    def __rfloordiv__(self, other):
        return MyNumber(other).__floordiv__(self)

    def __rmod__(self, other):
        return MyNumber(other).__mod__(self)

    def __rpow__(self, other):
        return MyNumber(other).__pow__(self)

# Example usage
a = MyNumber(3)
b = MyNumber(4)

print(a + b)          # MyNumber(7)
print(a * b)          # MyNumber(12)
print(a ** 2)         # MyNumber(9)
print(abs(MyNumber(-5)))  # MyNumber(5)
print(a + 2)          # MyNumber(5)
print(2 + a)          # MyNumber(5)
print(a // 2)         # MyNumber(1)
print(a % 2)          # MyNumber(1)
print(2 // a)         # MyNumber(0)
print(2 % a)          # MyNumber(2)
print(round(a, 1))    # MyNumber(3)

Output:

MyNumber(7)
MyNumber(12)
MyNumber(9)
MyNumber(5)
MyNumber(5)
MyNumber(5)
MyNumber(1)
MyNumber(1)
MyNumber(0)
MyNumber(2)
MyNumber(3)

Real-World Use Case

Custom Numeric Operations

In scientific computing or financial applications, you might need custom numeric types that adhere to specific rules or provide additional functionality. Using the numbers module, you can create these custom numeric types while ensuring they conform to the expected behavior of standard numeric types.

import numbers

class Currency(numbers.Real):
    def __init__(self, value):
        self.value = value

    def __repr__(self):
        return f"Currency({self.value:.2f})"

    def __float__(self):
        return float(self.value)

    def __int__(self):
        return int(self.value)

    def __hash__(self):
        return hash(self.value)

    def __trunc__(self):
        return int(self.value)

    def __floor__(self):
        return int(self.value // 1)

    def __ceil__(self):
        return int(-(-self.value // 1))

    def __round__(self, ndigits=None):
        return Currency(round(self.value, ndigits))

    def __add__(self, other):
        if isinstance(other, numbers.Real):
            return Currency(self.value + float(other))
        return NotImplemented

    def __sub__(self, other):
        if isinstance(other, numbers.Real):
            return Currency(self.value - float(other))
        return NotImplemented

    def __mul__(self, other):
        if isinstance(other, numbers.Real):
            return Currency(self.value * float(other))
        return NotImplemented

    def __truediv__(self, other):
        if isinstance(other, numbers.Real):
            return Currency(self.value / float(other))
        return NotImplemented

    def __floordiv__(self, other):
        if isinstance(other, numbers.Real):
            return Currency(self.value // float(other))
        return NotImplemented

    def __mod__(self, other):
        if isinstance(other, numbers.Real):
            return Currency(self.value % float(other))
        return NotImplemented

    def __pow__(self, exponent):
        if isinstance(exponent, numbers.Real):
            return Currency(self.value ** float(exponent))
        return NotImplemented

    def __abs__(self):
        return Currency(abs(self.value))

    def __neg__(self):
        return Currency(-self.value)

    def __pos__(self):
        return Currency(+self.value)

    def __eq__(self, other):
        if isinstance(other, numbers.Real):
            return self.value == float(other)
        return NotImplemented

    def __lt__(self, other):
        if isinstance(other, numbers.Real):
            return self.value < float(other)
        return NotImplemented

    def __le__(self, other):
        if isinstance(other, numbers.Real):
            return self.value <= float(other)
        return NotImplemented

    def __gt__(self, other):
        if isinstance(other, numbers.Real):
            return self.value > float(other)
        return NotImplemented

    def __ge__(self, other):
        if isinstance(other, numbers.Real):
            return self.value >= float(other)
        return NotImplemented

    def __radd__(self, other):
        return self.__add__(other)

    def __rsub__(self, other):
        return Currency(float(other)).__sub__(self)

    def __rmul__(self, other):
        return self.__mul__(other)

    def __rtruediv__(self, other):
        return Currency(float(other)).__truediv__(self)

    def __rfloordiv__(self, other):
        return Currency(float(other)).__floordiv__(self)

    def __rmod__(self, other):
        return Currency(float(other)).__mod__(self)

    def __rpow__(self, other):
        return Currency(float(other)).__pow__(self)

# Example usage
salary = Currency(5000.75)
bonus = Currency(1200.50)

total_income = salary + bonus
print(total_income)  # Currency(6201.25)

Output:

Currency(6201.25)

Conclusion

The numbers module in Python provides a robust framework for defining and working with numeric types. By using the abstract base classes in this module, you can ensure that your custom numeric types adhere to the expected behaviors of standard numeric types, making your code more consistent and reliable.

References

Comments

Spring Boot 3 Paid Course Published for Free
on my Java Guides YouTube Channel

Subscribe to my YouTube Channel (165K+ subscribers):
Java Guides Channel

Top 10 My Udemy Courses with Huge Discount:
Udemy Courses - Ramesh Fadatare