Adding and Extending Python Dictionaries: Flexible Data Manipulation for Data Science 2026
Adding and extending dictionaries is one of the most common and powerful operations in data science. Whether you are merging model configurations, adding new features to a metadata dict, combining summary statistics, or dynamically building hyperparameter grids, Python gives you several clean and efficient ways to extend dictionaries without writing verbose code.
TL;DR — Modern Ways to Add/Extend Dictionaries
.update()→ in-place extension{**dict1, **dict2}→ create new merged dict (very Pythonic)dict1 | dict2→ union operator (Python 3.9+).setdefault()→ lazy initialization
1. Basic Adding and Extending
# Starting dictionary
config = {"n_estimators": 100, "max_depth": 10}
# 1. .update() - modifies in place
config.update({"learning_rate": 0.1, "random_state": 42})
# 2. Unpacking (most Pythonic way)
default_config = {"n_estimators": 100, "max_depth": 10}
user_config = {"n_estimators": 300, "learning_rate": 0.05}
final_config = {**default_config, **user_config} # user wins on conflicts
# 3. Union operator | (clean and modern)
merged = default_config | user_config
2. Real-World Data Science Examples
import pandas as pd
# Small inline sample so this example runs as-is.
# In a real project you would load it: df = pd.read_csv("sales_data.csv")
df = pd.DataFrame({
"customer_id": [101, 102, 103, 104, 105, 106],
"customer_name": ["Ana", "Ben", "Chen", "Dana", "Eli", "Fay"],
"order_date": ["2026-01-05", "2026-01-12", "2026-02-03",
"2026-02-18", "2026-03-01", "2026-03-09"],
"amount": [1250.75, 890.50, 2340.00, 1780.25, 2615.40, 450.00],
"quantity": [3, 2, 5, 4, 6, 1],
"profit": [312.69, 222.63, 585.00, 445.06, 653.85, 112.50],
"region": ["North", "South", "East", "North", "West", "South"],
"category": ["Electronics", "Books", "Electronics", "Home", "Home", "Books"],
"rating": [5, 4, None, 3, 5, 4],
})
# Example 1: Building feature metadata dictionary
feature_metadata = {}
for col in df.columns:
feature_metadata.setdefault(col, {})
feature_metadata[col].update({
"dtype": str(df[col].dtype),
"unique_count": df[col].nunique(),
"has_missing": df[col].isna().any()
})
# Example 2: Merging model configurations safely
base_params = {"model_type": "random_forest", "n_estimators": 100}
user_params = {"n_estimators": 300, "max_depth": 15}
final_params = {**base_params, **user_params}
# Example 3: Extending summary statistics
summary = {"total_sales": 0, "count": 0}
for row in df.itertuples():
summary["total_sales"] += row.amount
summary["count"] += 1
summary.setdefault("avg_amount", 0) # lazy init
3. Advanced Extension Techniques
# Using .setdefault() for nested structures
nested = {}
nested.setdefault("model", {}).setdefault("params", {})["n_estimators"] = 200
# ChainMap for layered configuration (advanced)
from collections import ChainMap
defaults = {"n_estimators": 100}
user = {"n_estimators": 300}
final = ChainMap(user, defaults)
4. Best Practices in 2026
- Use
{**dict1, **dict2}or|for clean, readable merging - Use
.update()when you want to modify a dict in place - Use
.setdefault()for lazy initialization of nested structures - Prefer unpacking over manual loops when combining multiple dictionaries
- Keep configuration and metadata dictionaries immutable when possible (use
frozendictorMappingProxyType)
Merge Order and Which Value Wins
Every merge technique follows one rule: when the same key appears more than once, the last value wins. In {**a, **b} and a | b, values from b override a. a.update(b) and a |= b apply the same rule but modify a in place. The | and |= operators were added in Python 3.9.
All of these merges are shallow: if both dicts contain a nested dict under the same key, the whole inner dict is replaced, not combined. For nested configuration you need a small recursive merge, shown below.
Worked Example: Deep-Merging Configuration
def deep_merge(base, override):
"""Return a new dict: override wins, nested dicts are merged."""
result = dict(base)
for key, value in override.items():
if isinstance(value, dict) and isinstance(result.get(key), dict):
result[key] = deep_merge(result[key], value)
else:
result[key] = value
return result
defaults = {"model": {"type": "rf", "params": {"n_estimators": 100, "max_depth": 10}},
"seed": 42}
user = {"model": {"params": {"n_estimators": 300}}}
print(defaults | user) # shallow: params dict replaced, max_depth lost
print(deep_merge(defaults, user))
# {'model': {'type': 'rf', 'params': {'n_estimators': 300, 'max_depth': 10}}, 'seed': 42}
# Extending list values instead of replacing them
tags = {"a": ["x"]}
tags.setdefault("a", []).extend(["y", "z"])
tags.setdefault("b", []).append("new")
print(tags) # {'a': ['x', 'y', 'z'], 'b': ['new']}
Common Mistakes
- Expecting
update()to return the merged dict: it returnsNone - Assuming merges are deep: nested dicts are replaced wholesale
- Getting the order wrong: put defaults first and overrides last
- Mutating shared defaults:
defaults.update(user)changes the defaults for every later caller; build a new dict instead
Quick Summary
- Last value wins in
{**a, **b},a | bandupdate() - Use
|for a new dict and|=orupdate()for in-place changes - Write a small recursive merge for nested configuration
Conclusion
Adding and extending dictionaries is a daily task in data science. In 2026, the most Pythonic approaches are dictionary unpacking (**), the union operator (|), and .setdefault(). These techniques make your model configuration, feature metadata, and summary statistics code clean, concise, and maintainable while avoiding verbose boilerplate.
Next steps:
- Review your current code and replace manual dictionary merging loops with modern unpacking or union operations