# Iterators

An iterator in Python is an object that implements the iterator protocol, which consists of the `__iter__` and `__next__` methods. The `__iter__` method returns the iterator object itself, while the `__next__` method returns the next value from the iterator. The `StopIteration` exception is raised when there are no more values to return.

Iterators are used to iterate over a sequence of values, one at a time, without having to load the entire sequence into memory. This makes them very useful for working with large data sets or with data that is generated on-the-fly.

Python provides several built-in iterators, including the `list` iterator, which allows you to iterate over a list of values, the `range` iterator, which allows you to iterate over a sequence of numbers, and the `map` and `filter` iterators, which allow you to apply functions to values in a sequence.

You can also create your own iterators by defining a class that implements the iterator protocol.&#x20;

## Creating Iterators in Python&#x20;

In Python, we can create an iterator using two methods:

1. **iter**() method: This method returns the iterator object itself. It is called when we create an iterator object.
2. **next**() method: This method returns the next value from the iterator. It raises a StopIteration exception if there are no more items to return.

Here's an example of creating an iterator that returns numbers from 0 to 4:

```python
class MyIterator:
    def __init__(self):
        self.numbers = [0, 1, 2, 3, 4]
        self.current = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= len(self.numbers):
            raise StopIteration
        else:
            value = self.numbers[self.current]
            self.current += 1
            return value

# Using the iterator
my_iterator = MyIterator()
for num in my_iterator:
    print(num)
```

Output:

```python
0
1
2
3
4
```

In the above example, we have created an iterator using the `MyIterator` class. The `__iter__()` method returns the iterator object itself, and the `__next__()` method returns the next value from the iterator. We have raised the `StopIteration` exception when there are no more items to return.

We can use the created iterator using a `for` loop, which internally calls the `__next__()` method of the iterator until it raises a `StopIteration` exception.&#x20;

## Looping Through Iterators&#x20;

Once an iterator object is created, you can use it to traverse through a sequence.&#x20;

### a. Using the for Loop&#x20;

The `for` loop in Python is commonly used to iterate over a sequence of elements, such as a list or a tuple. However, it can also be used to iterate over an iterator.

Here's an example of using the `for` loop with an iterator:

```python
my_list = [1, 2, 3]
my_iterator = iter(my_list)

for i in my_iterator:
    print(i)
```

In this example, `my_list` is a list of integers, and `my_iterator` is an iterator created from that list using the `iter()` function. The `for` loop then iterates over each element in the iterator, which in turn iterates over each element in the list, printing each element to the console.

It's worth noting that the `for` loop automatically handles the `StopIteration` exception that is raised when there are no more elements to iterate over in the iterator.

### b. Using the while Loop&#x20;

To loop through an iterator using the `while` loop in Python, you can use the `next()` function to get the next item from the iterator and loop until the iterator is exhausted, raising the `StopIteration` exception.

Here's an example:

```python
my_list = [1, 2, 3]
my_iterator = iter(my_list)

while True:
    try:
        item = next(my_iterator)
        print(item)
    except StopIteration:
        break
```

In this example, `my_list` is a list of integers, and we create an iterator `my_iterator` from this list using the `iter()` function. We then use a `while` loop to repeatedly call the `next()` function on the iterator until the `StopIteration` exception is raised, indicating that the iterator is exhausted. Each time through the loop, we print the current item from the iterator. The output of this program will be:

```python
1
2
3
```

## Built-in Iterators in Python&#x20;

Python has several built-in iterators that can be used to loop through different data types. Some of these iterators include:

1. range() - Used to loop through a sequence of numbers.
2. enumerate() - Used to loop through an iterable and get the index and value of each item.
3. zip() - Used to combine two or more iterables and loop through them together.
4. reversed() - Used to loop through a sequence in reverse order.
5. sorted() - Used to loop through a sequence in sorted order.
6. filter() - Used to loop through an iterable and filter out items that don't meet a certain condition.
7. map() - Used to loop through an iterable and apply a function to each item.

These built-in iterators can make it easier to work with different data types and perform complex operations on them.

## Python Infinite Iterators <a href="#infinite" id="infinite"></a>

Infinite iterators are iterators that generate an infinite sequence of values. They are useful in situations where the number of items to be generated is unknown or unlimited. Python provides several built-in infinite iterators, which we can use to create infinite loops. The most commonly used infinite iterators in Python are:

1. count() - This iterator generates an infinite sequence of numbers, starting from a given number and incrementing by a given step size. By default, it starts from 0 and increments by 1.
2. cycle() - This iterator generates an infinite sequence by cycling through the elements of an iterable. It repeats the elements in a loop until it is stopped.
3. repeat() - This iterator generates an infinite sequence by repeating a single value a given number of times or infinitely.

We can use these iterators in combination with other iterators and loop constructs to create interesting and useful programs. However, it is important to be careful when working with infinite iterators as they can potentially create an infinite loop and crash the program if not used correctly.<br>

Here is an example of an infinite iterator in Python:

```python
# Define an infinite iterator that counts from 1 to infinity
def infinite_counter():
    count = 1
    while True:
        yield count
        count += 1

# Create an instance of the infinite iterator
counter = infinite_counter()

# Loop through the iterator and print the first 10 values
for i in range(10):
    print(next(counter))
```

This code defines an infinite iterator called `infinite_counter` that counts from 1 to infinity. It uses the `yield` keyword to return each value as it's generated, and then increments the count by 1. The `for` loop then uses the `next()` function to retrieve the next value from the iterator and print it to the console. Since the iterator is infinite, this loop will continue indefinitely unless it's interrupted manually.

<br>
