MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python字典的高级用法

2021-10-281.9k 阅读

一、字典推导式

在Python中,字典推导式是一种非常强大且简洁的创建字典的方式。它允许我们根据现有的可迭代对象,通过指定的规则快速生成新的字典。

基本语法

{key_expression: value_expression for item in iterable}

例如,我们有一个列表nums = [1, 2, 3, 4],现在要创建一个字典,键是列表中的数字,值是该数字的平方,代码如下:

nums = [1, 2, 3, 4]
square_dict = {num: num ** 2 for num in nums}
print(square_dict)

上述代码中,num 是从 nums 列表中依次取出的元素,num: num ** 2 定义了键值对,其中键为 num,值为 num 的平方。

我们还可以在推导式中添加条件语句,对可迭代对象中的元素进行筛选。例如,只对偶数进行平方操作:

nums = [1, 2, 3, 4]
even_square_dict = {num: num ** 2 for num in nums if num % 2 == 0}
print(even_square_dict)

这里的 if num % 2 == 0 就是筛选条件,只有满足该条件的元素才会参与字典的构建。

二、字典的解包

  1. 字典解包用于函数调用 在Python中,我们可以使用字典解包将字典中的键值对作为参数传递给函数。假设有一个函数 print_info,它接受 nameage 两个参数:
def print_info(name, age):
    print(f"Name: {name}, Age: {age}")


info_dict = {'name': 'Alice', 'age': 25}
print_info(**info_dict)

print_info(**info_dict) 中,**info_dict 就是字典解包操作。它会将 info_dict 字典中的键值对按照函数参数的名称进行匹配传递。

  1. 字典合并 字典解包还可以用于合并字典。例如,我们有两个字典 dict1dict2,要将它们合并成一个新的字典:
dict1 = {'a': 1, 'b': 2}
dict2 = {'c': 3, 'd': 4}
merged_dict = {**dict1, **dict2}
print(merged_dict)

这里通过 {**dict1, **dict2} 的方式,将 dict1dict2 合并成了一个新的字典 merged_dict。如果两个字典中有相同的键,后面字典中的值会覆盖前面字典中的值。

三、defaultdict的使用

defaultdictcollections 模块中的一个类,它继承自 dictdefaultdict 的特点是当访问一个不存在的键时,它会自动创建这个键,并为其赋予一个默认值,而不会像普通字典那样抛出 KeyError

创建 defaultdict

from collections import defaultdict

# 创建一个默认值为0的defaultdict
num_dict = defaultdict(int)
print(num_dict['new_key'])

在上述代码中,我们创建了一个 defaultdict,默认值类型为 int。当访问不存在的键 'new_key' 时,它会自动创建这个键,并将其值设为 int() 的返回值,即0。

我们也可以自定义默认值的生成方式。例如,要创建一个默认值为列表的 defaultdict

from collections import defaultdict


def list_creator():
    return []


list_dict = defaultdict(list_creator)
print(list_dict['new_key'])

这里通过定义 list_creator 函数来指定默认值为一个空列表。当访问不存在的键时,defaultdict 会调用这个函数来生成默认值。

defaultdict 在很多场景下都非常有用,比如统计单词出现的次数:

from collections import defaultdict

words = ['apple', 'banana', 'apple', 'cherry', 'banana']
word_count = defaultdict(int)
for word in words:
    word_count[word] += 1
print(word_count)

在这个例子中,如果使用普通字典,每次访问一个新单词时都需要先判断该单词是否已存在,然后再进行计数操作。而使用 defaultdict,可以简化代码,直接对单词进行计数,因为不存在的单词会自动被赋予默认值0。

四、OrderedDict的使用

OrderedDict 同样是 collections 模块中的一个类,它继承自 dict。与普通字典不同的是,OrderedDict 会记住字典中元素插入的顺序。

创建 OrderedDict

from collections import OrderedDict

ordered_dict = OrderedDict()
ordered_dict['a'] = 1
ordered_dict['b'] = 2
ordered_dict['c'] = 3

for key, value in ordered_dict.items():
    print(key, value)

上述代码中,我们创建了一个 OrderedDict 并依次插入了三个键值对。在遍历 OrderedDict 时,输出的顺序与插入顺序一致。

而普通字典在Python 3.6 之前是无序的,3.6 之后虽然在CPython实现中记住了插入顺序,但这只是一个实现细节,不应该依赖它。只有 OrderedDict 能保证严格按照插入顺序维护元素。

OrderedDict 可以用于很多场景,比如实现一个简单的缓存。假设我们有一个缓存类,当缓存满时,需要移除最早插入的元素:

from collections import OrderedDict


class Cache:
    def __init__(self, capacity):
        self.capacity = capacity
        self.cache = OrderedDict()

    def get(self, key):
        if key not in self.cache:
            return -1
        value = self.cache.pop(key)
        self.cache[key] = value
        return value

    def put(self, key, value):
        if key in self.cache:
            self.cache.pop(key)
        self.cache[key] = value
        if len(self.cache) > self.capacity:
            self.cache.popitem(last=False)


cache = Cache(2)
cache.put(1, 1)
cache.put(2, 2)
print(cache.get(1))
cache.put(3, 3)
print(cache.get(2))
cache.put(4, 4)
print(cache.get(1))
print(cache.get(3))
print(cache.get(4))

在这个缓存类中,OrderedDict 用于维护元素的插入顺序,使得我们可以方便地移除最早插入的元素,实现缓存的功能。

五、字典的视图对象

Python字典有三个视图对象:keys()values()items()。这些视图对象提供了字典键、值和键值对的动态视图,它们会随着字典的变化而自动更新。

  1. keys() 视图 keys() 方法返回一个包含字典所有键的视图对象。例如:
my_dict = {'a': 1, 'b': 2, 'c': 3}
keys_view = my_dict.keys()
print(keys_view)

这个视图对象是可迭代的,我们可以像遍历列表一样遍历它:

my_dict = {'a': 1, 'b': 2, 'c': 3}
keys_view = my_dict.keys()
for key in keys_view:
    print(key)

而且,当字典发生变化时,视图也会相应更新:

my_dict = {'a': 1, 'b': 2, 'c': 3}
keys_view = my_dict.keys()
print(keys_view)
my_dict['d'] = 4
print(keys_view)
  1. values() 视图 values() 方法返回一个包含字典所有值的视图对象。使用方式与 keys() 类似:
my_dict = {'a': 1, 'b': 2, 'c': 3}
values_view = my_dict.values()
print(values_view)
for value in values_view:
    print(value)
my_dict['d'] = 4
print(values_view)
  1. items() 视图 items() 方法返回一个包含字典所有键值对的视图对象,每个键值对以元组的形式呈现:
my_dict = {'a': 1, 'b': 2, 'c': 3}
items_view = my_dict.items()
print(items_view)
for item in items_view:
    print(item)
my_dict['d'] = 4
print(items_view)

这些视图对象在需要高效地处理字典的键、值或键值对,同时又要实时反映字典变化的场景中非常有用。例如,我们可以使用 items() 视图来方便地进行字典元素的遍历和修改:

my_dict = {'a': 1, 'b': 2, 'c': 3}
items_view = my_dict.items()
for key, value in items_view:
    my_dict[key] = value * 2
print(my_dict)

六、嵌套字典

嵌套字典是指字典中的值本身又是一个字典。这种数据结构在处理复杂的数据关系时非常有用。

例如,我们要记录不同班级学生的成绩,可以使用嵌套字典:

school_grades = {
    'Class1': {
        'Alice': 85,
        'Bob': 90
    },
    'Class2': {
        'Charlie': 78,
        'David': 88
    }
}

要访问 Class1Alice 的成绩,可以这样做:

print(school_grades['Class1']['Alice'])

我们也可以对嵌套字典进行添加、修改和删除操作。比如,要在 Class2 中添加一个学生 Eve 的成绩:

school_grades['Class2']['Eve'] = 92

删除 Class1Bob 的成绩:

del school_grades['Class1']['Bob']

在遍历嵌套字典时,我们可以使用多层循环。例如,要打印出所有学生的成绩:

for class_name, students in school_grades.items():
    print(f"Class: {class_name}")
    for student, grade in students.items():
        print(f"{student}: {grade}")

但是在使用嵌套字典时要注意,由于结构相对复杂,很容易出现键不存在的错误。所以在进行操作之前,最好先检查键是否存在。例如,在获取某个学生成绩之前,先检查班级和学生是否都存在:

class_name = 'Class1'
student_name = 'Frank'
if class_name in school_grades and student_name in school_grades[class_name]:
    print(school_grades[class_name][student_name])
else:
    print(f"{student_name} not found in {class_name}")

七、字典的比较

在Python中,字典的比较并不是简单地比较两个字典的内存地址。字典可以通过比较其键值对来判断是否相等。

  1. 相等比较 两个字典被认为相等,如果它们具有相同的键值对,键的顺序并不重要。例如:
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 2, 'a': 1}
print(dict1 == dict2)

上述代码会输出 True,因为虽然 dict1dict2 中键的顺序不同,但它们的键值对是完全一样的。

  1. 大小比较(Python 2.x 行为) 在Python 2.x 中,字典还可以进行大小比较。比较规则是首先比较字典的长度,长度大的字典更大;如果长度相同,则按字典序比较键值对。不过,在Python 3.x 中,字典不再支持大小比较操作。如果在Python 3.x 中尝试进行字典的大小比较,会抛出 TypeError

例如在Python 2.x 中的比较:

dict1 = {'a': 1}
dict2 = {'a': 1, 'b': 2}
print(dict1 < dict2)

这里 dict2 的长度大于 dict1,所以会输出 True。但再次强调,在Python 3.x 中这种操作是不被允许的。

八、字典与JSON的交互

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,在Web开发和数据存储中广泛使用。Python字典与JSON格式之间的转换非常方便,这得益于Python的 json 模块。

  1. 将字典转换为JSON字符串 使用 json.dumps() 方法可以将Python字典转换为JSON格式的字符串。例如:
import json

my_dict = {'name': 'Alice', 'age': 25}
json_str = json.dumps(my_dict)
print(json_str)

这里 json.dumps() 会将 my_dict 转换为一个JSON格式的字符串 {"name": "Alice", "age": 25}。默认情况下,json.dumps() 输出的字符串是紧凑格式的,如果想要更易读的输出,可以设置 indent 参数:

import json

my_dict = {'name': 'Alice', 'age': 25}
json_str = json.dumps(my_dict, indent=4)
print(json_str)

这样输出的JSON字符串会有缩进,更加美观易读。

  1. 将JSON字符串转换为字典 使用 json.loads() 方法可以将JSON格式的字符串转换回Python字典。例如:
import json

json_str = '{"name": "Alice", "age": 25}'
my_dict = json.loads(json_str)
print(my_dict)

这里 json.loads() 将JSON字符串转换为了Python字典,我们可以像操作普通字典一样对 my_dict 进行操作。

在处理文件时,也可以直接将字典写入JSON文件,或者从JSON文件读取数据并转换为字典。例如,将字典写入文件:

import json

my_dict = {'name': 'Alice', 'age': 25}
with open('data.json', 'w') as f:
    json.dump(my_dict, f)

这里 json.dump() 直接将字典写入了文件 data.json。读取文件并转换为字典:

import json

with open('data.json', 'r') as f:
    my_dict = json.load(f)
print(my_dict)

通过这种方式,Python字典与JSON之间的交互变得非常便捷,方便我们在不同的应用场景中进行数据的存储和传输。

九、字典的性能优化

在处理大规模数据时,字典的性能优化非常重要。以下是一些优化字典使用的方法:

  1. 减少键的查找次数 尽量避免在循环中频繁地通过键来访问字典。例如,假设我们有一个字典 my_dict,并且在循环中多次访问它的某个键:
my_dict = {'a': 1, 'b': 2, 'c': 3}
for _ in range(10000):
    value = my_dict['a']
    # 其他操作

这种方式在每次循环中都进行了键的查找操作。如果可以,我们可以在循环外部先获取这个值,然后在循环中使用:

my_dict = {'a': 1, 'b': 2, 'c': 3}
a_value = my_dict['a']
for _ in range(10000):
    value = a_value
    # 其他操作

这样可以减少键查找的开销,提高性能。

  1. 选择合适的键类型 字典的键必须是可哈希的(hashable)。在选择键的类型时,尽量使用简单的、不可变的数据类型,如字符串、整数等。因为这些类型的哈希计算相对较快。例如,使用字符串作为键通常比使用自定义类的实例作为键性能更好,除非自定义类实现了高效的 __hash__ 方法。

  2. 批量操作 如果需要对字典进行多次插入或删除操作,尽量进行批量操作,而不是逐个操作。例如,假设要向字典中插入多个键值对:

my_dict = {}
for i in range(1000):
    my_dict[i] = i * 2

可以改为批量插入:

data = {i: i * 2 for i in range(1000)}
my_dict = {}
my_dict.update(data)

这样通过 update 方法进行批量插入,比逐个插入要高效一些。

  1. 使用字典视图对象的高效操作 如前文提到的字典视图对象 keys()values()items(),在需要对字典的键、值或键值对进行操作时,优先使用这些视图对象。因为它们提供了动态视图,避免了创建额外的数据结构,并且在遍历等操作上有一定的性能优势。

通过以上这些方法,可以在一定程度上优化字典在实际应用中的性能,特别是在处理大规模数据和高频率操作的场景下。

十、字典在实际项目中的应用案例

  1. Web开发中的数据存储与传输 在Web开发框架如Django或Flask中,字典常用于存储和传输数据。例如,在处理HTTP请求和响应时,视图函数可以返回一个字典,框架会将其转换为JSON格式的数据返回给前端。假设我们有一个简单的Flask应用,获取用户信息并返回:
from flask import Flask, jsonify

app = Flask(__name__)


@app.route('/user')
def get_user():
    user = {'name': 'Alice', 'age': 25, 'email': 'alice@example.com'}
    return jsonify(user)


if __name__ == '__main__':
    app.run()

这里 jsonify 函数将字典 user 转换为JSON格式的响应数据返回给前端。

  1. 数据分析中的数据处理 在数据分析中,字典可以用于存储统计信息。例如,我们有一个包含多个学生成绩的列表,要统计每个分数段的学生人数:
scores = [85, 90, 78, 88, 92, 65, 70, 80]
score_range_count = {
    '60 - 69': 0,
    '70 - 79': 0,
    '80 - 89': 0,
    '90 - 100': 0
}
for score in scores:
    if 60 <= score < 70:
        score_range_count['60 - 69'] += 1
    elif 70 <= score < 80:
        score_range_count['70 - 79'] += 1
    elif 80 <= score < 90:
        score_range_count['80 - 89'] += 1
    else:
        score_range_count['90 - 100'] += 1
print(score_range_count)

通过字典,我们可以方便地对数据进行分类统计,为进一步的数据分析提供基础。

  1. 游戏开发中的角色属性管理 在游戏开发中,字典可以用于管理游戏角色的属性。例如,一个角色扮演游戏中,角色有生命值、攻击力、防御力等属性,可以用字典来表示:
character = {
    'name': 'Warrior',
    'health': 100,
    'attack': 20,
    'defense': 15
}

在游戏运行过程中,可以根据游戏逻辑对字典中的属性进行修改,比如角色受到攻击时减少生命值:

damage = 10
character['health'] -= damage
print(character['health'])

通过这种方式,字典为游戏开发中管理复杂的角色属性提供了一种简单有效的方式。

综上所述,字典在Python的实际项目中应用广泛,掌握其高级用法对于编写高效、灵活的代码至关重要。无论是数据处理、Web开发还是其他领域,字典都能发挥重要作用。