# What are Generators | Advance Python

# Starting Note
Before reading this blog, I want to make a clear statement that this blog is written completely based on my understanding about how generators work in python. To move forward, you must be an intermediate in python.
# Iterators
An object that contains countable number of values. The next value can be accessed with the `next(object_name)` parameter

You can know more about iterators here: [Iterators by W3 Schools](https://www.w3schools.com/python/python_iterators.asp)
# Case
Let's say you are working in a company and your company gives you a task to write a program to find a series of numbers. Each number having a difference of 12 from the previous. So the series is like: [12,24,36,48.....]

You have to find the series till a number given by the user and store the results somewhere so that others get those values using a loop. 

So, if the user enters 10, the program will return a series like: **12,24,36,........,120**

You will be like: *Okay that's easy*
```
def gen_series():
    n = int(input("What's n? "))
    a = []
    d = 11
    for i in range(1, n+1):
        a.append(i + d)
        d += 11
    return a

if __name__ == "__main__":
    print(gen_series())
```

Now, your program works nicely if the user inputs numbers like 12, 23, 89, 74.

If you test this code yourself and you enter a number like 1000000000000000 then what happens?
Your code starts to hang, the program doesn't work smoothly. So, this is not the best approach to get a series of numbers.

**Let's refactor this code a bit**
```
def gen_series():
    n = int(input("What's n? "))
    a = series(n)
    print(a)

def series(n):
    a = []
    d = 11
    for i in range(1, n+1):
        a.append(i + d)
        d += 11
    return a

if __name__ == "__main__":
    gen_series()
```
Now, the program runs the same but our problem is not yet solved. We want to generate a series of numbers with each number having a difference of 12 from the previous and we want our program to generate as much numbers as we want.

Try to run this code:
```
def gen_series():
    n = int(input("What's n? "))
    a = series(n)
    print(a)


def series(n):
    d = 11
    for i in range(1, n+1):
        yield i + d
        d += 11


if __name__ == "__main__":
    gen_series()
```
If you run this code and input a number as 20, you will see the output as `<generator object series at 0x0000020B20684350>`. Now, if you run again, and input a big number such as 1000000000000000, you will notice that your program doesn't lag. It works perfectly fine and you get the output.

The `yield` keyword is responsible to make this function a generator.

The `series` function is now a generator function which generates an iterator. If you have read the article of [W3 Schools](https://www.w3schools.com/python/python_iterators.asp), you will certainly know that we can easily access the values from an iterator using a loop. 

So, here's the definition of generators
### Generators
Generators are those kind of functions that return an iterator.

# Back to program
Now to print the values of the series we can use another function
```
def print_series():
    a = gen_series()
    for i in a:
        print(i)
```

The complete code:
```
def gen_series():
    n = int(input("What's n? "))
    a = series(n)
    print(a)
    return a


def print_series():
    a = gen_series()
    for i in a:
        print(i)


def series(n):
    d = 11
    for i in range(1, n+1):
        yield i + d
        d += 11


if __name__ == "__main__":
    print_series()
```

# Conclusion
- Generators are functions that return an iterator
- We can turn a normal function to generator by using the `yield` keyword


**If you didn't understand then don't forget to criticize me in the comments. That will help me to come up with a better explanation. It is okay to criticize me if I have wasted your time. Also if you have any questions, please ask me in the comments**
