Python 元组和映射类型深入指南
📌 引言
在上一篇《Python 数据类型完全指南》中,我们概览了 Python 的各种数据类型。本文将深入探讨两个重要但常被忽视的类型:元组(Tuple) 和 映射类型(主要是 Dict)。这两种类型在 Python 编程中扮演着关键角色。
第一部分:元组(Tuple)深度解析
🎯 什么是元组?
元组是 Python 中的不可变序列,一旦创建就不能修改。你可以把它理解为"只读列表"或"常量数组"。
📝 创建元组的多种方式
python
# 1. 使用圆括号
coordinates = (3, 4)
rgb = (255, 128, 0)
# 2. 省略圆括号(隐式元组)
point = 10, 20
user_info = "张三", 25, "北京"
# 3. 单元素元组(注意逗号!)
single = (1,) # ✅ 这是元组
not_tuple = (1) # ❌ 这只是整数 1,括号被当作运算符
# 4. 空元组
empty = ()
empty2 = tuple()
# 5. 使用 tuple() 构造函数
from_list = tuple([1, 2, 3])
from_string = tuple("abc") # ('a', 'b', 'c')
from_range = tuple(range(5)) # (0, 1, 2, 3, 4)
🔍 访问和操作元组
python
fruits = ('apple', 'banana', 'cherry', 'date', 'elderberry')
# 索引访问
print(fruits[0]) # 'apple'
print(fruits[-1]) # 'elderberry'(负索引)
print(fruits[-2]) # 'date'
# 切片操作(返回新元组)
print(fruits[1:3]) # ('banana', 'cherry')
print(fruits[:2]) # ('apple', 'banana')
print(fruits[2:]) # ('cherry', 'date', 'elderberry')
print(fruits[::2]) # ('apple', 'cherry', 'elderberry')(步长为2)
print(fruits[::-1]) # 反转元组
# 元组拼接(创建新元组)
t1 = (1, 2)
t2 = (3, 4)
t3 = t1 + t2 # (1, 2, 3, 4)
# 元组重复
repeated = (1, 2) * 3 # (1, 2, 1, 2, 1, 2)
# 检查元素是否存在
print('apple' in fruits) # True
print('grape' not in fruits) # True
⚠️ 元组的不可变性
python
t = (1, 2, 3)
# ❌ 以下操作都会报错
# t[0] = 10 # TypeError: 'tuple' object does not support item assignment
# t.append(4) # AttributeError: 'tuple' object has no attribute 'append'
# del t[1] # TypeError: 'tuple' object doesn't support item deletion
# ✅ 但可以重新赋值变量
t = (4, 5, 6) # 这是创建了新元组,而不是修改原元组
# ⚠️ 特殊情况:元组包含可变对象
mixed = (1, 2, [3, 4])
# mixed[2] = [5, 6] # ❌ 不能替换列表
mixed[2][0] = 99 # ✅ 但可以修改列表内容
print(mixed) # (1, 2, [99, 4])
🛠️ 元组的方法(只有 2 个!)
由于元组不可变,它只有两个方法:
python
numbers = (1, 2, 3, 2, 4, 2, 5)
# 1. count() - 统计某个值出现的次数
print(numbers.count(2)) # 3
print(numbers.count(10)) # 0
# 2. index() - 查找某个值首次出现的索引
print(numbers.index(2)) # 1
print(numbers.index(2, 2)) # 从索引2开始查找:3
print(numbers.index(2, 4, 7)) # 在索引4-7范围内查找:5
# 找不到会报错
try:
numbers.index(10)
except ValueError as e:
print(f"错误:{e}") # 错误:tuple.index(x): x not in tuple
🎁 元组解包(Unpacking)
这是元组最强大的特性之一!
基础解包
python
# 基本解包
point = (3, 4)
x, y = point
print(f"x={x}, y={y}") # x=3, y=4
# 多值解包
user = ("张三", 25, "北京", "工程师")
name, age, city, job = user
# 交换变量(Python 的优雅之处!)
a, b = 10, 20
a, b = b, a # 交换
print(a, b) # 20 10
# 函数返回多个值
def get_min_max(numbers):
return min(numbers), max(numbers)
minimum, maximum = get_min_max([3, 1, 4, 1, 5])
print(f"最小值:{minimum},最大值:{maximum}")
扩展解包(*运算符)
python
# * 收集剩余元素
first, *rest = (1, 2, 3, 4, 5)
print(first) # 1
print(rest) # [2, 3, 4, 5](注意:rest 是列表!)
# * 在中间
first, *middle, last = (1, 2, 3, 4, 5)
print(first) # 1
print(middle) # [2, 3, 4]
print(last) # 5
# * 在开头
*head, second_last, last = (1, 2, 3, 4, 5)
print(head) # [1, 2, 3]
print(second_last) # 4
print(last) # 5
# 只取部分值,忽略其他
name, _, _, job = ("张三", 25, "北京", "工程师")
print(name, job) # 张三 工程师
# 嵌套解包
person = ("李四", (170, 65)) # 姓名和(身高, 体重)
name, (height, weight) = person
print(f"{name}: {height}cm, {weight}kg")
🎯 元组的实际应用场景
1. 函数返回多个值
python
def divide_with_remainder(dividend, divisor):
"""返回商和余数"""
quotient = dividend // divisor
remainder = dividend % divisor
return quotient, remainder
q, r = divide_with_remainder(17, 5)
print(f"17 ÷ 5 = {q} ... {r}") # 17 ÷ 5 = 3 ... 2
# 内置函数 divmod() 就是这样做的
q, r = divmod(17, 5)
2. 字典的键(列表不能作为键)
python
# ✅ 元组可以作为字典的键
locations = {
(40.7128, -74.0060): "纽约",
(51.5074, -0.1278): "伦敦",
(35.6762, 139.6503): "东京"
}
# ❌ 列表不能作为键
# locations[[40.7128, -74.0060]] = "纽约" # TypeError: unhashable type: 'list'
# 查询坐标
coords = (40.7128, -74.0060)
print(locations[coords]) # 纽约
3. 保护数据不被修改
python
# 配置常量
DATABASE_CONFIG = (
"localhost",
5432,
"mydb",
"username"
)
# RGB 颜色值
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
# 尺寸规格
SIZES = (
("S", 85, 160),
("M", 90, 165),
("L", 95, 170),
("XL", 100, 175)
)
4. 作为轻量级数据结构
python
# 比定义类更轻量
students = [
("张三", 18, 85),
("李四", 19, 92),
("王五", 18, 78)
]
for name, age, score in students:
print(f"{name}({age}岁): {score}分")
# 当然,如果数据复杂,还是应该用命名元组或类
from collections import namedtuple
Student = namedtuple('Student', ['name', 'age', 'score'])
s = Student("张三", 18, 85)
print(s.name, s.score) # 张三 85
⚡ 元组 vs 列表:性能对比
python
import sys
import timeit
# 内存占用
list_data = [1, 2, 3, 4, 5]
tuple_data = (1, 2, 3, 4, 5)
print(f"列表大小:{sys.getsizeof(list_data)} 字节")
print(f"元组大小:{sys.getsizeof(tuple_data)} 字节")
# 列表大小:104 字节
# 元组大小:80 字节
# 创建速度
list_time = timeit.timeit('[1, 2, 3, 4, 5]', number=1000000)
tuple_time = timeit.timeit('(1, 2, 3, 4, 5)', number=1000000)
print(f"创建列表耗时:{list_time:.4f} 秒")
print(f"创建元组耗时:{tuple_time:.4f} 秒")
# 元组通常更快
💡 元组最佳实践
python
# ✅ 使用元组表示固定结构的数据
def get_user_info():
return "张三", 25, "engineer"
# ✅ 使用元组作为字典键
cache = {
('user', 123): {'name': '张三', 'age': 25},
('user', 456): {'name': '李四', 'age': 30}
}
# ✅ 元组解包让代码更清晰
for name, age, job in [("张三", 25, "工程师"), ("李四", 30, "设计师")]:
print(f"{name} - {job}")
# ✅ 使用 _ 忽略不需要的值
name, _, job = ("张三", 25, "工程师")
# ❌ 避免过长的元组(超过5个元素考虑用命名元组或类)
# bad = ("张三", 25, "北京", "工程师", "男", "13800138000", "...")
# ✅ 对于复杂数据,使用命名元组
from collections import namedtuple
Person = namedtuple('Person', ['name', 'age', 'city', 'job'])
p = Person("张三", 25, "北京", "工程师")
print(p.name, p.job)
第二部分:映射类型深度解析
🗺️ 字典(Dict)详解
字典是 Python 中最强大的数据类型之一,用于存储键值对。
📝 创建字典的多种方式
python
# 1. 字面量语法
user = {
'name': '张三',
'age': 25,
'email': 'zhangsan@example.com'
}
# 2. dict() 构造函数
user2 = dict(name='李四', age=30, email='lisi@example.com')
# 3. 从键值对列表创建
pairs = [('name', '王五'), ('age', 28)]
user3 = dict(pairs)
# 4. 使用 fromkeys() 创建(所有键共享同一个值)
default_config = dict.fromkeys(['host', 'port', 'database'], None)
# {'host': None, 'port': None, 'database': None}
# 5. 字典推导式
squares = {x: x**2 for x in range(6)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# 6. 使用 zip() 组合两个序列
keys = ['name', 'age', 'city']
values = ['赵六', 35, '上海']
user4 = dict(zip(keys, values))
🔍 访问字典元素
python
user = {
'name': '张三',
'age': 25,
'city': '北京',
'hobbies': ['reading', 'coding']
}
# 方法1:使用 [] 索引(键不存在会报错)
print(user['name']) # '张三'
# print(user['phone']) # ❌ KeyError: 'phone'
# 方法2:使用 get()(推荐,更安全)
print(user.get('name')) # '张三'
print(user.get('phone')) # None
print(user.get('phone', '未设置')) # '未设置'(提供默认值)
# 检查键是否存在
if 'email' in user:
print(user['email'])
else:
print("邮箱未设置")
# 使用 setdefault()(获取值,不存在则设置默认值)
phone = user.setdefault('phone', '13800138000')
print(phone) # '13800138000'
print(user) # 字典中已添加 'phone' 键
✏️ 修改和更新字典
python
user = {'name': '张三', 'age': 25}
# 1. 直接赋值(修改或添加)
user['age'] = 26 # 修改
user['email'] = 'z@ex.com' # 添加
# 2. update() - 批量更新
user.update({'city': '北京', 'age': 27})
user.update(phone='13800138000', job='工程师')
# 3. 合并字典(Python 3.9+)
defaults = {'theme': 'dark', 'language': 'zh'}
custom = {'language': 'en', 'fontSize': 14}
# 方法1:使用 | 运算符
config = defaults | custom
# {'theme': 'dark', 'language': 'en', 'fontSize': 14}
# 方法2:使用 |= 就地更新
defaults |= custom
# 方法3:使用解包(适用于所有 Python 3.5+)
config = {**defaults, **custom}
# 4. 嵌套更新(合并嵌套字典)
def deep_merge(dict1, dict2):
"""深度合并字典"""
result = dict1.copy()
for key, value in dict2.items():
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
result[key] = deep_merge(result[key], value)
else:
result[key] = value
return result
base = {'a': 1, 'b': {'x': 10, 'y': 20}}
update = {'b': {'y': 30, 'z': 40}, 'c': 3}
merged = deep_merge(base, update)
# {'a': 1, 'b': {'x': 10, 'y': 30, 'z': 40}, 'c': 3}
🗑️ 删除字典元素
python
user = {
'name': '张三',
'age': 25,
'email': 'z@ex.com',
'phone': '13800138000'
}
# 1. del - 删除指定键
del user['phone']
# 2. pop() - 删除并返回值
email = user.pop('email')
print(email) # 'z@ex.com'
# pop() 提供默认值(避免 KeyError)
website = user.pop('website', None)
# 3. popitem() - 删除并返回最后一个键值对(Python 3.7+ 保证顺序)
item = user.popitem()
print(item) # ('age', 25)
# 4. clear() - 清空字典
user.clear()
print(user) # {}
🔄 遍历字典
python
user = {
'name': '张三',
'age': 25,
'city': '北京',
'job': '工程师'
}
# 1. 遍历键(默认行为)
for key in user:
print(key)
# 明确遍历键
for key in user.keys():
print(f"{key}: {user[key]}")
# 2. 遍历值
for value in user.values():
print(value)
# 3. 遍历键值对(推荐)
for key, value in user.items():
print(f"{key}: {value}")
# 4. 带索引遍历
for index, (key, value) in enumerate(user.items()):
print(f"{index}. {key} = {value}")
# 5. 反向遍历(Python 3.8+)
for key in reversed(user):
print(key)
# 6. 按键排序遍历
for key in sorted(user.keys()):
print(f"{key}: {user[key]}")
# 7. 按值排序遍历
for key, value in sorted(user.items(), key=lambda item: item[1]):
print(f"{key}: {value}")
🛠️ 字典的高级方法
1. keys(), values(), items()
python
user = {'name': '张三', 'age': 25, 'city': '北京'}
# 返回视图对象(动态反映字典变化)
keys = user.keys() # dict_keys(['name', 'age', 'city'])
values = user.values() # dict_values(['张三', 25, '北京'])
items = user.items() # dict_items([('name', '张三'), ('age', 25), ('city', '北京')])
# 视图是动态的
user['job'] = '工程师'
print(keys) # dict_keys(['name', 'age', 'city', 'job'])
# 转换为列表
keys_list = list(user.keys())
values_list = list(user.values())
2. copy() - 浅拷贝
python
original = {'a': 1, 'b': [2, 3]}
# 浅拷贝
shallow = original.copy()
shallow['a'] = 10
shallow['b'].append(4)
print(original) # {'a': 1, 'b': [2, 3, 4]} - 嵌套对象被影响
print(shallow) # {'a': 10, 'b': [2, 3, 4]}
# 深拷贝
import copy
deep = copy.deepcopy(original)
deep['b'].append(5)
print(original) # {'a': 1, 'b': [2, 3, 4]} - 不受影响
print(deep) # {'a': 1, 'b': [2, 3, 4, 5]}
3. setdefault() - 获取或设置默认值
python
# 统计字符出现次数
text = "hello world"
char_count = {}
for char in text:
char_count[char] = char_count.get(char, 0) + 1
# 或使用 setdefault
# char_count.setdefault(char, 0)
# char_count[char] += 1
print(char_count)
🎯 字典推导式
python
# 基础推导式
squares = {x: x**2 for x in range(6)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# 带条件过滤
even_squares = {x: x**2 for x in range(10) if x % 2 == 0}
# {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}
# 转换现有字典
prices = {'apple': 3, 'banana': 2, 'cherry': 5}
discounted = {item: price * 0.8 for item, price in prices.items()}
# {'apple': 2.4, 'banana': 1.6, 'cherry': 4.0}
# 键值互换
original = {'a': 1, 'b': 2, 'c': 3}
swapped = {value: key for key, value in original.items()}
# {1: 'a', 2: 'b', 3: 'c'}
# 从两个列表创建字典
keys = ['name', 'age', 'city']
values = ['张三', 25, '北京']
user = {k: v for k, v in zip(keys, values)}
🎯 字典的实际应用场景
1. 数据聚合和分组
python
students = [
{'name': '张三', 'class': '1班', 'score': 85},
{'name': '李四', 'class': '2班', 'score': 92},
{'name': '王五', 'class': '1班', 'score': 78},
{'name': '赵六', 'class': '2班', 'score': 88}
]
# 按班级分组
from collections import defaultdict
grouped = defaultdict(list)
for student in students:
grouped[student['class']].append(student)
for class_name, students_list in grouped.items():
print(f"{class_name}: {len(students_list)}人")
2. 缓存和记忆化
python
# 使用字典缓存计算结果
def fibonacci(n, cache={}):
if n in cache:
return cache[n]
if n <= 1:
return n
result = fibonacci(n-1, cache) + fibonacci(n-2, cache)
cache[n] = result
return result
print(fibonacci(100)) # 很快!
3. 配置管理
python
# 多层配置合并
default_config = {
'database': {
'host': 'localhost',
'port': 5432,
'name': 'mydb'
},
'cache': {
'enabled': True,
'ttl': 3600
}
}
user_config = {
'database': {
'host': '192.168.1.100'
},
'cache': {
'ttl': 7200
}
}
# 合并配置(保留嵌套结构)
final_config = {**default_config, **user_config}
4. 计数器(使用 Counter)
python
from collections import Counter
# 统计词频
text = "apple banana apple cherry banana apple"
word_count = Counter(text.split())
print(word_count) # Counter({'apple': 3, 'banana': 2, 'cherry': 1})
# 最常见的元素
print(word_count.most_common(2)) # [('apple', 3), ('banana', 2)]
# Counter 运算
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)
print(c1 + c2) # Counter({'a': 4, 'b': 3})
print(c1 - c2) # Counter({'a': 2})
🔒 其他映射类型
1. defaultdict - 带默认值的字典
python
from collections import defaultdict
# 普通字典需要检查键是否存在
normal_dict = {}
for item in ['a', 'b', 'a', 'c', 'b', 'a']:
if item not in normal_dict:
normal_dict[item] = 0
normal_dict[item] += 1
# defaultdict 自动处理
dd = defaultdict(int) # 默认值为 0
for item in ['a', 'b', 'a', 'c', 'b', 'a']:
dd[item] += 1
print(dd) # defaultdict(<class 'int'>, {'a': 3, 'b': 2, 'c': 1})
# 用于分组
grouped = defaultdict(list)
for key, value in [('fruit', 'apple'), ('veg', 'carrot'), ('fruit', 'banana')]:
grouped[key].append(value)
print(grouped) # defaultdict(<class 'list'>, {'fruit': ['apple', 'banana'], 'veg': ['carrot']})
2. OrderedDict - 记住插入顺序的字典
python
from collections import OrderedDict
# 注意:Python 3.7+ 普通 dict 也保证顺序,OrderedDict 现在主要用于明确表达意图
od = OrderedDict()
od['first'] = 1
od['second'] = 2
od['third'] = 3
# 移动到末尾
od.move_to_end('first')
print(od) # OrderedDict([('second', 2), ('third', 3), ('first', 1)])
# 移动到开头
od.move_to_end('third', last=False)
print(od) # OrderedDict([('third', 3), ('second', 2), ('first', 1)])
3. ChainMap - 链式字典
python
from collections import ChainMap
# 合并多个字典,按顺序查找
defaults = {'color': 'red', 'user': 'guest'}
environment = {'user': 'admin'}
cli_args = {'color': 'blue'}
combined = ChainMap(cli_args, environment, defaults)
print(combined['color']) # 'blue'(从第一个字典找到)
print(combined['user']) # 'admin'(从第二个字典找到)
# 查看所有字典
print(combined.maps) # [{'color': 'blue'}, {'user': 'admin'}, {'color': 'red', 'user': 'guest'}]
💡 字典最佳实践
python
# ✅ 使用 get() 而不是 []
value = user.get('key', default_value)
# ✅ 使用 items() 遍历键值对
for key, value in user.items():
print(key, value)
# ✅ 使用字典推导式
squared = {x: x**2 for x in range(10)}
# ✅ 使用 defaultdict 简化逻辑
from collections import defaultdict
dd = defaultdict(list)
# ✅ 使用 ** 解包合并字典
merged = {**dict1, **dict2}
# ❌ 避免在遍历时修改字典大小
# for key in user:
# del user[key] # 会报错
# ✅ 创建副本来修改
for key in list(user.keys()):
if condition:
del user[key]
# ✅ 使用类型提示(Python 3.9+)
from typing import Dict, List
user_scores: Dict[str, int] = {'Alice': 95, 'Bob': 87}
📊 总结对比
元组 vs 列表
特性 | 元组 | 列表 |
---|---|---|
可变性 | 不可变 | 可变 |
语法 | () |
[] |
性能 | 更快,占用内存更少 | 稍慢 |
方法 | 只有 count() 和 index() |
丰富的修改方法 |
用途 | 固定数据、函数返回值、字典键 | 动态数据集合 |
字典的关键特性
- ✅ 键必须是不可变类型(如字符串、数字、元组)
- ✅ Python 3.7+ 保证插入顺序
- ✅ 平均 O(1) 查找时间
- ✅ 支持推导式语法
- ✅ 可以嵌套任意深度
🚀 实战练习
练习 1:统计文本中单词出现次数
python
def word_frequency(text):
"""统计单词频率并返回排序后的结果"""
from collections import Counter
words = text.lower().split()
counter = Counter(words)
return counter.most_common()
text = "apple banana apple cherry banana apple orange"
print(word_frequency(text))
练习 2:实现 LRU 缓存
python
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity):
self.cache = OrderedDict()
self.capacity = capacity
def get(self, key):
if key not in self.cache:
return -1
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:
self.cache.popitem(last=False)
# 使用示例
cache = LRUCache(2)
cache.put(1, "a")
cache.put(2, "b")
print(cache.get(1)) # "a"
cache.put(3, "c") # 淘汰键 2
print(cache.get(2)) # -1
📚 延伸阅读
本文档最后更新:2025 年 10 月 23 日