# Iterators

* In Python, an iterator is an object that enables traversal over a collection of items, one at a time.
    
* It follows the iterator protocol, which involves implementing the `__iter__()` and `__next__()` methods.
    
* The `__iter__()` method returns the iterator object itself, while `__next__()` retrieves the next element from the collection.
    
* When there are no more elements, a `StopIteration` exception is raised.
    

### Iterating over lists

**Approach -1: Iterating over the list directly**

```python
list = [2, 4, 6, 8, 10]
for value in list:
    print(value, end=' ')
#Output = 2 4 6 8 10
```

* In this approach, the entire list `[2, 4, 6, 8, 10]` is stored in memory.
    
* The `for` loop iterates over each element of the list, processing them one by one.
    
* Memory usage is relatively higher since the complete list is stored in memory throughout the loop execution.
    

**Approach -2:** **Using an iterator object**

```python
list = [2, 4, 6, 8, 10]
iter_obj = iter(list) # <list_iterator object at 0x7fcf3519f670>
print(next(iter_obj)) # Output = 2
print(next(iter_obj))# Output = 4
print(next(iter_obj))# Output = 6
print(next(iter_obj))# Output = 8
print(next(iter_obj))# Output = 10
```

* In this approach, an iterator object is explicitly created using `iter(list)`.
    
* Each call to `next(iter_obj)` retrieves the next element from the iterator, without storing the entire list in memory.
    
* Only one element at a time is accessed and processed, reducing memory usage.
    
* As elements are fetched one by one, the memory footprint is lower compared to
    

### Built-in Iterators in Python

Python provides several built-in iterators and iterable objects that simplify common iteration tasks. Let's explore a few of them:

1. **range()**
    
    The `range()` function generates a sequence of numbers within a specified range. It can be used directly as an iterator.
    
    ```python
    for i in range(5):
        print(i,end = " ")
    #Output = 0 1 2 3 4 5
    ```
    
2. **enumerate()**
    
    The `enumerate()` function is useful when we need both the index and value of each element while iterating over a sequence.
    
    ```python
    my_list = ['Nirmal', 'Niraj', 'Retrica']
    for index, value in enumerate(my_list):
        print(f"Index: {index}, Value: {value}")
    ```
    
    **Output :**
    
    ```python
    Index: 0, Value: nirmal
    Index: 1, Value: niraj
    Index: 2, Value: retrica
    ```
    
3. `zip()`:
    
    The `zip()` function combines multiple iterables into a single iterator. It pairs up corresponding elements from each iterable.
    
    ```python
    names =  ['Nirmal', 'Niraj', 'Retrica']
    ages = [23, 20, 27]
    
    for name, age in zip(names, ages):
        print(f"Name: {name}, Age: {age}")
    ```
    
    **Output :**
    
    ```python
    Name: Nirmal, Age: 23
    Name: Niraj, Age: 20
    Name: Retrica, Age: 27
    ```
    

### Itertools Module

The `itertools` module is a powerful tool that provides various functions for efficient iteration and a combination of iterables. It extends the capabilities of built-in iterators.

**Some modules are :**

1\. `itertools.cycle()`

This creates an iterator that cycles through elements of an iterable indefinitely.

```python
import itertools

name = ["nirmal", "niraj", "retrica"]
myloopy = itertools.cycle(name)

print(next(myloopy))  # Output: nirmal
print(next(myloopy))  # Output: niraj
print(next(myloopy))  # Output: retrica
```

2\. `itertools.chain()`

This combines multiple iterables into a single sequence.

```python
import itertools

inter1 = itertools.chain("nirmal", "pandey")
print(list(inter1))  # Output: ['n', 'i', 'r', 'm', 'a', 'l', 'p', 'a', 'n', 'd', 'e', 'y']
```

3. `itertools. accumulate()`

This returns an iterator that produces accumulated sums of an iterable.

```python
import itertools

num = [1, 2, 3, 4, 5, 6]
inter2 = itertools.accumulate(num)
print(list(inter2))  # Output: [1, 3, 6, 10, 15, 21]
```

4.`itertools.repeat()`

This generates an iterator that repeats a value a specified number of times.

```python
import itertools

inter3 = itertools.repeat(10, 3)
print(list(inter3))  # Output: [10, 10, 10]
```

5: `itertools.compress()`

This filters an iterable based on the truth value of corresponding selectors.

```python
import itertools

inter4 = itertools.compress("nirmal", [1, 0, 1, 0, 1, 1])
print(list(inter4))  # Output: ['n', 'r', 'a', 'l']
```

### **Custom Iterator**

```python
class MyIterator:
    def __init__(self, data):
        self.data = data
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index >= len(self.data):
            raise StopIteration
        value = self.data[self.index]
        self.index += 1
        return value


my_list = [1, 2, 3, 4, 5]
my_iter = MyIterator(my_list)

for item in my_iter:
    print(item)
```

In this example, we create a custom iterator `MyIterator` takes a list as input. The `__iter__()` method returns the iterator object itself, and `__next__()` fetches the next element from the list. Once there are no more elements, a `StopIteration` exception is raised.
