# Python List Comprehensions and Generators

List comprehensions provide a concise way to create lists. Generators provide a way to iterate over data without storing it all in memory.

## List Comprehensions

List comprehension offers a shorter syntax when you want to create a new list based on the values of an existing list.

### Syntax

```python
# newlist = [expression for item in iterable if condition == True]
```

### Examples

```python
fruits = ["apple", "banana", "cherry", "kiwi", "mango"]

# Create a new list containing only fruits with the letter "a"
newlist = [x for x in fruits if "a" in x]

print(newlist)
# Output: ['apple', 'banana', 'mango']
```

### With Conditions

The expression can also contain conditions (ternary operator).

```python
# Return "orange" instead of "banana"
newlist = [x if x != "banana" else "orange" for x in fruits]
```

## Generator Expressions

Generator expressions are similar to list comprehensions, but instead of creating a list, they return a generator object. They use parentheses `()` instead of brackets `[]`.

### Memory Efficiency

Generators are memory efficient because they yield items one by one rather than creating the entire list in memory.

```python
# List comprehension (creates full list in memory)
my_list = [x * x for x in range(1000000)]

# Generator expression (returns an object)
my_gen = (x * x for x in range(1000000))

import sys
print(sys.getsizeof(my_list)) # Large size
print(sys.getsizeof(my_gen))  # Small size (constant)
```

## Generator Functions (`yield`)

A generator function is defined like a normal function, but whenever it needs to generate a value, it does so with the `yield` keyword rather than `return`.

```python
def countdown(num):
    print("Starting")
    while num > 0:
        yield num
        num -= 1

val = countdown(5)
print(next(val)) # Starting, 5
print(next(val)) # 4
```

[[programming/python/python]]