Python 101: Understanding the Python `range()` Function

The range() function in Python generates a sequence of numbers. It’s commonly used for looping a specific number of times in for loops. While the range() function appears straightforward, its implementation is a bit more complex.

Here’s a detailed look at how the range() function is implemented and how it works.

Source Code of range() Function

The range() function is implemented in C in the CPython interpreter for performance reasons. Below is a simplified Python version that mimics the functionality of the range() function.

Simplified Python Implementation of range()

class Range:
    def __init__(self, start, stop=None, step=1):
        if stop is None:
            self.start = 0
            self.stop = start
        else:
            self.start = start
            self.stop = stop

        self.step = step

        if step == 0:
            raise ValueError("range() arg 3 must not be zero")
        if not isinstance(start, int) or not isinstance(stop, int) or not isinstance(step, int):
            raise TypeError("range() integers")

    def __iter__(self):
        return RangeIterator(self.start, self.stop, self.step)

    def __repr__(self):
        return f"Range({self.start}, {self.stop}, {self.step})"

class RangeIterator:
    def __init__(self, start, stop, step):
        self.current = start
        self.stop = stop
        self.step = step

    def __iter__(self):
        return self

    def __next__(self):
        if (self.step > 0 and self.current >= self.stop) or (self.step < 0 and self.current <= self.stop):
            raise StopIteration
        current = self.current
        self.current += self.step
        return current

# Example usage
for i in Range(1, 10, 2):
    print(i)  # Output: 1, 3, 5, 7, 9

Detailed Explanation

  1. Initialization (__init__ method):

    • The Range class is initialized with start, stop, and step parameters.
    • If stop is None, then start is set to 0, and stop takes the value of the start parameter.
    • Validates that step is not zero and all arguments are integers.
    def __init__(self, start, stop=None, step=1):
        if stop is None:
            self.start = 0
            self.stop = start
        else:
            self.start = start
            self.stop = stop
    
        self.step = step
    
        if step == 0:
            raise ValueError("range() arg 3 must not be zero")
        if not isinstance(start, int) or not isinstance(stop, int) or not isinstance(step, int):
            raise TypeError("range() integers")
  2. Iterator Protocol:

    • The __iter__ method returns an iterator object, RangeIterator.
    • The RangeIterator class implements the iterator protocol with __iter__ and __next__ methods.
    def __iter__(self):
        return RangeIterator(self.start, self.stop, self.step)
  3. Representation (__repr__ method):

    • The __repr__ method provides a string representation of the Range object.
    def __repr__(self):
        return f"Range({self.start}, {self.stop}, {self.step})"
  4. RangeIterator Class:

    • RangeIterator class is responsible for the actual iteration.
    • The __next__ method returns the next value in the sequence or raises StopIteration when the sequence is exhausted.
    class RangeIterator:
        def __init__(self, start, stop, step):
            self.current = start
            self.stop = stop
            self.step = step
    
        def __iter__(self):
            return self
    
        def __next__(self):
            if (self.step > 0 and self.current >= self.stop) or (self.step < 0 and self.current <= self.stop):
                raise StopIteration
            current = self.current
            self.current += self.step
            return current

Practical Example

for i in Range(1, 10, 2):
    print(i)  # Output: 1, 3, 5, 7, 9

Key Points

  • Initialization: Handles different parameter combinations (start, stop, step) and validates inputs.
  • Iterator Protocol: Implements the __iter__ and __next__ methods to conform to the iterator protocol.
  • Efficient Iteration: Uses a separate RangeIterator class to handle iteration efficiently.

Conclusion

The range() function in Python is a powerful and efficient way to generate sequences of numbers. Understanding its implementation helps appreciate how Python handles iteration and provides insights into writing custom iterators.

By examining this simplified implementation, we can see how the range() function initializes parameters, validates input, and iterates over a sequence of numbers, all while conforming to Python's iterator protocol.


Recommend Resources:

range() function

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *