GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing zetcode.com — Python collections - Counter, defaultdict, OrderedDict, ChainMap Python collectionslast modified October 29, 2026 Python 3.15
In this article we show how to use the The module contains the following types:
The Counting with Counter
A counter_basic.py
import re from collections import Counter text = """ The sky is blue. The sea is blue too. The grass is green and the sun is yellow. """ words = re.findall(r'\w+', text.lower()) counts = Counter(words) print(counts.most_common(3)) print(counts['blue'], counts['red']) print(counts.total(), len(counts))
We count the words of a text. The print(counts.most_common(3)) print(counts['blue'], counts['red'])
The print(counts.total(), len(counts))
The $ python counter_basic.py
[('the', 4), ('is', 4), ('blue', 2)]
2 0
18 11
Counter arithmeticCounters support arithmetic and set-like operators. The results keep only positive counts. counter_math.py
from collections import Counter monday = Counter(apples=3, pears=1, plums=4) tuesday = Counter(apples=1, pears=2, cherries=5) print(monday + tuesday) print(monday - tuesday) print(monday & tuesday) print(monday | tuesday) print(monday ^ tuesday) We have the fruit sales of two days. print(monday + tuesday) print(monday - tuesday) Addition adds the counts. Subtraction subtracts them and drops the elements whose count is zero or negative. print(monday & tuesday) print(monday | tuesday) The intersection takes the minimum of each count and keeps only elements found in both counters. The union takes the maximum. print(monday ^ tuesday) The symmetric difference operator was added in Python 3.15. It returns the absolute difference of the counts: how much the two days differ for each fruit. $ python counter_math.py
Counter({'cherries': 5, 'apples': 4, 'plums': 4, 'pears': 3})
Counter({'plums': 4, 'apples': 2})
Counter({'apples': 1, 'pears': 1})
Counter({'cherries': 5, 'plums': 4, 'apples': 3, 'pears': 2})
Counter({'cherries': 5, 'plums': 4, 'apples': 2, 'pears': 1})
Updating counters
The counter_update.py
from collections import Counter stock = Counter(apples=4, pears=2) stock.update(['apples', 'plums', 'plums']) print(stock) stock.subtract(apples=5, pears=1) print(stock) print(+stock) print(sorted(stock.elements()))
Unlike stock.subtract(apples=5, pears=1) The in-place methods keep zero and negative counts. Here the count of apples drops to 0. print(+stock) print(sorted(stock.elements()))
The unary plus operator removes elements with zero or negative counts. The
$ python counter_update.py
Counter({'apples': 5, 'pears': 2, 'plums': 2})
Counter({'plums': 2, 'pears': 1, 'apples': 0})
Counter({'plums': 2, 'pears': 1})
['pears', 'plums', 'plums']
Grouping with defaultdict
A defaultdict_group.py
from collections import defaultdict
words = ['sky', 'forest', 'sun', 'falcon', 'storm', 'fog', 'cloud']
by_letter = defaultdict(list)
for word in words:
by_letter[word[0]].append(word)
print(by_letter)
print(dict(by_letter))
lengths = defaultdict(int)
for word in words:
lengths[len(word)] += 1
print(dict(lengths))
We group words by their first letter and count words by length. by_letter = defaultdict(list)
for word in words:
by_letter[word[0]].append(word)
When a letter is seen for the first time, lengths = defaultdict(int)
for word in words:
lengths[len(word)] += 1
Calling $ python defaultdict_group.py
defaultdict(<class 'list'>, {'s': ['sky', 'sun', 'storm'], 'f': ['forest', 'falcon', 'fog'], 'c': ['cloud']})
{'s': ['sky', 'sun', 'storm'], 'f': ['forest', 'falcon', 'fog'], 'c': ['cloud']}
{3: 3, 6: 2, 5: 2}
When default values are created
The factory is called only by the square bracket lookup. Other ways of
accessing the dictionary behave as with a plain defaultdict_missing.py
from collections import defaultdict
scores = defaultdict(list)
print(scores.get('john'))
print('john' in scores)
print(scores['john'])
print('john' in scores)
scores.default_factory = None
try:
scores['paul']
except KeyError as e:
print('KeyError:', e)
The scores.default_factory = None
The factory is stored in the $ python defaultdict_missing.py None False [] True KeyError: 'paul' A tree of defaultdicts
A factory function that returns a new defaultdict_tree.py
import json
from collections import defaultdict
def tree():
return defaultdict(tree)
config = tree()
config['database']['primary']['host'] = 'db1.example.com'
config['database']['primary']['port'] = 5432
config['database']['replica']['host'] = 'db2.example.com'
config['cache']['ttl'] = 300
print(json.dumps(config, indent=2))
The $ python defaultdict_tree.py
{
"database": {
"primary": {
"host": "db1.example.com",
"port": 5432
},
"replica": {
"host": "db2.example.com"
}
},
"cache": {
"ttl": 300
}
}
OrderedDict
Since Python 3.7, a plain ordereddict_ops.py
from collections import OrderedDict
tasks = OrderedDict(write='todo', test='todo', deploy='todo')
tasks.move_to_end('write')
print(list(tasks))
tasks.move_to_end('deploy', last=False)
print(list(tasks))
print(tasks.popitem())
print(tasks.popitem(last=False))
a = OrderedDict(x=1, y=2)
b = OrderedDict(y=2, x=1)
print(a == b, dict(a) == dict(b))
The print(tasks.popitem()) print(tasks.popitem(last=False))
The print(a == b, dict(a) == dict(b)) Two ordered dictionaries are equal only if their items are in the same order. Plain dictionaries ignore the order. $ python ordereddict_ops.py
['test', 'deploy', 'write']
['deploy', 'test', 'write']
('write', 'todo')
('deploy', 'todo')
False True
An LRU cache
The reordering operations make lru_cache.py
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity):
self.capacity = capacity
self.data = OrderedDict()
def get(self, key):
if key not in self.data:
return None
self.data.move_to_end(key)
return self.data[key]
def put(self, key, value):
self.data[key] = value
self.data.move_to_end(key)
if len(self.data) > self.capacity:
evicted, _ = self.data.popitem(last=False)
print(f'evicted {evicted}')
cache = LRUCache(2)
cache.put('a', 1)
cache.put('b', 2)
cache.get('a')
cache.put('c', 3)
cache.put('d', 4)
print(list(cache.data.items()))
The cache keeps the most recently used keys at the end. self.data.move_to_end(key) Each access moves the key to the end of the dictionary. if len(self.data) > self.capacity:
evicted, _ = self.data.popitem(last=False)
When the cache is full, the first item, the least recently used one, is
removed. Because we read $ python lru_cache.py
evicted b
evicted a
[('c', 3), ('d', 4)]
Layered settings with ChainMap
A chainmap_config.py
from collections import ChainMap
defaults = {'theme': 'light', 'language': 'en', 'autosave': True}
user = {'theme': 'dark'}
cli = {'language': 'sk'}
settings = ChainMap(cli, user, defaults)
print(settings['theme'], settings['language'], settings['autosave'])
print(dict(settings))
settings['autosave'] = False
print(cli)
print(defaults)
The settings of a program come from command line arguments, user configuration, and defaults, in this order of priority. settings = ChainMap(cli, user, defaults) The theme comes from the user configuration, the language from the command line, and the autosave value from the defaults. No data is copied; the chain only refers to the original dictionaries, so later changes to them are visible through the chain. settings['autosave'] = False Writes and deletions affect only the first mapping. The defaults stay unchanged. $ python chainmap_config.py
dark sk True
{'theme': 'dark', 'language': 'sk', 'autosave': True}
{'language': 'sk', 'autosave': False}
{'theme': 'light', 'language': 'en', 'autosave': True}
Nested scopes with ChainMapA chain of mappings can model nested scopes, such as variables in a programming language. chainmap_scopes.py
from collections import ChainMap
module = ChainMap({'x': 1, 'y': 2})
function = module.new_child({'x': 10})
block = function.new_child({'z': 30})
print(block['x'], block['y'], block['z'])
print(block.maps)
print(block.parents['x'])
block['y'] = 99
print(block.maps[0], function['y'])
The print(block.maps) print(block.parents['x'])
The block['y'] = 99
The assignment creates a new $ python chainmap_scopes.py
10 2 30
[{'z': 30}, {'x': 10}, {'x': 1, 'y': 2}]
10
{'z': 30, 'y': 99} 2
UserDict
The userdict.py
from collections import UserDict
class LowerDict(dict):
def __setitem__(self, key, value):
super().__setitem__(key.lower(), value)
class LowerUserDict(UserDict):
def __setitem__(self, key, value):
super().__setitem__(key.lower(), value)
headers = {'Content-Type': 'text/html', 'Server': 'Python'}
d1 = LowerDict(headers)
d2 = LowerUserDict(headers)
print(d1)
print(d2)
d1.update(Host='example.com')
d2.update(Host='example.com')
print(list(d1))
print(list(d2))
Both classes try to store all keys in lower case by overriding
class LowerDict(dict):
def __setitem__(self, key, value):
super().__setitem__(key.lower(), value)
The methods of the built-in class LowerUserDict(UserDict):
The methods of $ python userdict.py
{'Content-Type': 'text/html', 'Server': 'Python'}
{'content-type': 'text/html', 'server': 'Python'}
['Content-Type', 'Server', 'Host']
['content-type', 'server', 'host']
Abstract base classes
The abc_sequence.py
from collections.abc import Sequence
class Squares(Sequence):
def __init__(self, n):
self.n = n
def __len__(self):
return self.n
def __getitem__(self, i):
if not 0 <= i < self.n:
raise IndexError(i)
return i * i
sq = Squares(6)
print(list(sq))
print(25 in sq, sq.index(16), sq.count(9))
print(list(reversed(sq)))
print(isinstance(sq, Sequence), isinstance([], Sequence), isinstance({}, Sequence))
The def __len__(self):
return self.n
def __getitem__(self, i):
The print(isinstance(sq, Sequence), isinstance([], Sequence), isinstance({}, Sequence))
Checks against the abstract classes accept any type that fits the interface.
Lists are sequences, while dictionaries are not; a dictionary would match
$ python abc_sequence.py [0, 1, 4, 9, 16, 25] True 4 1 [25, 16, 9, 4, 1, 0] True True False Practical example
In the last example, we analyze a web server log with a
log_report.py
from collections import Counter, defaultdict
log = """\
192.168.0.4 GET /index.html 200
192.168.0.7 GET /about.html 200
192.168.0.4 POST /login 302
192.168.0.9 GET /admin 403
192.168.0.4 GET /index.html 200
192.168.0.7 GET /missing 404
192.168.0.9 GET /admin 403
"""
statuses = Counter()
pages_by_ip = defaultdict(set)
for line in log.splitlines():
ip, method, path, status = line.split()
statuses[status] += 1
pages_by_ip[ip].add(path)
print(statuses.most_common())
for ip, pages in sorted(pages_by_ip.items()):
print(ip, sorted(pages))
errors = Counter({k: v for k, v in statuses.items() if k.startswith(('4', '5'))})
print(f'{errors.total()} error responses: {dict(errors)}')
Each line contains a client address, a method, a path, and a status code. statuses = Counter() pages_by_ip = defaultdict(set)
We count the status codes and collect the distinct pages requested by each
client. A errors = Counter({k: v for k, v in statuses.items() if k.startswith(('4', '5'))})
Finally, we build a counter of client and server errors and print their total. $ python log_report.py
[('200', 3), ('403', 2), ('302', 1), ('404', 1)]
192.168.0.4 ['/index.html', '/login']
192.168.0.7 ['/about.html', '/missing']
192.168.0.9 ['/admin']
3 error responses: {'403': 2, '404': 1}
Sourcecollections – Container datatypes collections.abc – Abstract Base Classes for Containers
In this article we have worked with the container types of the Python
AuthorMy name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming. List all Python tutorials. |
| You are visitor 01402520 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |