# Generator Function in Python

In Python, a generator function is a special kind of function that generates a sequence of values one at a time. These values can be generated on the fly, without having to store them in memory all at once. This can be especially useful when working with large data sets or when you don’t know ahead of time how many values you’ll need to generate.

Generator functions are defined using the `'yield'`

keyword instead of `'return'`

. When a generator function is called, it returns a generator object. This object can then be used to iterate over the sequence of values that the generator function produces.

Let’s take a look at a simple example of a generator function in action:

```
def count_up_to(n):
i = 0
while i < n:
yield i
i += 1
```

This generator function, `'count_up_to'`

, takes a single argument, `'n'`

, and generates a sequence of numbers from 0 up to (but not including) `'n'`

. The `'yield'`

keyword is used to produce each value in the sequence.

To use this generator function, we can call it and store the resulting generator object in a variable:

```
my_generator = count_up_to(5)
```

We can then iterate over the values produced by the generator using a for loop or a list comprehension:

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

This will output:

```
0
1
2
3
4
```

Notice that the generator function does not produce the value `'5'`

. This is because the `'while'`

loop in the function terminates before reaching `'n'`

. If we wanted to include n in the sequence, we would need to change the condition in the `'while'`

loop to `'i <= n'`

.

Another useful feature of generator functions is that they can be infinite. That is, they can produce an endless sequence of values without ever terminating. Here’s an example of an infinite generator function:

```
def infinite_sequence():
num = 0
while True:
yield num
num += 1
```

This generator function, `'infinite_sequence'`

, simply produces an infinite sequence of numbers, starting from 0 and counting up to infinity. To use this generator function, we can create a generator object and iterate over its values:

```
my_generator = infinite_sequence()
for i in my_generator:
print(i)
```

This will continue outputting numbers indefinitely.

One more thing to note is that generator functions can be composed, just like regular functions. That is, you can pass the output of one generator function as input to another generator function. Here’s an example of two generator functions that are composed together:

```
def evens_up_to(n):
for i in range(n):
if i % 2 == 0:
yield i
def squared(nums):
for num in nums:
yield num ** 2
```

The `'evens_up_to'`

generator function produces a sequence of even numbers up to (but not including) `'n'`

. The squared generator function takes a sequence of numbers and produces the square of each number in the sequence. We can compose these two functions together by passing the output of `'evens_up_to'`

as input to `'squared'`

:

```
my_generator = squared(evens_up_to(10))
for i in my_generator:
print(i)
```

This will output:

```
0
4
16
36
64
```

In summary, generator functions in Python are a powerful tool for generating sequences of values on the fly. They can be used to work with large data sets or when you don’t know ahead of time