Python基础:字典与集合详解

153 阅读9分钟

Python字典与集合详解

在Python的数据类型家族中,字典(dict)和集合(set)是两种强大而高效的非序列集合类型。它们不仅在日常编程中使用频繁,而且在处理大量数据时能提供近乎常数时间的查找性能。本文将深入剖析这两种数据结构的特性、用法和最佳实践,帮助你更加得心应手地使用它们。

目录

字典:键值对的世界

字典是Python中最灵活的数据结构之一,本质上是键值对的无序集合。每个键必须是唯一的且必须是不可变类型(如字符串、数字或元组),而值则可以是任意Python对象。

创建与初始化

# 创建空字典的两种方式
empty_dict1 = {}
empty_dict2 = dict()

# 使用字面量语法创建
person = {"name": "Alice", "age": 30, "city": "New York"}

# 使用dict()构造函数
person2 = dict(name="Bob", age=25, city="Boston")

# 使用键值对列表创建
items = [("apple", 0.5), ("banana", 0.3), ("orange", 0.7)]
prices = dict(items)

# 使用字典推导式
squares = {x: x**2 for x in range(6)}
print(squares)  # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

访问与修改

person = {"name": "Alice", "age": 30, "city": "New York"}

# 访问值
print(person["name"])  # Alice

# 使用get()方法安全访问(不存在键时返回默认值)
print(person.get("email"))  # None
print(person.get("email", "Not Available"))  # Not Available

# 修改值
person["age"] = 31
print(person)  # {'name': 'Alice', 'age': 31, 'city': 'New York'}

# 添加新键值对
person["email"] = "alice@example.com"

# 删除键值对的几种方式
del person["city"]  # 删除指定键
age = person.pop("age")  # 删除并返回值
last_item = person.popitem()  # 删除并返回最后添加的键值对(Python 3.7+保证顺序)
print(age)  # 31
print(last_item)  # ('email', 'alice@example.com')

字典的基本操作

student = {"name": "John", "grades": [85, 90, 78]}

# 获取所有键
print(list(student.keys()))  # ['name', 'grades']

# 获取所有值
print(list(student.values()))  # ['John', [85, 90, 78]]

# 获取所有键值对
print(list(student.items()))  # [('name', 'John'), ('grades', [85, 90, 78])]

# 合并字典
extra_info = {"age": 20, "major": "Computer Science"}
student.update(extra_info)
print(student)  # {'name': 'John', 'grades': [85, 90, 78], 'age': 20, 'major': 'Computer Science'}

# 检查键是否存在
print("name" in student)  # True
print("address" in student)  # False

# 清空字典
student.clear()
print(student)  # {}

嵌套字典

字典可以包含任何Python对象,包括其他字典:

# 用字典表示复杂结构
university = {
    "name": "Tech University",
    "departments": {
        "CS": {
            "professors": 15,
            "students": 250
        },
        "Physics": {
            "professors": 8,
            "students": 120
        }
    },
    "founded": 1985
}

# 访问嵌套值
cs_students = university["departments"]["CS"]["students"]
print(cs_students)  # 250

# 修改嵌套值
university["departments"]["CS"]["students"] += 10

集合:唯一性的保证

集合是唯一元素的无序集合,主要用于成员测试、消除重复项和执行数学集合运算。集合中的元素必须是不可变类型。

创建与初始化

# 创建空集合(注意:{}创建的是空字典)
empty_set = set()

# 从列表创建集合(会去除重复项)
numbers = set([1, 2, 2, 3, 4, 4, 5])
print(numbers)  # {1, 2, 3, 4, 5}

# 使用花括号语法
fruits = {"apple", "banana", "cherry", "apple"}
print(fruits)  # {'banana', 'cherry', 'apple'}

# 集合推导式
evens = {x for x in range(10) if x % 2 == 0}
print(evens)  # {0, 2, 4, 6, 8}

基本操作

fruits = {"apple", "banana", "cherry"}

# 添加元素
fruits.add("orange")

# 移除元素
fruits.remove("banana")  # 如果元素不存在会引发KeyError
fruits.discard("kiwi")   # 如果元素不存在不会引发错误

# 随机移除一个元素并返回
popped = fruits.pop()
print(popped)  # 可能是集合中的任何元素

# 清空集合
fruits.clear()

集合运算

集合支持数学集合运算,这是其最强大的特性之一:

A = {1, 2, 3, 4, 5}
B = {4, 5, 6, 7, 8}

# 并集(所有元素)
union = A | B  # 或 A.union(B)
print(union)  # {1, 2, 3, 4, 5, 6, 7, 8}

# 交集(共有元素)
intersection = A & B  # 或 A.intersection(B)
print(intersection)  # {4, 5}

# 差集(A中存在但B中不存在的元素)
difference = A - B  # 或 A.difference(B)
print(difference)  # {1, 2, 3}

# 对称差集(A或B中存在但不同时存在于两者中的元素)
sym_diff = A ^ B  # 或 A.symmetric_difference(B)
print(sym_diff)  # {1, 2, 3, 6, 7, 8}

# 子集与超集关系测试
C = {1, 2}
print(C.issubset(A))  # True
print(A.issuperset(C))  # True

不可变集合:frozenset

普通集合是可变的,但有时我们需要不可变版本,例如作为字典的键:

# 创建不可变集合
fs = frozenset([1, 2, 3, 4])

# 作为字典键使用
groups = {
    frozenset(["Alice", "Bob"]): "Group 1",
    frozenset(["Charlie", "Dave"]): "Group 2"
}

# 查找
print(groups[frozenset(["Alice", "Bob"])])  # Group 1

# 不能修改frozenset
# fs.add(5)  # AttributeError

底层原理与性能

哈希表原理

字典和集合都基于哈希表实现,这就是为什么它们能提供O(1)的平均查找时间:

import time

# 比较列表和集合查找性能
def find_in_list(n):
    big_list = list(range(1000000))
    start = time.time()
    for _ in range(n):
        999999 in big_list  # O(n)复杂度
    return time.time() - start

def find_in_set(n):
    big_set = set(range(1000000))
    start = time.time()
    for _ in range(n):
        999999 in big_set  # O(1)复杂度
    return time.time() - start

n = 100
list_time = find_in_list(n)
set_time = find_in_set(n)
print(f"列表查找时间: {list_time:.5f}秒")
print(f"集合查找时间: {set_time:.5f}秒")
print(f"性能比: {list_time/set_time:.0f}x")

运行这段代码,你会发现集合的查找速度比列表快数千倍!

字典的有序性(Python 3.7+)

从Python 3.7开始,字典保持键插入顺序:

# Python 3.7+中字典保持插入顺序
colors = {}
colors["red"] = "#FF0000"
colors["green"] = "#00FF00"
colors["blue"] = "#0000FF"

for color, code in colors.items():
    print(f"{color}: {code}")
# 输出顺序将是: red, green, blue

Python 3.6中这也是事实,但只是实现细节,不是语言保证。

高级用法与技巧

字典默认值处理

# 常见模式:计数
text = "hello world hello python world python"
word_count = {}

# 不优雅的方式
for word in text.split():
    if word not in word_count:
        word_count[word] = 0
    word_count[word] += 1

# 使用get()更优雅
word_count = {}
for word in text.split():
    word_count[word] = word_count.get(word, 0) + 1

# 使用defaultdict最优雅
from collections import defaultdict
word_count = defaultdict(int)  # 默认值为0
for word in text.split():
    word_count[word] += 1

print(dict(word_count))  # {'hello': 2, 'world': 2, 'python': 2}

字典与集合推导式

字典和集合推导式提供了创建复杂结构的简洁方法:

# 字典推导式
prices = {"apple": 0.5, "banana": 0.3, "orange": 0.7, "grape": 1.2}
cheap_prices = {k: v for k, v in prices.items() if v < 0.6}
print(cheap_prices)  # {'apple': 0.5, 'banana': 0.3}

# 嵌套字典推导式
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = {f"({i},{j})": val
             for i, row in enumerate(matrix)
             for j, val in enumerate(row)}
print(flattened)  # {'(0,0)': 1, '(0,1)': 2, ...}

# 集合推导式
words = ["hello", "world", "python", "programming"]
long_words = {word for word in words if len(word) > 5}
print(long_words)  # {'python', 'programming'}

嵌套结构与递归函数

处理深度嵌套的字典或集合时,递归函数特别有用:

def find_keys(d, target, path=None):
    """在嵌套字典中查找特定键,并返回路径列表"""
    if path is None:
        path = []

    results = []

    for k, v in d.items():
        if k == target:
            results.append(path + [k])
        if isinstance(v, dict):
            # 递归搜索
            nested_results = find_keys(v, target, path + [k])
            results.extend(nested_results)

    return results

# 使用示例
data = {
    "users": {
        "alice": {"email": "alice@example.com"},
        "settings": {
            "email": "settings@system.com",
            "admin": {"email": "admin@system.com"}
        }
    }
}

paths = find_keys(data, "email")
print(paths)  # [['users', 'alice', 'email'], ['users', 'settings', 'email'], ['users', 'settings', 'admin', 'email']]

常见陷阱与解决方案

可变对象作为字典键

只有不可变对象(如字符串、数字、元组)可以用作字典键或集合元素:

# 这会失败
# bad_dict = {[1, 2]: "value"}  # TypeError: unhashable type: 'list'

# 但这可以工作
good_dict = {(1, 2): "value"}

# 如果需要使用复杂对象作为键,可以通过实现__hash__和__eq__方法
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __hash__(self):
        return hash((self.x, self.y))

    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)

points = {Point(0, 0): "Origin", Point(1, 1): "One-One"}

字典视图对象与迭代

字典的.keys(), .values().items()方法返回视图对象,它们会动态反映字典的变更:

scores = {"Alice": 85, "Bob": 90, "Charlie": 78}
students = scores.keys()
print(students)  # dict_keys(['Alice', 'Bob', 'Charlie'])

# 视图会跟踪字典变化
scores["Dave"] = 92
print(students)  # dict_keys(['Alice', 'Bob', 'Charlie', 'Dave'])

# 视图支持集合操作
alice_bob = {"Alice", "Bob"}
print(set(students) & alice_bob)  # {'Bob', 'Alice'}

# 避免在迭代时修改字典
for name in list(scores):  # 转换为列表创建快照
    if name.startswith("A"):
        del scores[name]

集合元素的限制

集合只能包含可哈希(不可变)元素。常见的可哈希类型有:

  • 整数、浮点数、复数
  • 字符串
  • 元组(如果元组所有元素都是可哈希的)
  • frozenset
# 这会失败
# bad_set = {[1, 2], (3, 4)}  # TypeError

# 这可以工作
good_set = {(1, 2), frozenset([3, 4])}

实际应用场景

字典的实际应用

  1. 数据转换和映射
country_codes = {
    "USA": "1",
    "UK": "44",
    "China": "86",
    "India": "91"
}
  1. 缓存/记忆化
# 斐波那契数列带缓存
def fibonacci(n, cache={}):
    if n in cache:
        return cache[n]
    if n <= 1:
        return n
    result = fibonacci(n-1) + fibonacci(n-2)
    cache[n] = result
    return result
  1. 配置和设置存储
app_settings = {
    "theme": "dark",
    "notifications": True,
    "language": "en",
    "max_items": 50
}

集合的实际应用

  1. 去重
log_entries = ["ERROR: File not found",
               "INFO: Task started",
               "ERROR: File not found",
               "INFO: Task completed"]
unique_entries = set(log_entries)
  1. 成员资格测试
valid_users = {"alice", "bob", "charlie", "dave"}

def authenticate(username):
    return username.lower() in valid_users
  1. 数据比较
def find_differences(old_data, new_data):
    old_set = set(old_data)
    new_set = set(new_data)

    added = new_set - old_set
    removed = old_set - new_set
    unchanged = old_set & new_set

    return {
        "added": added,
        "removed": removed,
        "unchanged": unchanged
    }

总结

Python的字典和集合是两种功能强大的数据结构,它们共享类似的底层实现(哈希表),但服务于不同的目的:

  • 字典 擅长键值对的存储和查找,非常适合表示映射关系、对象属性或任何需要通过键快速访问值的场景。

  • 集合 擅长唯一元素的管理和集合运算,非常适合去重、成员测试和处理集合运算(如并集、交集等)的场景。

它们的主要优势在于查找速度快(近乎O(1)),使它们成为处理大量数据时的理想选择。

最佳实践要点:

  1. 使用字典存储映射关系和需要通过键快速查找的数据
  2. 使用集合去除重复项和执行快速成员资格测试
  3. 利用字典和集合推导式创建复杂结构
  4. 选择合适的工具处理默认值(get方法、setdefault方法或defaultdict)
  5. 记住它们对键/元素的限制(必须是可哈希/不可变的)
  6. 利用视图对象及其集合特性处理字典键
  7. 在Python 3.7+中,利用字典的有序性简化代码

通过深入理解和有效利用字典和集合,你可以编写出更高效、更简洁的Python代码,处理各种复杂的数据结构和算法问题。

无论是在数据处理、网络编程、Web开发还是机器学习等领域,掌握这两种数据结构都将使你的Python编程更上一层楼。