1. 字典是什么,以及为什么它如此重要
如果你刚开始学Python,或者已经写过一些脚本,那么dict(字典)这个数据结构,你肯定绕不过去。它几乎无处不在,从读取配置文件、处理JSON数据,到构建缓存、管理对象属性,字典都扮演着核心角色。很多人觉得字典不就是“键值对”嘛,有什么好讲的?但恰恰是这种看似简单的结构,里面藏着不少门道和“坑”。用好了,你的代码简洁高效;用不好,可能就是性能瓶颈和Bug的温床。
简单来说,Python的字典是一个可变、无序的映射类型,它存储的是键(key)和值(value)的对应关系。键必须是不可变类型(如字符串、数字、元组),而值可以是任意Python对象。它的核心优势在于,通过键来查找、插入或删除对应的值,其平均时间复杂度是O(1),也就是常数时间,速度极快。这背后是哈希表(Hash Table)的功劳。你可以把它想象成一个超级高效的“电话本”:你知道一个人的名字(键),就能瞬间找到他的电话号码(值),而不需要从头到尾翻一遍。
为什么我要专门总结字典的常用操作?因为在日常开发中,我看到太多人只停留在dict[key] = value和dict.get(key)的层面。实际上,字典提供了丰富的方法和技巧来处理更复杂的场景,比如合并多个字典、安全地访问嵌套数据、按照特定条件筛选内容,甚至是利用字典来实现一些巧妙的逻辑。掌握这些,能让你从“会用字典”升级到“善用字典”,写出更Pythonic、更健壮的代码。
2. 字典的创建与初始化:不止一种方式
创建字典是最基础的一步,但你知道有多少种方法吗?每种方法适用的场景又是什么?这里我为你梳理了最实用的几种。
2.1 最直接的方式:花括号{}
这是最常用、最直观的方法,适合在代码中直接定义已知的键值对。
# 创建一个空字典 empty_dict = {} # 创建一个包含初始数据的字典 user_info = { "name": "张三", "age": 30, "city": "北京" }这种方式清晰明了,键和值一目了然。在定义配置、常量映射或者小型数据集合时,我强烈推荐使用它。
2.2 使用dict()构造函数
dict()函数更加灵活,它可以从多种数据结构构造字典。
# 1. 使用关键字参数(键必须是合法的变量名,即字符串且不含特殊字符) dict1 = dict(name="李四", age=25, city="上海") # 结果: {'name': '李四', 'age': 25, 'city': '上海'} # 2. 使用包含二元组(或列表)的可迭代对象 dict2 = dict([("name", "王五"), ("age", 28), ("city", "广州")]) # 结果: {'name': '王五', 'age': 28, 'city': '广州'} # 3. 使用zip函数合并两个列表 keys = ["a", "b", "c"] values = [1, 2, 3] dict3 = dict(zip(keys, values)) # 结果: {'a': 1, 'b': 2, 'c': 3}什么时候用dict()?当你的数据源是动态生成的(比如从两个列表合并而来),或者键名包含连字符等不能作为变量名的字符时(这时不能用关键字参数方式),dict()就派上用场了。例如,从API接口拿到两个列表,一个存字段名,一个存值,用zip加dict()就能快速组装成字典。
2.3 字典推导式:优雅且强大
如果你需要对数据进行一些处理再生成字典,字典推导式是你的最佳选择。它的语法和列表推导式类似,非常Pythonic。
# 将一个列表的元素作为键,其平方作为值 numbers = [1, 2, 3, 4, 5] squared_dict = {x: x**2 for x in numbers} # 结果: {1: 1, 2: 4, 3: 9, 4: 16, 5: 25} # 带条件过滤的推导式 even_squared = {x: x**2 for x in numbers if x % 2 == 0} # 结果: {2: 4, 4: 16} # 处理两个列表,键值都做转换 keys = ["apple", "banana", "cherry"] prices = [10, 20, 30] price_dict = {k.upper(): v*1.1 for k, v in zip(keys, prices)} # 键转大写,值加10%税 # 结果: {'APPLE': 11.0, 'BANANA': 22.0, 'CHERRY': 33.0}字典推导式不仅代码简洁,而且执行效率通常也高于显式的循环。在处理数据转换和过滤时,我几乎总是首选它。
2.4 使用fromkeys方法快速创建默认字典
当你需要创建一个新字典,并且所有键都对应同一个初始值时,fromkeys方法非常方便。
# 为多个键设置相同的默认值 default_dict = dict.fromkeys(["key1", "key2", "key3"], "default_value") # 结果: {'key1': 'default_value', 'key2': 'default_value', 'key3': 'default_value'} # 如果不提供第二个参数,默认值为None keys_only = dict.fromkeys(["a", "b", "c"]) # 结果: {'a': None, 'b': None, 'c': None}这个方法在初始化计数器、状态标记或者缓存结构时特别有用。但要注意,如果默认值是一个可变对象(如列表、字典),所有键会共享同一个对象引用,这通常不是你想要的,会导致意外的数据污染。我们后面在“坑与技巧”部分会详细讨论这个问题。
3. 核心操作:增删改查的学问
创建了字典,接下来就是对它进行“增删改查”。这些操作看似基础,但细节决定成败。
3.1 访问元素:安全第一
访问字典元素最直接的方式是用方括号[],但如果键不存在,会抛出KeyError异常。
my_dict = {"a": 1, "b": 2} value = my_dict["a"] # 正确,得到 1 # value = my_dict["c"] # 错误!KeyError: 'c'在不确定键是否存在时,更安全的做法是使用get(key, default)方法。如果键存在,返回对应的值;如果不存在,则返回你指定的默认值(默认为None),而不会引发异常。
value = my_dict.get("c") # 返回 None value = my_dict.get("c", 0) # 返回指定的默认值 0我的经验是:在大多数从外部获取数据(如解析JSON、读取数据库)后访问字典的场景中,优先使用get方法。这能有效避免因为数据格式意外变化而导致的程序崩溃。只有在你百分之百确定键一定存在时,才使用[]直接访问。
3.2 新增与修改元素:本质相同
向字典中添加新元素或修改现有元素,语法是一样的:dict[key] = value。如果键已存在,则更新其值;如果不存在,则创建新的键值对。
my_dict = {} my_dict["new_key"] = "new_value" # 新增 my_dict["new_key"] = "updated_value" # 修改3.3 删除元素:多种方法各有用处
删除操作有几种方式,适用于不同场景:
del语句:直接删除指定键的项。如果键不存在,同样会引发KeyError。my_dict = {"a": 1, "b": 2, "c": 3} del my_dict["b"] # 现在 my_dict 是 {'a': 1, 'c': 3} # del my_dict["d"] # KeyError!pop(key, default)方法:删除指定键的项,并返回被删除的值。这是del的“有返回值”版本。如果提供了default参数且键不存在,则返回default而不抛异常;否则抛KeyError。value = my_dict.pop("a") # 删除键'a',并返回1 # my_dict 现在是 {'c': 3} value = my_dict.pop("d", None) # 键'd'不存在,返回None,不抛异常popitem()方法:在Python 3.7+中,字典会保持插入顺序。popitem()会移除并返回最后插入的(LIFO顺序)键值对,作为一个元组。对于空字典调用会引发KeyError。这个方法在实现栈或缓存淘汰(如LRU Cache的简单实现)时很有用。my_dict = {"a": 1, "b": 2} key, value = my_dict.popitem() # 移除并返回 ('b', 2)clear()方法:清空字典中的所有项,使其变为空字典{}。my_dict.clear() # my_dict 现在是 {}
选择建议:如果你需要知道被删除的值,用pop;如果只是要删除,用del;如果想清空整个字典,用clear;popitem则在特定顺序操作场景下使用。
3.4 检查键是否存在:in操作符
判断一个键是否在字典中,使用in操作符。这是最快、最Pythonic的方式。
my_dict = {"a": 1, "b": 2} if "a" in my_dict: print("键 'a' 存在") # 检查键不存在 if "c" not in my_dict: print("键 'c' 不存在")不要用my_dict.get(key)是否等于None来判断,因为值本身可能就是None,这样会产生歧义。
4. 遍历与迭代:高效获取数据
字典的遍历有多种方式,分别用于获取键、值或键值对。理解它们的区别和性能影响很重要。
4.1 遍历键:最直接的方式
直接对字典进行迭代,默认就是遍历它的键。
my_dict = {"name": "Alice", "age": 25, "city": "London"} for key in my_dict: print(key) # 输出: # name # age # city你也可以显式地使用keys()方法,它返回一个视图对象(dict_keys),这个视图会动态反映字典的变化。
for key in my_dict.keys(): print(key)在大多数情况下,直接迭代字典和迭代my_dict.keys()效果一样。但keys()视图在某些需要集合操作的场景下更方便,比如求两个字典键的交集:my_dict.keys() & other_dict.keys()。
4.2 遍历值:使用values()方法
如果你只关心字典中的值,可以使用values()方法,它返回一个dict_values视图。
for value in my_dict.values(): print(value) # 输出: # Alice # 25 # London4.3 遍历键值对:使用items()方法
这是最常用的遍历方式,可以同时拿到键和值。items()返回一个dict_items视图,其中每个元素是一个(key, value)元组。
for key, value in my_dict.items(): print(f"{key}: {value}") # 输出: # name: Alice # age: 25 # city: London重要技巧:在遍历过程中修改字典(如删除项)是危险的,可能会引发RuntimeError。安全的做法是先收集要处理的键,遍历结束后再统一修改,或者遍历其键或项的副本。
# 错误示例:在遍历时删除 # for key in my_dict: # if some_condition(key): # del my_dict[key] # 可能引发 RuntimeError # 正确做法1:先记录要删除的键 keys_to_delete = [] for key, value in my_dict.items(): if value == 25: # 假设要删除值为25的项 keys_to_delete.append(key) for key in keys_to_delete: del my_dict[key] # 正确做法2(Python 3+):遍历keys()的副本 for key in list(my_dict.keys()): if my_dict[key] == 25: del my_dict[key]5. 字典的合并与更新
在实际项目中,我们经常需要将多个字典的内容合并到一起。Python提供了几种方法,它们的行为有细微差别。
5.1update()方法:就地更新
update()方法将一个字典(或键值对序列)中的所有项合并到当前字典中。如果键重复,后者的值会覆盖前者的值。这个操作是就地修改原字典。
dict1 = {"a": 1, "b": 2} dict2 = {"b": 3, "c": 4} dict1.update(dict2) print(dict1) # 输出: {'a': 1, 'b': 3, 'c': 4} # dict1被修改了,dict2不变update()也可以接受关键字参数或其他可迭代对象。
dict1.update(d=5, e=6) # 使用关键字参数 # dict1 现在是 {'a': 1, 'b': 3, 'c': 4, 'd': 5, 'e': 6}5.2 合并运算符|和|=(Python 3.9+)
从Python 3.9开始,引入了字典合并运算符,让合并操作更直观。
|(合并运算符):创建一个新字典,包含两个字典的所有项。重复键的值来自右边的操作数。dict1 = {"a": 1, "b": 2} dict2 = {"b": 3, "c": 4} merged_dict = dict1 | dict2 print(merged_dict) # 输出: {'a': 1, 'b': 3, 'c': 4} print(dict1) # 输出: {'a': 1, 'b': 2} (原字典未变)|=(更新运算符):相当于update()的增强版,就地更新左边的字典。dict1 = {"a": 1, "b": 2} dict2 = {"b": 3, "c": 4} dict1 |= dict2 print(dict1) # 输出: {'a': 1, 'b': 3, 'c': 4}
如何选择?如果你需要保留原始字典不变,就使用|运算符或后面提到的字典解包。如果你明确要修改原字典,使用update()或|=。
5.3 字典解包**(Python 3.5+)
在函数调用和字典字面量中,可以使用**运算符来解包字典。这为合并多个字典提供了一种非常优雅的方式。
dict1 = {"a": 1, "b": 2} dict2 = {"b": 3, "c": 4} dict3 = {"d": 5} # 合并多个字典到一个新字典 merged_dict = {**dict1, **dict2, **dict3} print(merged_dict) # 输出: {'a': 1, 'b': 3, 'c': 4, 'd': 5} # 注意:dict2中的'b'覆盖了dict1中的'b'解包语法非常灵活,你可以在创建新字典时混合使用解包和显式的键值对。
base_config = {"host": "localhost", "port": 8080} user_config = {"port": 9090, "debug": True} final_config = {**base_config, **user_config, "timeout": 30} # 结果: {'host': 'localhost', 'port': 9090, 'debug': True, 'timeout': 30}6. 字典的常用方法进阶
除了基础的增删改查,字典还有一些内置方法能解决特定问题,极大地提升代码效率。
6.1setdefault(key, default):安全的“获取或设置”
这个方法用于安全地获取一个值。如果键存在,则返回其值;如果键不存在,则先将key: default插入字典,再返回default。
my_dict = {"a": 1} # 键存在,直接返回值 value = my_dict.setdefault("a", 100) print(value) # 输出: 1 print(my_dict) # 输出: {'a': 1} (字典未变) # 键不存在,插入并返回默认值 value = my_dict.setdefault("b", 2) print(value) # 输出: 2 print(my_dict) # 输出: {'a': 1, 'b': 2} (字典已更新)经典应用场景:分组计数或初始化复杂值。比如统计一段文本中单词出现的频率:
text = "apple banana apple orange banana apple" word_count = {} for word in text.split(): # 如果word不在字典中,将其计数初始化为0,然后加1 # 如果word已在字典中,获取当前计数,然后加1 word_count[word] = word_count.setdefault(word, 0) + 1 print(word_count) # 输出: {'apple': 3, 'banana': 2, 'orange': 1}在没有setdefault的情况下,你需要写一个if...else判断,代码会冗长一些。
6.2 视图对象:keys(),values(),items()
前面提到这些方法返回的是“视图对象”,而不是列表。这一点非常重要。视图对象是动态的,它们会实时反映字典的变化,并且支持集合操作。
my_dict = {"a": 1, "b": 2} keys_view = my_dict.keys() values_view = my_dict.values() items_view = my_dict.items() print(keys_view) # 输出: dict_keys(['a', 'b']) print(values_view) # 输出: dict_values([1, 2]) print(items_view) # 输出: dict_items([('a', 1), ('b', 2)]) # 动态性演示 my_dict["c"] = 3 print(keys_view) # 输出: dict_keys(['a', 'b', 'c']) 视图同步更新了!视图对象还支持集合的交集(&)、并集(|)、差集(-)等操作(values()视图除外,因为值可能不唯一)。
dict1 = {"a": 1, "b": 2, "c": 3} dict2 = {"b": 20, "c": 3, "d": 4} # 找出两个字典中都有的键 common_keys = dict1.keys() & dict2.keys() print(common_keys) # 输出: {'b', 'c'} # 找出在dict1中但不在dict2中的键 unique_to_dict1 = dict1.keys() - dict2.keys() print(unique_to_dict1) # 输出: {'a'}6.3copy():浅拷贝与深拷贝的陷阱
copy()方法返回字典的一个浅拷贝(shallow copy)。这意味着它创建了一个新字典,但新字典中的值是对原字典中值的引用(对于可变对象)。
original = {"list": [1, 2, 3], "num": 10} shallow_copied = original.copy() # 修改浅拷贝字典中的不可变值,不影响原字典 shallow_copied["num"] = 20 print(original["num"]) # 输出: 10 (未变) # 修改浅拷贝字典中的可变值(列表),原字典也会被影响! shallow_copied["list"].append(4) print(original["list"]) # 输出: [1, 2, 3, 4] (被修改了!)这就是一个常见的“坑”。如果你需要完全独立地复制一个字典,包括它内部所有嵌套的可变对象,你需要使用copy模块的deepcopy函数。
import copy original = {"list": [1, 2, 3]} deep_copied = copy.deepcopy(original) deep_copied["list"].append(4) print(original["list"]) # 输出: [1, 2, 3] (不受影响)经验法则:如果字典的值只包含不可变对象(数字、字符串、元组),用copy()就够了。如果字典嵌套了列表、字典或其他可变对象,并且你希望副本完全独立,一定要用deepcopy()。
7. 嵌套字典与复杂数据访问
现实世界的数据很少是扁平的。我们经常要处理嵌套的字典,比如从JSON API返回的数据。安全、优雅地访问嵌套数据是一项必备技能。
7.1 链式访问与KeyError风险
访问嵌套字典最直接的方式是链式使用[]。
data = { "user": { "name": "Bob", "address": { "city": "New York", "zipcode": "10001" } } } city = data["user"]["address"]["city"] # 得到 'New York'但这种方式的缺点是,如果中间任何一层键不存在,就会立即抛出KeyError,导致程序中断。
7.2 使用get()方法进行安全链式访问
我们可以通过多次调用get()来避免异常,但代码会变得冗长。
city = data.get("user", {}).get("address", {}).get("city") # 如果任何一层缺失,city将为None这虽然安全,但写起来麻烦,尤其是嵌套很深的时候。
7.3 更优雅的解决方案:collections模块的ChainMap
ChainMap可以将多个字典链接成一个逻辑上的单一映射。查找时,它会按顺序在多个字典中搜索,直到找到第一个匹配的键。这可以用来为嵌套访问提供默认值,但它更适合管理多个上下文的配置,而不是纯粹的深层访问。
7.4 终极方案:自定义递归访问函数或使用第三方库
对于复杂的嵌套字典访问,一个更健壮的方法是写一个辅助函数。
def safe_get(dict_obj, keys, default=None): """ 安全地获取嵌套字典中的值。 :param dict_obj: 目标字典 :param keys: 键的列表或元组,如 ['user', 'address', 'city'] :param default: 如果路径不存在,返回的默认值 :return: 找到的值或默认值 """ current = dict_obj for key in keys: if isinstance(current, dict) and key in current: current = current[key] else: return default return current # 使用示例 city = safe_get(data, ["user", "address", "city"]) # 返回 'New York' country = safe_get(data, ["user", "address", "country"], "USA") # 路径不存在,返回默认值 'USA'对于大型项目,你可能会考虑使用像glom或jmespath这样的第三方库,它们提供了更强大、更声明式的语法来查询复杂的嵌套结构。
8. 字典排序:从无序到有序
在Python 3.7之前,字典是无序的,你无法依赖其插入顺序。从Python 3.7开始,字典会保持插入顺序,这是一个官方保证的语言特性。这意味着你遍历字典时,项的顺序就是你添加它们的顺序。
但“保持插入顺序”不等于“按某种规则排序”。如果你需要按键或按值进行排序,仍然需要额外的操作。
8.1 按键排序
使用内置的sorted()函数,它可以对字典的键(通过keys()获取)进行排序,然后你可以根据排序后的键来访问值。
my_dict = {"banana": 3, "apple": 4, "pear": 1, "orange": 2} # 按键升序排序 for key in sorted(my_dict.keys()): print(key, my_dict[key]) # 输出: # apple 4 # banana 3 # orange 2 # pear 1 # 按键降序排序 for key in sorted(my_dict.keys(), reverse=True): print(key, my_dict[key])更简洁的方式是直接对items()返回的(key, value)元组进行排序。因为元组比较时,默认先比较第一个元素(即键)。
# 按键排序,并直接获得排序后的键值对列表 sorted_items = sorted(my_dict.items()) print(sorted_items) # 输出: [('apple', 4), ('banana', 3), ('orange', 2), ('pear', 1)] for key, value in sorted_items: print(key, value)8.2 按值排序
按值排序稍微复杂一点,因为我们需要告诉sorted()函数根据每个键值对的第二个元素(即值)来排序。这可以通过key参数实现。
my_dict = {"banana": 3, "apple": 4, "pear": 1, "orange": 2} # 按值升序排序 sorted_by_value = sorted(my_dict.items(), key=lambda item: item[1]) print(sorted_by_value) # 输出: [('pear', 1), ('orange', 2), ('banana', 3), ('apple', 4)] # 按值降序排序 sorted_by_value_desc = sorted(my_dict.items(), key=lambda item: item[1], reverse=True) print(sorted_by_value_desc) # 输出: [('apple', 4), ('banana', 3), ('orange', 2), ('pear', 1)]这里的lambda item: item[1]是一个匿名函数,它接收一个元组item(如('banana', 3)),然后返回其第二个元素3,sorted()函数就根据这个返回值进行排序。
8.3 使用operator模块
对于简单的按值排序,使用lambda很清晰。但operator模块提供了更高效且可读性可能更好的方式。
import operator # 按值排序(使用itemgetter获取元组的第二个元素) sorted_by_value = sorted(my_dict.items(), key=operator.itemgetter(1)) # 按键排序(使用itemgetter获取元组的第一个元素) sorted_by_key = sorted(my_dict.items(), key=operator.itemgetter(0))operator.itemgetter(1)会创建一个函数,这个函数的作用和lambda x: x[1]一样,但通常性能稍好一些,尤其是在处理大量数据时。
重要提示:sorted()函数返回的是一个列表,而不是字典。如果你需要一个保持排序顺序的字典,Python 3.7+的普通字典就可以,因为你只需要按排序后的顺序插入项。或者,你可以使用collections.OrderedDict(在Python 3.7后与普通字典在有序性上功能重叠,但仍有其特定方法)。
# 创建一个按键排序的新字典 (Python 3.7+) sorted_dict = dict(sorted(my_dict.items(), key=lambda item: item[0])) print(sorted_dict) # 输出: {'apple': 4, 'banana': 3, 'orange': 2, 'pear': 1} # 这个字典的遍历顺序就是按键排序的顺序9. 字典与JSON的亲密关系
字典和JSON(JavaScript Object Notation)可以说是天生一对。JSON是一种轻量级的数据交换格式,其结构(对象、数组、字符串、数字、布尔值、null)几乎可以直接映射到Python的数据类型(字典、列表、字符串、int/float、True/False、None)。
9.1 字典转JSON字符串:json.dumps()
使用json模块的dumps()函数,可以将字典序列化为JSON格式的字符串。
import json data_dict = { "name": "小明", "age": 20, "courses": ["数学", "物理"], "is_student": True, "address": None } json_str = json.dumps(data_dict, ensure_ascii=False, indent=2) print(json_str) # 输出格式化的JSON字符串: # { # "name": "小明", # "age": 20, # "courses": ["数学", "物理"], # "is_student": true, # "address": null # }参数说明:
ensure_ascii=False:确保中文字符正常显示,而不是被转义为\u形式。indent=2:使输出的JSON字符串带有缩进,更易读。在生产环境为了节省空间通常不设置。sort_keys=True:可选参数,对输出的键进行排序。
9.2 JSON字符串转字典:json.loads()
反过来,使用loads()函数可以将JSON字符串解析为Python字典。
json_string = '{"name": "小红", "age": 22, "city": "上海"}' parsed_dict = json.loads(json_string) print(parsed_dict["name"]) # 输出: 小红 print(type(parsed_dict)) # 输出: <class 'dict'>9.3 处理文件
更常见的场景是从文件读取JSON或写入JSON。
# 将字典写入JSON文件 with open("data.json", "w", encoding="utf-8") as f: json.dump(data_dict, f, ensure_ascii=False, indent=2) # 从JSON文件读取到字典 with open("data.json", "r", encoding="utf-8") as f: loaded_dict = json.load(f)踩坑提醒:JSON和Python的数据类型并非完全一致。JSON中的true、false、null对应Python的True、False、None。JSON的数值都是浮点数,但在Python中json.loads()会智能地解析为int或float。最重要的是,JSON的键必须是字符串,而Python字典的键可以是多种不可变类型。如果你尝试序列化一个键为元组的字典,会得到TypeError。
10. 性能考量与高级技巧
字典以其O(1)的平均时间复杂度而闻名,但了解其内部机制和边界条件,能帮助你写出性能更好的代码。
10.1 键的选择:为什么必须是不可变类型?
字典的快速查找依赖于哈希表。每个键都会被计算出一个哈希值(通过__hash__方法),这个值用于确定键值对在内存中的存储位置。如果键是可变对象(如列表、字典),其内容变化会导致哈希值变化,这就破坏了哈希表的完整性,使得之前存储的值无法被正确找到。因此,Python要求字典的键必须是“可哈希的”(hashable),即不可变对象(如数字、字符串、元组(仅当元组内所有元素也都是可哈希的))。
# 有效的键 valid_dict = { 1: "integer", # 整数 "hello": "string", # 字符串 (1, 2): "tuple", # 元组(元素都是不可变的) # [1,2]: "list" # 错误!列表不可哈希 # {"a":1}: "dict" # 错误!字典不可哈希 }10.2 字典的大小与扩容
字典在内部会维护一个哈希表。当字典中的项数增加,使得负载因子(已用槽位/总槽位)超过某个阈值时,字典会进行扩容(resize),即分配一个更大的内存空间,并重新哈希(rehash)所有现有的键。这是一个相对昂贵的操作(O(n)时间复杂度)。
这意味着,如果你能提前知道字典的大致规模,在创建时指定一个初始容量,可以避免多次扩容,提升性能。
# 如果你知道大约要存1000个项,可以这样创建 d = dict.fromkeys(range(1000)) # 一种方式 # 或者,更直接地(虽然不能精确控制,但给了提示) d = {i: None for i in range(1000)} # Python解释器会根据这个规模进行优化10.3in操作符 vsget()vs[]的性能
检查一个键是否在字典中,in操作符是最快的方式,因为它只进行哈希查找,不涉及值的返回或异常处理。get()方法在键不存在时返回默认值,比先用in检查再用[]访问要快,因为get()只做一次哈希查找。而直接使用[]访问不存在的键会引发异常,异常处理的开销是最大的。
性能排序(从快到慢):in≈get()(键存在时) >[](键存在时) >get()(键不存在,但避免了异常) >>[](键不存在,引发异常)。
10.4 使用字典模拟Switch-Case语句
Python没有switch-case语句,但我们可以用字典巧妙地模拟,使代码更简洁。
def handle_operation(operation, a, b): # 定义一个操作到函数的映射字典 operations = { "add": lambda x, y: x + y, "subtract": lambda x, y: x - y, "multiply": lambda x, y: x * y, "divide": lambda x, y: x / y if y != 0 else "Division by zero" } # 使用get方法,并提供默认处理函数 func = operations.get(operation, lambda x, y: f"Unknown operation: {operation}") return func(a, b) print(handle_operation("add", 5, 3)) # 输出: 8 print(handle_operation("power", 2, 3)) # 输出: Unknown operation: power这种方法将逻辑分发(dispatch)从一长串if-elif-else语句中解放出来,使代码更易于维护和扩展。
10.5 使用字典进行缓存(Memoization)
字典是实现缓存(Memoization)的绝佳数据结构,常用于优化递归函数,避免重复计算。
def fibonacci(n, cache={}): """使用缓存计算斐波那契数列""" if n in cache: return cache[n] if n <= 1: result = n else: result = fibonacci(n-1, cache) + fibonacci(n-2, cache) cache[n] = result return result print(fibonacci(50)) # 如果没有缓存,这个计算会非常慢这里利用了一个技巧:默认参数cache={}在函数定义时被求值,并且对于每次函数调用,如果调用者没有提供cache参数,就会使用这个同一个默认字典对象。这相当于在函数外部隐藏了一个全局缓存,但封装得更好。不过要小心,这个缓存会在多次调用fibonacci时持续存在并累积。