# Generators & yield — Python Fundamentals: From Basics to Async

Source: https://www.skillbyai.com/en/python/py-generators

> Produce values lazily with yield and generator expressions to process huge sequences with little memory.

## A generator function

`yield` pauses a function and hands back a value; the function resumes right there on the next call — no need to build the whole list in memory.

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

for num in count_up_to(5):
    print(num)          # 1 2 3 4 5, one at a time
```

## Generator expressions

A **generator expression** looks like a list comprehension with `()` instead of `[]`, and produces values lazily too.

```python
squares = (n * n for n in range(1_000_000))  # nothing computed yet
print(next(squares))   # 0
print(next(squares))   # 1
print(sum(n * n for n in range(10)))   # 285
```

**Quiz:** Why prefer a generator over a list for a huge sequence?

- [ ] It runs faster in every case
- [ ] It automatically sorts the items
- [x] It produces items lazily instead of storing them all in memory
- [ ] It always uses more memory

*Answer:* It produces items lazily instead of storing them all in memory. Generators compute one value at a time on demand, so huge or infinite sequences don't need to fit in memory.
