展开菜单
首页 精品内容 本月促销 装机必备 Windows PDF教程 macOS软件 IOS软件 Android AI 专题
全部分类

当前位置:

首页 > 编程开发 > Python字典中defaultdict与OrderedDict的使用详解

Python字典中defaultdict与OrderedDict的使用详解

defaultdict是collections模块中dict的子类,通过工厂函数在键不存在时自动生成默认值,避免了手动初始化。支持list、int、set等类型,内部依赖__missing__方法实现。常用于分组、计数、嵌套字典等场景,代码更简洁高效,减少冗余逻辑,显著提升可读性。

本文目录

    一、开篇:普通字典不够用时怎么办?

    前面我们刚刚聊完字典的基础操作——创建、增删改查、遍历、推导式和合并。现在你已经能用字典处理大部分日常开发中的数据映射需求了。但在某些特定场景下,普通字典会暴露出两个让人头疼的问题:

    Python字典中defaultdict与OrderedDict的使用详解

    问题一:每次往字典里追加数据,都得先检查键是否存在。比如你要按类别分组,每遇到一个新品类别,都得写if key not in dict: dict[key] = [],代码又臭又长。

    # 没有defaultdict之前——很啰嗦
    words = ['apple', 'banana', 'a vocado', 'blueberry', 'apricot']
    groups = {}
    for word in words:
        first_letter = word[0]
        if first_letter not in groups:   # 每次都要检查!
            groups[first_letter] = []
        groups[first_letter].append(word)
    

    问题二:普通字典虽说Python 3.7+保序了,但在某些需要精确控制键顺序的场景(比如LRU缓存),还是不够用。

    好消息是,Python的collections模块给我们准备了两个超级好用的字典变体——defaultdict和OrderedDict,专门解决这两类场景。今天这篇文章,我们就来彻底掌握它们。

    二、defaultdict:自带默认值的字典

    2.1 普通字典的痛点回顾

    在你没用过defaultdict之前,这类代码你一定写过无数次:

    # 场景一:按类别分组
    students = [
        ('数学', '小明'),
        ('语文', '小红'),
        ('数学', '小刚'),
        ('英语', '小红'),
        ('数学', '小丽'),
        ('语文', '小华'),
    ]
    
    # 传统写法——每次都要检查和初始化
    subject_groups = {}
    for subject, name in students:
        if subject not in subject_groups:
            subject_groups[subject] = []  # 初始化空列表
        subject_groups[subject].append(name)
    
    print(subject_groups)
    # {'数学': ['小明', '小刚', '小丽'], '语文': ['小红', '小华'], '英语': ['小红']}
    
    
    # 另一种写法——用setdefault(稍微好一点,但还是啰嗦)
    subject_groups2 = {}
    for subject, name in students:
        subject_groups2.setdefault(subject, []).append(name)
    
    # 或者用dict.get配合or(有点hacky)
    subject_groups3 = {}
    for subject, name in students:
        lst = subject_groups3.get(subject) or []
        # 等等,这里不对——如果lst是[](空列表),它也是falsy的
        # 所以这写法有bug...
    

    这些写法的问题:要么代码冗长(if key not in),要么有陷阱(get() or []当值本身是空列表时出错),要么不够直观(setdefault的名字不够直白)。

    2.2 defaultdict的基本使用

    defaultdict是dict的子类,它在创建时需要传入一个工厂函数(一个无参数的可调用对象),当访问的键不存在时,自动用这个工厂函数生成默认值。

    from collections import defaultdict
    
    # 语法:defaultdict(工厂函数)
    # 工厂函数是一个无参数、返回默认值的可调用对象
    
    # 最常见的用法——默认值为空列表
    dd_list = defaultdict(list)
    print(dd_list['a'])  # []——键'a'不存在,自动创建空列表
    print(dd_list)       # defaultdict(, {'a': []})
    
    # 现在前面的分组问题变得多简洁:
    subject_groups = defaultdict(list)
    for subject, name in students:
        subject_groups[subject].append(name)  # 不需要检查键是否存在!
    
    print(dict(subject_groups))
    # {'数学': ['小明', '小刚', '小丽'], '语文': ['小红', '小华'], '英语': ['小红']}
    
    
    # 默认值为0——计数器
    dd_int = defaultdict(int)
    words = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
    for word in words:
        dd_int[word] += 1  # 键不存在时自动初始化为0,然后+1
    
    print(dict(dd_int))  # {'apple': 3, 'banana': 2, 'orange': 1}
    

    2.3 各种工厂函数的选择

    from collections import defaultdict
    
    # 1. list——分组收集
    dd = defaultdict(list)
    dd['fruits'].append('apple')
    dd['fruits'].append('banana')
    print(dd)  # {'fruits': ['apple', 'banana']}
    
    # 2. int——计数
    dd = defaultdict(int)
    dd['a'] += 1
    dd['a'] += 1
    dd['b'] += 1
    print(dd)  # {'a': 2, 'b': 1}
    
    # 3. set——去重收集
    dd = defaultdict(set)
    dd['vowels'].add('a')
    dd['vowels'].add('e')
    dd['vowels'].add('a')  # 重复——自动去重
    print(dd)  # {'vowels': {'a', 'e'}}
    
    # 4. float——累加浮点数
    dd = defaultdict(float)
    prices = [('apple', 5.5), ('banana', 3.2), ('apple', 4.5)]
    for item, price in prices:
        dd[item] += price
    print(dd)  # {'apple': 10.0, 'banana': 3.2}
    
    # 5. str——字符串拼接
    dd = defaultdict(str)
    dd['greeting'] += 'Hello'
    dd['greeting'] += ' World'
    print(dd)  # {'greeting': 'Hello World'}
    
    # 6. dict——嵌套字典
    dd = defaultdict(dict)
    dd['user1']['name'] = '小明'
    dd['user1']['age'] = 25
    dd['user2']['name'] = '小红'
    print(dict(dd))
    # {'user1': {'name': '小明', 'age': 25}, 'user2': {'name': '小红'}}
    
    # 7. 自定义函数/lambda
    dd = defaultdict(lambda: '未知')
    print(dd['anything'])  # '未知'
    
    # lambda也可以做更复杂的默认值
    dd = defaultdict(lambda: {'count': 0, 'items': []})
    dd['electronics']['count'] += 1
    dd['electronics']['items'].append('手机')
    print(dict(dd))
    # {'electronics': {'count': 1, 'items': ['手机']}}
    

    重要:工厂函数必须是无参数的可调用对象。defaultdict([])会报错,因为[]不是可调用的;应该用defaultdict(list)。

    2.4 defaultdict的内部原理

    # defaultdict最核心的秘密在于 __missing__ 方法
    # 这是Python字典的一个钩子方法,当键不存在时被调用
    
    # 简化版的defaultdict实现
    class SimpleDefaultDict(dict):
        def __init__(self, default_factory=None):
            super().__init__()
            self.default_factory = default_factory
        
        def __missing__(self, key):
            """当键不存在时被调用"""
            if self.default_factory is None:
                raise KeyError(key)
            # 调用工厂函数生成默认值
            value = self.default_factory()
            # 把默认值存入字典(这样下次访问同一个键就不会再触发__missing__)
            self[key] = value
            return value
    
    
    # 测试我们的简化版
    sdd = SimpleDefaultDict(list)
    print(sdd['a'])  # []
    sdd['a'].append(1)
    print(sdd)       # {'a': [1]}——默认值已经被存入字典了
    
    # Python真正的defaultdict就是这个原理
    # __missing__方法只在键完全不存在时被调用
    # get()方法不会触发__missing__(它有自己的默认值逻辑)
    
    from collections import defaultdict
    dd = defaultdict(list)
    print(dd.get('nonexistent'))  # None——get不触发default_factory!
    print(dd)                      # {}——键没有被创建
    

    三、defaultdict的实战场景

    3.1 场景一:单词频率统计

    from collections import defaultdict
    
    text = """
    Python is an interpreted high-level programming language
    for general-purpose programming Python is dynamically typed
    and garbage-collected it supports multiple programming
    paradigms including procedural object-oriented and functional
    programming Python is often described as a batteries included
    language due to its comprehensive standard library
    """
    
    # 用defaultdict统计词频
    word_count = defaultdict(int)
    for word in text.lower().split():
        # 去掉标点符号
        word = word.strip('.,"'()[]{};:')
        if word:
            word_count[word] += 1
    
    # 按词频排序输出
    sorted_words = sorted(word_count.items(), key=lambda x: x[1], reverse=True)
    for word, count in sorted_words[:10]:
        print(f'{word}: {count}')
    

    3.2 场景二:按多种条件分组

    from collections import defaultdict
    
    # 有一个学生成绩列表,需要按多种维度分组
    students = [
        {'name': '小明', 'grade': 'A', 'class': '1班', 'score': 95},
        {'name': '小红', 'grade': 'B', 'class': '1班', 'score': 82},
        {'name': '小刚', 'grade': 'A', 'class': '2班', 'score': 91},
        {'name': '小丽', 'grade': 'C', 'class': '2班', 'score': 73},
        {'name': '小华', 'grade': 'B', 'class': '1班', 'score': 87},
        {'name': '小强', 'grade': 'A', 'class': '2班', 'score': 98},
    ]
    
    # 按等级分组
    by_grade = defaultdict(list)
    for s in students:
        by_grade[s['grade']].append(s['name'])
    print('按等级分组:')
    for grade, names in sorted(by_grade.items()):
        print(f'  {grade}等: {names}')
    # A等: ['小明', '小刚', '小强']
    # B等: ['小红', '小华']
    # C等: ['小丽']
    
    # 按班级分组
    by_class = defaultdict(list)
    for s in students:
        by_class[s['class']].append(s)
    print('n按班级分组:')
    for cls, members in by_class.items():
        a vg = sum(m['score'] for m in members) / len(members)
        print(f'  {cls}: 平均分{a vg:.1f}')
    
    # 同时按等级和班级分组(嵌套defaultdict)
    by_grade_and_class = defaultdict(lambda: defaultdict(list))
    for s in students:
        by_grade_and_class[s['grade']][s['class']].append(s['name'])
    
    print('n按等级→班级两级分组:')
    for grade in sorted(by_grade_and_class):
        print(f'  {grade}等:')
        for cls, names in by_grade_and_class[grade].items():
            print(f'    {cls}: {names}')
    

    3.3 场景三:构建索引(倒排索引)

    from collections import defaultdict
    
    # 构建一个简单的文档搜索索引
    # 倒排索引:{word: {doc_id: [positions]}}
    
    documents = {
        1: "Python is a great programming language",
        2: "Ja va is also a programming language",
        3: "Python and Ja va are popular languages",
    }
    
    # 构建倒排索引
    inverted_index = defaultdict(lambda: defaultdict(list))
    
    for doc_id, text in documents.items():
        for position, word in enumerate(text.lower().split()):
            word = word.strip('.,;:!?')
            inverted_index[word][doc_id].append(position)
    
    # 查询
    def search(query):
        """简单的AND查询——所有词都必须出现在文档中"""
        query_words = query.lower().split()
        if not query_words:
            return []
        
        # 取第一个词的文档集合
        result_docs = set(inverted_index[query_words[0]].keys())
        # 与其他词取交集
        for word in query_words[1:]:
            result_docs &= set(inverted_index[word].keys())
        
        return sorted(result_docs)
    
    
    print('倒排索引:')
    for word, doc_info in sorted(inverted_index.items()):
        print(f'  "{word}": 出现在 {dict(doc_info)}')
    
    print(f'n搜索 "programming language": 文档 {search("programming language")}')
    print(f'搜索 "Python language": 文档 {search("Python language")}')
    

    3.4 场景四:聚合操作(类似SQL的GROUP BY)

    from collections import defaultdict
    
    # 模拟SQL的GROUP BY聚合
    orders = [
        {'product': '手机', 'category': '电子产品', 'amount': 2999, 'quantity': 1},
        {'product': '电脑', 'category': '电子产品', 'amount': 5999, 'quantity': 1},
        {'product': '苹果', 'category': '食品', 'amount': 5, 'quantity': 3},
        {'product': '香蕉', 'category': '食品', 'amount': 3, 'quantity': 5},
        {'product': 'T恤', 'category': '服装', 'amount': 99, 'quantity': 2},
        {'product': '耳机', 'category': '电子产品', 'amount': 299, 'quantity': 2},
        {'product': '牛奶', 'category': '食品', 'amount': 8, 'quantity': 4},
    ]
    
    # 按类别聚合
    category_stats = defaultdict(lambda: {
        'total_amount': 0,
        'total_quantity': 0,
        'order_count': 0,
        'products': set()
    })
    
    for order in orders:
        cat = order['category']
        stats = category_stats[cat]
        stats['total_amount'] += order['amount'] * order['quantity']
        stats['total_quantity'] += order['quantity']
        stats['order_count'] += 1
        stats['products'].add(order['product'])
    
    print('按类别汇总:')
    for cat, stats in sorted(category_stats.items()):
        print(f'n{cat}:')
        print(f'  总金额: ¥{stats["total_amount"]}')
        print(f'  总数量: {stats["total_quantity"]}件')
        print(f'  订单数: {stats["order_count"]}笔')
        print(f'  涉及产品: {stats["products"]}')
    

    四、defaultdict的注意事项

    4.1 工厂函数必须是无参数的

    from collections import defaultdict
    
    # ✅ 正确——这些工厂函数都不需要参数
    dd1 = defaultdict(list)      # list() → []
    dd2 = defaultdict(int)       # int() → 0
    dd3 = defaultdict(float)     # float() → 0.0
    dd4 = defaultdict(str)       # str() → ''
    dd5 = defaultdict(set)       # set() → set()
    dd6 = defaultdict(dict)      # dict() → {}
    dd7 = defaultdict(bool)      # bool() → False
    
    # ✅ 也可以用无参数的lambda
    dd8 = defaultdict(lambda: '默认值')
    dd9 = defaultdict(lambda: [0, 0, 0])
    dd10 = defaultdict(lambda: {'hits': 0, 'first_seen': None})
    
    # ❌ 错误——传了值而不是工厂函数
    # dd = defaultdict([])        # TypeError: list object is not callable
    # dd = defaultdict(0)         # TypeError: int object is not callable
    # dd = defaultdict({})        # TypeError: dict object is not callable
    
    # ⚠️ 注意:下面的写法是创建一个带参数的工厂函数——这是OK的
    dd11 = defaultdict(lambda: [0, 0, 0])  # lambda本身无参数,但内部返回了一个列表
    print(dd11['a'])  # [0, 0, 0]
    

    4.2 访问不存在的键会创建它

    from collections import defaultdict
    
    # ⚠️ 注意:defaultdict会"悄悄地"创建键
    dd = defaultdict(list)
    print(f'访问前: {dict(dd)}')  # {}
    _ = dd['new_key']              # 只是读取,但键被创建了!
    print(f'访问后: {dict(dd)}')  # {'new_key': []}
    
    # 这在某些场景下是好事(少写代码)
    # 但在另一些场景下可能是坏事(不小心创建了不需要的键)
    
    # 如果你不想创建键,可以用get
    dd2 = defaultdict(int)
    print(dd2.get('nonexistent'))   # None——get不会触发default_factory
    print(dict(dd2))                 # {}——get确实没有创建键
    
    # 判断键是否真正存在
    dd3 = defaultdict(list, {'a': [1], 'b': [2]})
    dd3['c']  # 这会创建'c'
    print('a' in dd3)  # True——原来就有
    print('b' in dd3)  # True——原来就有
    print('c' in dd3)  # True——被访问时创建了
    print('d' in dd3)  # False——从未访问过
    

    4.3 不要滥用defaultdict

    from collections import defaultdict
    
    # ❌ 有时候用普通字典更清晰
    # 如果你只是想给键设置默认值,普通dict.get就够了
    config = {'host': 'localhost', 'port': 8080}
    host = config.get('host', '0.0.0.0')  # 简单明了
    
    # ❌ 如果默认值只需要在少数几个地方使用
    # 用setdefault可能比defaultdict更合适
    d = {}
    d.setdefault('key', []).append(1)
    d.setdefault('key', []).append(2)
    print(d)  # {'key': [1, 2]}
    
    # ✅ defaultdict适用于"频繁访问不存在的键"的场景
    # 比如循环中的分组、计数、累加
    
    # 一个好的判断标准:
    # 如果你的代码里有超过3次的 if key not in dict 或 dict.setdefault
    # 那就应该考虑用defaultdict
    

    五、OrderedDict:记住插入顺序的字典

    5.1 Python字典的顺序演变史

    在Python 3.6之前,普通字典是不保证顺序的。那时如果你需要记住键的插入顺序,必须使用OrderedDict。从Python 3.7开始,普通字典已经正式保证按插入顺序排列了,那OrderedDict是不是就没用了?

    不!OrderedDict还有一些普通字典没有的特殊能力:

    from collections import OrderedDict
    
    # 创建OrderedDict
    od = OrderedDict()
    od['first'] = 1
    od['second'] = 2
    od['third'] = 3
    od['fourth'] = 4
    
    print(od)
    # OrderedDict([('first', 1), ('second', 2), ('third', 3), ('fourth', 4)])
    
    # 遍历顺序=插入顺序
    for key, value in od.items():
        print(f'{key}: {value}', end=' | ')
    # first: 1 | second: 2 | third: 3 | fourth: 4 |
    

    5.2 OrderedDict的独特能力

    from collections import OrderedDict
    
    od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
    print(f'初始: {od}')
    
    # 独特能力一:move_to_end——把某个键移到末尾
    od.move_to_end('b')
    print(f'b移到末尾: {od}')
    # OrderedDict([('a', 1), ('c', 3), ('b', 2)])
    
    # 移到开头
    od.move_to_end('b', last=False)
    print(f'b移到开头: {od}')
    # OrderedDict([('b', 2), ('a', 1), ('c', 3)])
    
    # 独特能力二:popitem——弹出指定位置的元素
    od = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
    # last=True(默认)——弹出最后一个(LIFO,像栈)
    item = od.popitem(last=True)
    print(f'弹出最后一个: {item}, 剩余: {od}')
    # 弹出最后一个: ('d', 4), 剩余: OrderedDict([('a', 1), ('b', 2), ('c', 3)])
    
    # last=False——弹出第一个(FIFO,像队列)
    item = od.popitem(last=False)
    print(f'弹出第一个: {item}, 剩余: {od}')
    # 弹出第一个: ('a', 1), 剩余: OrderedDict([('b', 2), ('c', 3)])
    

    5.3 OrderedDict的相等性比较

    from collections import OrderedDict
    
    # ⚠️ OrderedDict和普通字典在相等性判断上有一个重要区别
    
    # 普通字典——只比较内容,不关心顺序
    d1 = {'a': 1, 'b': 2}
    d2 = {'b': 2, 'a': 1}
    print(f'普通字典: d1 == d2 → {d1 == d2}')  # True
    
    # OrderedDict——既比较内容,也比较顺序!
    od1 = OrderedDict([('a', 1), ('b', 2)])
    od2 = OrderedDict([('b', 2), ('a', 1)])
    print(f'OrderedDict: od1 == od2 → {od1 == od2}')  # False——顺序不同!
    
    od3 = OrderedDict([('a', 1), ('b', 2)])
    print(f'相同顺序: od1 == od3 → {od1 == od3}')  # True
    
    # 但OrderedDict可以和普通字典相等(内容相同就相等)
    print(f'od1 == d1 → {od1 == d1}')  # True
    

    六、OrderedDict的实战场景

    6.1 场景一:LRU缓存实现

    from collections import OrderedDict
    
    class LRUCache:
        """
        使用OrderedDict实现LRU(Least Recently Used)缓存。
        
        原理:每次访问时将键移到末尾(表示最近使用),
        当容量超限时,删除最开头(最久未使用)的键。
        """
        
        def __init__(self, capacity: int):
            self.capacity = capacity
            self.cache = OrderedDict()
        
        def get(self, key):
            """获取缓存值,同时标记为最近使用"""
            if key not in self.cache:
                return -1  # 或 raise KeyError
            
            # 移到末尾——标记为最近使用
            self.cache.move_to_end(key)
            return self.cache[key]
        
        def put(self, key, value):
            """存入缓存,超容量时淘汰最久未使用的"""
            if key in self.cache:
                # 更新已有键——移到末尾
                self.cache.move_to_end(key)
            
            self.cache[key] = value
            
            # 超容量——删除第一个(最久未使用)
            if len(self.cache) > self.capacity:
                oldest_key, oldest_value = self.cache.popitem(last=False)
                print(f'  淘汰: {oldest_key} → {oldest_value}')
        
        def __repr__(self):
            return f'LRUCache({dict(self.cache)})'
    
    
    # 使用示例
    print("=== LRU缓存演示(容量=3) ===n")
    cache = LRUCache(3)
    
    cache.put('A', 1)
    cache.put('B', 2)
    cache.put('C', 3)
    print(f'初始: {cache}')  # A, B, C
    
    # 访问A——A变为最近使用
    cache.get('A')
    print(f'访问A后: {cache}')  # B, C, A(A移到最后)
    
    # 存入D——触发淘汰(B最久未使用)
    cache.put('D', 4)
    print(f'存入D后: {cache}')  # C, A, D
    
    # 存入E——触发淘汰(C最久未使用)
    cache.put('E', 5)
    print(f'存入E后: {cache}')  # A, D, E
    

    6.2 场景二:去重但保留顺序

    from collections import OrderedDict
    
    # 问题:给一个列表去重,但保留第一次出现的顺序
    items = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
    
    # 方法一:用OrderedDict(Python 3.6+)
    unique_ordered = list(OrderedDict.fromkeys(items))
    print(f'去重保序: {unique_ordered}')  # [3, 1, 4, 5, 9, 2, 6]
    
    # 对比:用set会丢失顺序
    unique_no_order = list(set(items))
    print(f'set去重(失序): {unique_no_order}')  # [1, 2, 3, 4, 5, 6, 9]顺序乱了
    
    # 方法二:手动去重保序(Python 3.6-)
    def dedupe_ordered(seq):
        seen = set()
        result = []
        for item in seq:
            if item not in seen:
                seen.add(item)
                result.append(item)
        return result
    
    print(f'手动去重保序: {dedupe_ordered(items)}')  # [3, 1, 4, 5, 9, 2, 6]
    
    # ? Python 3.7+中,普通字典也保序了
    # 所以 dict.fromkeys(items) 也能达到同样效果
    unique_with_dict = list(dict.fromkeys(items))
    print(f'普通字典去重: {unique_with_dict}')  # [3, 1, 4, 5, 9, 2, 6]
    

    6.3 场景三:配置覆盖记录

    from collections import OrderedDict
    
    class ConfigTracker:
        """
        追踪配置的来源和覆盖顺序。
        后面的配置覆盖前面的,同时记录每个配置值的来源。
        """
        
        def __init__(self):
            self._config = OrderedDict()
            self._sources = OrderedDict()
        
        def load(self, config_dict, source_name):
            """加载配置,记录来源"""
            for key, value in config_dict.items():
                self._config[key] = value
                # move_to_end确保这个键在最后(最新加载的)
                self._config.move_to_end(key)
                self._sources[key] = source_name
        
        def get(self, key):
            return self._config.get(key)
        
        def get_source(self, key):
            return self._sources.get(key, '未设置')
        
        def show_all(self):
            """展示所有配置及其来源"""
            print('当前配置(按加载顺序):')
            for key, value in self._config.items():
                source = self._sources.get(key, 'unknown')
                print(f'  {key} = {value}  (来源: {source})')
    
    
    # 使用示例
    tracker = ConfigTracker()
    
    # 模拟多层配置加载
    tracker.load({'host': 'localhost', 'port': 8080, 'debug': True}, '默认配置')
    tracker.load({'host': 'dev.server.com', 'debug': False}, '开发环境')
    tracker.load({'port': 9090, 'timeout': 30}, '命令行参数')
    
    tracker.show_all()
    # host = dev.server.com  (来源: 开发环境)        ← 被开发环境覆盖了
    # debug = False          (来源: 开发环境)        ← 被开发环境覆盖了
    # port = 9090            (来源: 命令行参数)      ← 被命令行覆盖了
    # timeout = 30           (来源: 命令行参数)      ← 新增
    

    6.4 场景四:有序JSON保持

    from collections import OrderedDict
    import json
    
    # JSON规范不保证键的顺序,但有时我们需要保持顺序
    
    # 用OrderedDict构建响应数据结构
    response = OrderedDict()
    response['status'] = 'success'
    response['code'] = 200
    response['data'] = OrderedDict()
    response['data']['user'] = OrderedDict()
    response['data']['user']['id'] = 12345
    response['data']['user']['name'] = '小明'
    response['data']['user']['email'] = 'xiaoming@example.com'
    response['data']['token'] = 'eyJhbGciOiJIUzI1NiJ9...'
    response['timestamp'] = '2025-06-01T12:00:00Z'
    
    # 转JSON(键的插入顺序会被保留)
    json_str = json.dumps(response, ensure_ascii=False, indent=2)
    print(json_str)
    
    # 解析JSON时也可以保持顺序
    json_data = '{"c": 3, "a": 1, "b": 2}'
    parsed = json.loads(json_data, object_pairs_hook=OrderedDict)
    print(f'类型: {type(parsed)}')  # 
    print(f'内容: {parsed}')        # OrderedDict([('c', 3), ('a', 1), ('b', 2)])
    

    七、OrderedDict vs Python 3.7+普通字典

    7.1 什么时候仍需OrderedDict

    from collections import OrderedDict
    
    # Python 3.7+的普通字典已经保序了
    d = {}
    d['a'] = 1
    d['b'] = 2
    d['c'] = 3
    print(list(d.keys()))  # ['a', 'b', 'c']——按插入顺序
    
    # 那OrderedDict还有什么用?
    # 1. 需要move_to_end——普通字典没有这个方法
    # 2. 需要popitem(last=False)——弹出第一个元素
    # 3. 需要基于顺序的相等性比较
    # 4. 需要兼容Python 3.6及更早版本
    # 5. 需要向你的代码读者明确传达"顺序很重要"的意图
    
    # 性能对比
    import time
    
    n = 100000
    # OrderedDict有一些额外的开销(维护双向链表)
    start = time.perf_counter()
    for _ in range(100000):
        od = OrderedDict(zip(range(100), range(100)))
        _ = od.keys()
    print(f'OrderedDict: {time.perf_counter() - start:.3f}s')
    
    start = time.perf_counter()
    for _ in range(100000):
        d = dict(zip(range(100), range(100)))
        _ = d.keys()
    print(f'普通dict: {time.perf_counter() - start:.3f}s')
    # 普通字典通常更快一些
    

    7.2 选型建议

    需求推荐理由
    基本键值映射dict最简单、最快
    需要记住插入顺序(Python3.7+)dict已经内置保序
    需要记住插入顺序(Python3.6-)OrderedDict老版本不保序
    需要LRU缓存OrderedDictmove_to_end是核心
    需要弹出最旧条目OrderedDictpopitem(last=False)
    需要频繁的键不存在处理defaultdict自动默认值
    需要用顺序传达语义OrderedDict代码意图更明确
    需要最佳性能dict开销最小

    八、综合实战:用defaultdict + OrderedDict构建高级数据结构

    8.1 带时间窗口的频率计数器

    from collections import defaultdict, OrderedDict
    import time
    
    
    class TimeWindowCounter:
        """
        带时间窗口的计数器。
        
        用OrderedDict记录每次事件的时间戳,
        查询时自动剔除超出时间窗口的旧数据。
        按事件类型分组,每组维护一个时间戳有序字典。
        """
        
        def __init__(self, window_seconds=60):
            self.window_seconds = window_seconds
            # {event_type: OrderedDict{timestamp: count}}
            self._events = defaultdict(OrderedDict)
        
        def record(self, event_type):
            """记录一个事件"""
            now = time.time()
            bucket = int(now)  # 按秒分桶
            
            od = self._events[event_type]
            od[bucket] = od.get(bucket, 0) + 1
            
            # 清理过期数据(也可以在查询时清理)
            self._clean(event_type)
        
        def count(self, event_type):
            """查询指定事件类型在时间窗口内的总次数"""
            self._clean(event_type)
            od = self._events[event_type]
            return sum(od.values())
        
        def _clean(self, event_type):
            """清理超出时间窗口的旧数据"""
            now = time.time()
            cutoff = int(now) - self.window_seconds
            
            od = self._events[event_type]
            # 从最旧的开始检查,移除过期的
            expired_keys = [k for k in od if k < cutoff]
            for k in expired_keys:
                del od[k]
        
        def stats(self):
            """获取所有事件类型的统计"""
            result = {}
            for event_type in self._events:
                count = self.count(event_type)
                if count > 0:
                    result[event_type] = count
            return result
    
    
    # 使用示例
    counter = TimeWindowCounter(window_seconds=5)
    
    # 模拟事件
    counter.record('page_view')
    counter.record('page_view')
    counter.record('click')
    counter.record('page_view')
    
    print(f'当前统计: {counter.stats()}')
    
    print('等待3秒...')
    time.sleep(3)
    counter.record('page_view')
    counter.record('click')
    print(f'3秒后统计: {counter.stats()}')
    
    print('再等待3秒(第一批数据过期)...')
    time.sleep(3)
    print(f'过期后统计: {counter.stats()}')
    # 前3条page_view和1条click可能已过期
    

    8.2 多级分组 + 顺序保持

    from collections import defaultdict, OrderedDict
    
    
    class HierarchicalGrouper:
        """
        多级分组器。
        外层用defaultdict支持任意分组维度,
        内层用OrderedDict保持数据插入顺序。
        """
        
        def __init__(self):
            # 第一级:类别 → 第二级分组
            self._groups = defaultdict(OrderedDict)
        
        def add(self, primary_key, secondary_key, item):
            """添加条目"""
            if secondary_key not in self._groups[primary_key]:
                self._groups[primary_key][secondary_key] = []
            self._groups[primary_key][secondary_key].append(item)
        
        def get_primary(self, primary_key):
            """获取某个主类别的所有数据(按子类别顺序)"""
            if primary_key not in self._groups:
                return OrderedDict()
            return self._groups[primary_key]
        
        def get_secondary(self, primary_key, secondary_key):
            """获取某个子类别的数据"""
            return self._groups[primary_key].get(secondary_key, [])
        
        def iter_hierarchical(self):
            """按层次遍历所有数据"""
            for primary in sorted(self._groups):
                print(f'n? {primary}:')
                for secondary, items in self._groups[primary].items():
                    print(f'  ├─ {secondary}: {items}')
    
    
    # 使用示例:组织项目文件结构
    grouper = HierarchicalGrouper()
    
    # 模拟文件
    files = [
        ('src', 'models', 'user.py'),
        ('src', 'models', 'product.py'),
        ('src', 'views', 'home.py'),
        ('src', 'views', 'dashboard.py'),
        ('tests', 'unit', 'test_user.py'),
        ('tests', 'unit', 'test_product.py'),
        ('tests', 'integration', 'test_api.py'),
        ('docs', 'api', 'reference.md'),
        ('docs', 'guide', 'getting_started.md'),
        ('src', 'models', 'order.py'),  # 后添加的
    ]
    
    for category, subcategory, filename in files:
        grouper.add(category, subcategory, filename)
    
    grouper.iter_hierarchical()
    
    # 可以看到:
    # - 三大主类别按字母序排列
    # - 每个主类别下的子类别按插入顺序排列
    # - 每个子类别下的文件按插入顺序排列
    

    九、常见陷阱与注意事项

    9.1 defaultdict的序列化问题

    from collections import defaultdict
    import json
    
    # ⚠️ defaultdict不能直接用json序列化
    dd = defaultdict(int)
    dd['a'] = 1
    dd['b'] = 2
    
    # json.dumps(dd)  # TypeError: Object of type defaultdict is not JSON serializable
    
    # ✅ 解决方案:转成普通字典
    json_str = json.dumps(dict(dd))
    print(json_str)  # {"a": 1, "b": 2}
    
    # 或者自定义编码器
    class DefaultDictEncoder(json.JSONEncoder):
        def default(self, obj):
            if isinstance(obj, defaultdict):
                return dict(obj)
            return super().default(obj)
    
    json_str = json.dumps(dd, cls=DefaultDictEncoder)
    print(json_str)  # {"a": 1, "b": 2}
    

    9.2 defaultdict的拷贝行为

    from collections import defaultdict
    import copy
    
    dd = defaultdict(list, {'a': [1, 2], 'b': [3]})
    
    # 浅拷贝——default_factory被保留
    dd_shallow = copy.copy(dd)
    print(type(dd_shallow))  # 
    print(dd_shallow.default_factory)  # 
    
    # 转成普通字典——default_factory丢失
    dd_as_dict = dict(dd)
    print(type(dd_as_dict))  # 
    # 没有default_factory了
    
    # ⚠️ 如果你需要保留default_factory但想要独立的数据
    dd_deep = copy.deepcopy(dd)
    print(type(dd_deep))  # 
    print(dd_deep.default_factory)  # 
    # 深拷贝保留了一切,且数据完全独立
    

    9.3 pickle序列化OrderedDict

    from collections import OrderedDict
    import pickle
    
    # OrderedDict可以正常序列化
    od = OrderedDict([('a', 1), ('b', 2)])
    data = pickle.dumps(od)
    od_restored = pickle.loads(data)
    print(type(od_restored))  # 
    print(od_restored)        # OrderedDict([('a', 1), ('b', 2)])
    

    十、本篇小结

    collections模块中的两个字典变体,各自解决了普通字典的短板:

    defaultdict:

    • 核心价值:自动为不存在的键创建默认值
    • 创建方式:defaultdict(工厂函数),工厂函数是一个无参数的可调用对象
    • 常用工厂:list(分组)、int(计数)、set(去重收集)、float(累加)、dict(嵌套字典)
    • 原理:__missing__钩子方法
    • 注意:访问不存在的键会创建它,get()不会触发默认值
    • 注意:工厂函数必须是无参数的

    OrderedDict:

    • 核心价值:记住键的插入顺序(Python 3.7+普通字典也保序了,但OrderedDict有额外能力)
    • 独特方法:move_to_end(key, last=True)——移动键到末尾/开头
    • 独特方法:popitem(last=True)——弹出最后一个/第一个
    • 相等性:既比较内容也比较顺序
    • 经典应用:LRU缓存(用move_to_end + popitem)
    • 经典应用:去重保序(用fromkeys)
    • Python 3.7+普通字典保序后,大部分场景用dict就够了,但LRU等场景OrderedDict仍然不可替代

    学完字典体系(普通字典、推导式、合并、defaultdict、OrderedDict),你已经掌握了Python中最常用也最强大的数据结构。从下一篇开始,我们将进入一个新的数据类型——集合set,学习它独特的去重和集合运算能力。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 Python
    相关文章 更多
    精品专题 更多
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    macOS软件

    正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

    IOS软件

    正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

    AI

    正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

    Mac软件 更多
    AirBuddy 3 macOS版
    AirBuddy 3 macOS版

    AirBuddy 3 是面向 macOS 的蓝牙外设增强工具,可集中查看 AirPods、Beats 与苹果设备电量,提供快速连接、播放控制、电量提醒、桌面小组件和多台 Mac 外设接力等功能。

    AirServer macOS版
    AirServer macOS版

    AirServer macOS版可把 Mac 转换为无线投屏接收端,通过 AirPlay 或 Google Cast 接收手机、平板和电脑画面,并支持多设备同时连接、高清镜像及会话录制,适合家庭娱乐、课堂教学、会议演示和应用展示。

    AnyDesk macOS版
    AnyDesk macOS版

    AnyDesk macOS版是一款适用于远程办公、设备维护和客户支持的远程桌面工具,可连接并操作异地计算机,支持文件传输、无人值守访问、会话权限控制及跨平台协作。

    Beamer 4 macOS版
    Beamer 4 macOS版

    Beamer 4是面向macOS的本地视频投屏工具,可将Mac中的影片投放到Apple TV、Chromecast及兼容设备,支持常见格式、多类字幕、环绕声、播放列表和电视遥控操作。

    Boom 3D macOS版
    Boom 3D macOS版

    Boom 3D macOS版是一款系统级音效增强工具,可为音乐、电影、游戏和流媒体提供3D虚拟环绕声、31段均衡器、音量增强及应用独立音量控制,并支持本地播放器与多种音频输出设备。

    Boris FX macOS版
    Boris FX macOS版

    Boris FX macOS版是一套面向剪辑、合成、调色及动态图形工作的专业后期工具,涵盖视觉特效、平面与三维跟踪、转描、画面修复、粒子动画和音频清理,可配合多种主流宿主软件使用。

    Capto macOS版
    Capto macOS版

    Capto 是面向 macOS 的屏幕截图、高清录屏与媒体编辑工具,支持区域及整屏捕获、网页长截图、双音轨处理、图片和视频标注、素材分类与多渠道分享,适合教师、学生、内容创作者及需要制作演示教程的用户。

    ChemDraw macOS版
    ChemDraw macOS版

    ChemDraw macOS版是一款面向化学学习、科研和制药工作的专业结构绘图工具,可创建分子、反应及生物聚合物图形,并提供结构整理、格式转换和科研成果展示能力。

    Cursor Crane macOS版
    Cursor Crane macOS版

    Cursor Crane 是一款仅面向 macOS 的键盘操控效率工具,可通过提示标签、网格、文字查找和窗口模式完成点击、滚动、定位与切换,并支持屏幕内容提取、脚本及工作流扩展。

    Deep Freeze 冰点还原 macOS版
    Deep Freeze 冰点还原 macOS版

    Deep Freeze 冰点还原 macOS版通过冻结系统配置,让受保护的Mac在重新启动后回到预设状态,并提供解冻空间、维护计划、密码保护及Apple Remote Desktop管理能力。

    Focusky macOS版
    Focusky macOS版

    Focusky macOS版是一款面向教学课件、商务汇报、产品展示和动画宣传内容的演示制作工具。它通过无限画布、路径编排、缩放旋转转场及多媒体编辑,将传统逐页幻灯片转化为更具空间感的动态演示。

    Folder Colorizer 2 macOS版
    Folder Colorizer 2 macOS版

    Folder Colorizer 2 macOS版是一款直观的文件夹外观定制工具,可更换颜色,添加表情、贴纸及图片背景,并支持批量处理、智能推荐和恢复默认样式,帮助用户更快辨认工作、学习与生活资料。

    WINDOWS 更多
    讯飞绘文 网页版
    讯飞绘文 网页版

    讯飞绘文网页版是科大讯飞推出的AI图文创作与内容运营工具,覆盖热点选题、文章生成、稿件校对、智能配图、排版发布和数据复盘等环节,适合自媒体创作者及内容团队在线使用。

    AirServer Windows版
    AirServer Windows版

    AirServer Windows版可把电脑变成AirPlay、Google Cast和Miracast无线投屏接收端,适合会议演示、课堂教学及家庭大屏分享。发送设备通常无需另装客户端,并可同时接收和智能排列多个镜像会话。

    AlwaysUp Windows版
    AlwaysUp Windows版

    AlwaysUp是一款面向Windows的系统服务注册与程序托管工具,可让普通应用、脚本及批处理在无人登录时随系统启动,并通过状态检测、异常重启、资源监控和邮件通知保障关键任务持续运行。

    AnyDesk Windows版
    AnyDesk Windows版

    AnyDesk Windows版是一款轻量化远程桌面与技术支持工具,可用于控制异地电脑、传输文件、远程打印及无人值守访问。它兼顾连接响应、安全权限和部署灵活性,适合个人远程办公、企业设备维护与客服支持。

    Boom 3D Windows版
    Boom 3D Windows版

    Boom 3D Windows版是一款面向音乐、电影和游戏场景的系统级音效增强工具,支持虚拟3D环绕声、31段均衡器、预设音效、应用音量控制及本地音乐播放,可配合耳机、扬声器和蓝牙耳机使用。

    Boris FX Windows版
    Boris FX Windows版

    Boris FX Windows版是一套服务于影视剪辑、视觉特效、运动跟踪、转描、图像修饰及音频修复的专业创作工具集合,可作为插件接入多种主流后期宿主,也包含可独立运行的应用,适合剪辑师、特效师与内容创作者使用。

    ChemDraw Windows版
    ChemDraw Windows版

    ChemDraw Windows版是一款面向化学研究、药物研发、材料科学和教学工作的专业结构绘图工具,可快速绘制分子、反应式及生物聚合物,并提供结构整理、格式转换、性质计算和科研文档协作能力。

    CODESOFT Windows版
    CODESOFT Windows版

    CODESOFT Windows版是一套企业级条码与RFID标签设计打印工具,可连接Excel、Access、SQL及云端数据源,支持变量、表单、GS1条码、数据库记录和多种工业打印机,适合制造、仓储、物流及合规标识场景。

    Deep Freeze 冰点还原 Windows版
    Deep Freeze 冰点还原 Windows版

    Deep Freeze 冰点还原 Windows版通过重启恢复技术保护系统盘及指定分区,自动撤销恶意程序、误操作和配置漂移造成的改动,并支持密码保护、解冻空间与多硬盘管理,适合学校机房、公共电脑、办公终端及家庭电脑使用。

    EDIUS Windows版
    EDIUS Windows版

    EDIUS 11 Windows版是一款面向影视制作、广播电视、纪录片、短视频及教育机构的专业非线性编辑工具,支持4K/8K、多格式混编、HDR调色、字幕、音频处理和后台渲染,并提供素材管理及丰富的导出预设。

    FastStone Capture Windows版
    FastStone Capture Windows版

    FastStone Capture Windows版是一款轻量级屏幕捕捉、录屏与图像编辑工具,可截取窗口、区域、菜单和滚动页面,并提供标注、模糊、取色、OCR、录音录像及多格式导出能力,适合办公说明、教程制作和问题反馈。

    Focusky Windows版
    Focusky Windows版

    Focusky Windows版是一款动画演示与交互课件制作工具,通过无限画布、缩放旋转镜头、模板素材和对象动画组织内容,可导入PPTX,并输出视频、EXE、PDF、网页等格式,适用于课堂教学、微课、商务汇报及产品展示。