Skip to content

dict Merge Operators

Python 3.9+ introduced the | and |= operators for merging dictionaries, providing a cleaner syntax than update() and providing merge semantics. This is part of PEP 584 and improves dict manipulation.

Mental Model

Think of | as layering two transparencies: the left dict goes down first, then the right dict goes on top. Where keys overlap, the right side wins. | produces a new dict (like + for lists), while |= updates the left dict in place (like += for lists).


The | Operator (Merge)

Basic Dict Merging

```python d1 = {'a': 1, 'b': 2} d2 = {'c': 3, 'd': 4}

merged = d1 | d2 print(merged) ```

Output: {'a': 1, 'b': 2, 'c': 3, 'd': 4}

Right Side Overwrites

```python d1 = {'a': 1, 'b': 2} d2 = {'b': 20, 'c': 3}

merged = d1 | d2 print(merged) ```

Output: {'a': 1, 'b': 20, 'c': 3}

The |= Operator (In-Place Merge)

Updating a Dictionary

python d = {'a': 1, 'b': 2} d |= {'c': 3, 'd': 4} print(d)

Output: {'a': 1, 'b': 2, 'c': 3, 'd': 4}

Overwriting Values

python d = {'a': 1, 'b': 2} d |= {'b': 20, 'c': 3} print(d)

Output: {'a': 1, 'b': 20, 'c': 3}

Practical Applications

Configuration Merging

```python defaults = {'host': 'localhost', 'port': 8000, 'debug': False} custom = {'port': 5000, 'debug': True}

config = defaults | custom print(config) ```

Output: {'host': 'localhost', 'port': 5000, 'debug': True}

Layered Configuration

```python system = {'theme': 'dark', 'language': 'en'} user = {'theme': 'light'} session = {'theme': 'auto'}

final = system | user | session print(final) ```

Output: {'theme': 'auto', 'language': 'en'}

Comparison with update()

| Creates New Dict

```python d1 = {'a': 1, 'b': 2} d2 = {'c': 3}

d3 = d1 | d2 print(f"d1: {d1}") print(f"d3: {d3}") print(f"d1 unchanged: {d1 is not d3}") ```

Output: d1: {'a': 1, 'b': 2} d3: {'a': 1, 'b': 2, 'c': 3} d1 unchanged: True


Exercises

Exercise 1. Given two dictionaries defaults = {"color": "red", "size": 10} and custom = {"size": 20, "font": "Arial"}, merge them so that custom values take priority. Show two approaches: one using | and one using {**d1, **d2}.

Solution to Exercise 1
```python
defaults = {"color": "red", "size": 10}
custom = {"size": 20, "font": "Arial"}

# Using | (Python 3.9+)
merged1 = defaults | custom
print(merged1)  # {'color': 'red', 'size': 20, 'font': 'Arial'}

# Using ** unpacking
merged2 = {**defaults, **custom}
print(merged2)  # {'color': 'red', 'size': 20, 'font': 'Arial'}
```

Both approaches give custom priority. The rightmost dictionary's values win for duplicate keys.


Exercise 2. Write a function deep_merge(d1, d2) that merges two dictionaries recursively. If both values for a key are dictionaries, merge them recursively. Otherwise, d2's value wins.

Solution to Exercise 2
```python
def deep_merge(d1, d2):
    result = d1.copy()
    for key, value in d2.items():
        if key in result and isinstance(result[key], dict) and isinstance(value, dict):
            result[key] = deep_merge(result[key], value)
        else:
            result[key] = value
    return result

a = {"db": {"host": "localhost", "port": 5432}, "debug": True}
b = {"db": {"port": 3306, "user": "admin"}, "debug": False}

print(deep_merge(a, b))
# {'db': {'host': 'localhost', 'port': 3306, 'user': 'admin'}, 'debug': False}
```

The recursive approach preserves nested keys from both dictionaries while letting d2 values take priority.


Exercise 3. Demonstrate the difference between d1 | d2 (creates new dict) and d1 |= d2 (updates in place) by showing that one changes the original dictionary and the other does not.

Solution to Exercise 3
```python
d1 = {"a": 1, "b": 2}
d2 = {"b": 3, "c": 4}

# | creates a new dict
d3 = d1 | d2
print(d1)  # {'a': 1, 'b': 2} (unchanged)
print(d3)  # {'a': 1, 'b': 3, 'c': 4}

# |= updates in place
original_id = id(d1)
d1 |= d2
print(d1)  # {'a': 1, 'b': 3, 'c': 4} (modified)
print(id(d1) == original_id)  # True (same object)
```

| returns a new dictionary, leaving originals intact. |= modifies the left-hand dictionary in place.