Skip to Content
Course content

232: Collections Module: OrderedDict and ChainMap

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've probably noticed by now that Python dictionaries remember the order in which you add keys. For a long time, that was the primary selling point of OrderedDict. Since Python 3.7, the standard dict does this by default, which leads a lot of developers to think OrderedDict is a relic. It isn't. The difference isn't about if it remembers the order, but how it treats that order as a piece of data.

Why your standard dict isn't always enough for ordering

Imagine you're building a "Recent Searches" feature for an app. You want to keep track of the last five things a user searched for. When a user searches for something they've already searched for, you don't want a duplicate entry; you want that item to jump to the "most recent" position.

The naive way to do this with a standard dictionary is to pop the key and then re-insert it. It works, but it's clunky. You're manually managing the lifecycle of the key just to move its position. This is where OrderedDict shines because it provides the move_to_end() method. I find this much cleaner because it explicitly communicates your intent to anyone reading your code: "I am reordering this collection," not "I am deleting and recreating a value."

from collections import OrderedDict

# The naive way: pop and re-insert
history = {'python': 1, 'rust': 2, 'go': 3}
# User searches for 'python' again
val = history.pop('python')
history['python'] = val 

# The OrderedDict way: explicit movement
recent_searches = OrderedDict([('python', 1), ('rust', 2), ('go', 3)])
recent_searches.move_to_end('python') # Moves to the right (most recent)
# To move it to the left (least recent), just use last=False

There's another critical difference: equality. If you compare two standard dictionaries with the same keys and values but in a different order, Python tells you they are equal. OrderedDict doesn't. It considers the order part of the identity. If the sequence of events matters for your business logic, using a standard dict is a bug waiting to happen.

The hidden cost of merging configuration dictionaries

Now let's talk about ChainMap. I see people handle application settings by merging dictionaries all the time. They'll have a dictionary of default settings, a dictionary from a config file, and a dictionary of command-line overrides. The common approach is to merge them using the unpack operator:

defaults = {'theme': 'light', 'port': 8080, 'debug': False}
user_config = {'theme': 'dark', 'port': 9000}
cli_args = {'debug': True}

# The naive merge
final_config = {**defaults, **user_config, **cli_args}

On the surface, this looks fine. But this creates a brand new dictionary in memory. If your configuration is massive, or if you're doing this frequently in a loop, you're wasting resources. More importantly, if you update a value in user_config after the merge, final_config won't reflect that change. You've created a static snapshot, not a dynamic link.

When the view is better than the copy

ChainMap solves this by creating a view. Instead of merging the dictionaries into one, it just keeps a list of the dictionaries and searches through them one by one until it finds the key it's looking for. It's like a stack of transparent sheets; you look through the top sheet first, then the one below it, and so on.

from collections import ChainMap

# The ChainMap way: a virtual view
config = ChainMap(cli_args, user_config, defaults)

print(config['theme']) # 'dark' (found in user_config)
print(config['debug']) # True (found in cli_args)

# If the user updates their config at runtime:
user_config['theme'] = 'high-contrast'
print(config['theme']) # 'high-contrast' - it updated automatically!

The trade-off here is look-up speed versus flexibility. A standard dict lookup is nearly instantaneous. A ChainMap lookup might have to check three or four dictionaries before it finds the value. In 99% of configuration scenarios, that performance hit is completely negligible, but the ability to have a live, tiered view of your data is a huge architectural win. Just remember: if you write to a ChainMap, it only ever updates the first dictionary in the chain. It won't touch your defaults, which is exactly what you want.




📋 Practical Task

Exercise: Building a Tiered Configuration Manager for a CLI Tool

You are building a CLI tool that manages system backups. The tool needs to resolve settings based on a strict hierarchy: Command Line Arguments (highest priority) → User Home Directory ConfigSystem-wide Global ConfigHardcoded Defaults (lowest priority).

Write a script that does the following:

  1. Create four dictionaries representing the levels mentioned above. Include a 'log_level' and a 'backup_path' in at least three of them to test the overriding logic.
  2. Use ChainMap to create a single configuration view.
  3. Print the resolved 'log_level' and 'backup_path' to verify the highest priority value is chosen.
  4. Simulate a user changing their home directory configuration at runtime by updating the user_home_config dictionary.
  5. Print the resolved values again to demonstrate that the ChainMap reflects the change without needing to be re-initialized.
  6. Add a new setting 'timeout' to the configuration view and verify that this new value is stored only in the highest-priority dictionary (the CLI args), leaving the others untouched.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.