展开菜单
首页 精品内容 本月促销 装机必备 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软件 更多
    讯飞智作 网页版
    讯飞智作 网页版

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    立刻MV 网页版
    立刻MV 网页版

    立刻MV 面向音乐人、内容创作者和品牌团队,帮助用户低成本完成歌曲 MV、歌词视频、AI 分镜、图生视频与最终合成。

    蛙蛙写作 网页版
    蛙蛙写作 网页版

    蛙蛙写作 AI 创作工具,覆盖小说、剧本、文案,一键生成文稿,打通文字转视频全流程,高效解决创作难题。

    超级简历WonderCV 网页版
    超级简历WonderCV 网页版

    超级简历WonderCV是面向应届生和AI小白的智能求职平台,覆盖简历、面试与职业规划。

    音述AI 网页版
    音述AI 网页版

    音述AI,是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区。

    AionClaw macOS版
    AionClaw macOS版

    AionClaw 是可在桌面端运行的任务型 AI 助手,通过数字专家与技能市场组合办公、创作、数据和开发能力。

    Loomy macOS版
    Loomy macOS版

    Loomy 是科大讯飞推出的桌面 AI 助手,可通过对话、任务与工作目录协助处理办公、内容创作和资料整理。

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

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。

    讯飞绘文 网页版
    讯飞绘文 网页版

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

    Maya(Autodesk Maya) Windows版
    Maya(Autodesk Maya) Windows版

    Autodesk Maya 是面向影视、电视、动画和游戏行业的专业三维制作软件,集多边形与 NURBS 建模、角色绑定、关键帧和动作捕捉动画、Bifrost 特效模拟、材质开发及 Arnold 渲染于一体,

    Aperty Windows版
    Aperty Windows版

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    立刻MV 网页版
    立刻MV 网页版

    立刻MV 面向音乐人、内容创作者和品牌团队,帮助用户低成本完成歌曲 MV、歌词视频、AI 分镜、图生视频与最终合成。

    蛙蛙写作 网页版
    蛙蛙写作 网页版

    蛙蛙写作 AI 创作工具,覆盖小说、剧本、文案,一键生成文稿,打通文字转视频全流程,高效解决创作难题。

    超级简历WonderCV 网页版
    超级简历WonderCV 网页版

    超级简历WonderCV是面向应届生和AI小白的智能求职平台,覆盖简历、面试与职业规划。

    音述AI 网页版
    音述AI 网页版

    音述AI,是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区。

    AionClaw Windows版
    AionClaw Windows版

    AionClaw 是可在桌面端运行的任务型 AI 助手,通过数字专家与技能市场组合办公、创作、数据和开发能力。

    Loomy Windows版
    Loomy Windows版

    Loomy 是科大讯飞推出的桌面 AI 助手,可通过对话、任务与工作目录协助处理办公、内容创作和资料整理。