Adding and Extending Python Dictionaries: Flexible Data Manipulation for Data Science 2026

Datatypes Mar 19, 2026

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 frozendict or MappingProxyType)

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 returns None
  • 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 | b and update()
  • Use | for a new dict and |= or update() 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

Related Tutorials

Share:
Last updated: March 2026
Built-in Functions in Python 2026 str() in Python 2026: String Conversion + Modern Formatting & Best Practices memoryview with JAX in Python 2026: Zero-Copy NumPy → JAX Array Interop + Efficient ML Examples memoryview with TensorFlow in Python 2026: Zero-Copy NumPy → Tensor Interop + GPU Pinning & ML Examples memoryview with TensorFlow in Python 2026: Zero-Copy NumPy → Tensor Interop + ML Examples memoryview with NumPy & PyTorch in Python 2026: Zero-Copy Views, Efficient Slicing & ML Interop Examples memoryview with NumPy in Python 2026: Zero-Copy Views, Efficient Slicing & Real ML Examples memoryview() in Python 2026: Zero-Copy Magic for Large Binary Data + Real Examples import() in Python 2026: How It Works, Security Risks & Modern Alternatives (importlib) zip() in Python 2026: How to Use It, strict=True Behavior & Real-World Examples vars() in Python 2026: Accessing Object Namespace + Modern Introspection Patterns type() in Python 2026: Dynamic Type Inspection & Object Creation + Modern Patterns tuple() in Python 2026: Immutable Sequences + Modern Patterns & Best Practices super() in Python 2026: Method Resolution & Modern Inheritance Patterns sum() in Python 2026: Summing Iterables + Modern Numeric Patterns & Best Practices staticmethod() in Python 2026: Static Methods, Utility Functions & Modern Best Practices sorted() in Python 2026: Sorting Iterables + Modern Patterns & Best Practices slice() in Python 2026: Creating Slice Objects + Modern Patterns & Best Practices setattr() in Python 2026: Dynamic Attribute Setting + Modern Patterns & Safety set() in Python 2026: Mutable Sets Creation + Modern Patterns & Best Practices