本文涵盖两个 Python 高频基础知识点:
zip()内置函数的各类实用技巧,以及@property装饰器如何将方法"属性化"。从基础语法到实战封装,一篇搞懂。
一、zip() 基础用法
1.1 什么是 zip
zip() 是 Python 内置函数,将多个可迭代对象"拉链式"打包,逐元素配对成元组。
names = ["Alice", "Bob", "Charlie"]
scores = [90, 85, 78]
result = zip(names, scores)
print(list(result))
# [('Alice', 90), ('Bob', 85), ('Charlie', 78)]
可以想象成拉链——两边对齐咬合:
names: Alice Bob Charlie
↕ ↕ ↕
scores: 90 85 78
───────────────────────
zip: (Alice,90) (Bob,85) (Charlie,78)
1.2 长度不一致时
zip() 以最短的可迭代对象为准,多余元素自动丢弃:
a = [1, 2, 3, 4, 5]
b = ["a", "b", "c"]
print(list(zip(a, b)))
# [(1, 'a'), (2, 'b'), (3, 'c')] ← 4 和 5 被丢弃
如果想保留所有元素,用 itertools.zip_longest:
from itertools import zip_longest
a = [1, 2, 3, 4, 5]
b = ["a", "b", "c"]
print(list(zip_longest(a, b, fillvalue="???")))
# [(1, 'a'), (2, 'b'), (3, 'c'), (4, '???'), (5, '???')]
1.3 zip 三个或更多序列
names = ["Alice", "Bob", "Charlie"]
scores = [90, 85, 78]
ages = [20, 21, 22]
for name, score, age in zip(names, scores, ages):
print(f"{name}: 分数={score}, 年龄={age}")
# Alice: 分数=90, 年龄=20
# Bob: 分数=85, 年龄=21
# Charlie: 分数=78, 年龄=22
二、zip() 的实战技巧
2.1 同时遍历多个列表
最常见的用途——替代索引访问:
# ❌ 不推荐:用索引访问
names = ["Alice", "Bob", "Charlie"]
scores = [90, 85, 78]
for i in range(len(names)):
print(f"{names[i]}: {scores[i]}")
# ✅ 推荐:用 zip
for name, score in zip(names, scores):
print(f"{name}: {score}")
2.2 构建字典
keys = ["name", "age", "city"]
values = ["Alice", 20, "Beijing"]
d = dict(zip(keys, values))
print(d)
# {'name': 'Alice', 'age': 20, 'city': 'Beijing'}
2.3 矩阵转置
经典面试题——二维矩阵行列互换:
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
transposed = list(zip(*matrix))
print(transposed)
# [(1, 4, 7), (2, 5, 8), (3, 6, 9)]
原理:*matrix 将矩阵解包成三个独立的行,zip 再将它们按列重新组合:
解包前: [1,2,3] [4,5,6] [7,8,9]
↓ ↓ ↓
zip后: (1,4,7) (2,5,8) (3,6,9)
如果需要结果是 list of list:
transposed = [list(row) for row in zip(*matrix)]
print(transposed)
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
2.4 解压(unzip)
zip 的逆操作——把配对的元组拆回两个列表:
pairs = [("Alice", 90), ("Bob", 85), ("Charlie", 78)]
names, scores = zip(*pairs)
print(names) # ('Alice', 'Bob', 'Charlie')
print(scores) # (90, 85, 78)
原理:*pairs 解包成三个元组,zip 重新组合成两组。
2.5 并行排序
按一个列表的顺序对另一个列表排序:
names = ["Alice", "Bob", "Charlie"]
scores = [90, 85, 78]
# 按分数降序排列,名字跟着走
sorted_pairs = sorted(zip(scores, names), reverse=True)
sorted_names = [name for _, name in sorted_pairs]
print(sorted_names) # ['Alice', 'Bob', 'Charlie']
2.6 交替合并字符串
a = "abc"
b = "123"
merged = "".join(x + y for x, y in zip(a, b))
print(merged) # a1b2c3
2.7 批量分组
将一个列表按固定大小分组:
def chunk(lst, size):
"""将列表按 size 个一组切分"""
return list(zip(*[iter(lst)] * size))
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(chunk(data, 3))
# [(1, 2, 3), (4, 5, 6), (7, 8, 9)] ← 10 被丢弃
# 不丢弃余数版本
from itertools import zip_longest
def chunk_safe(lst, size, fill=None):
args = [iter(lst)] * size
return [tuple(x for x in t if x is not fill) for t in zip_longest(*args, fillvalue=fill)]
print(chunk_safe(data, 3))
# [(1, 2, 3), (4, 5, 6), (7, 8, 9), (10,)]
三、zip() 速查表
| 用法 | 代码 | 结果 |
|---|---|---|
| 基本打包 | list(zip([1,2], ['a','b'])) | [(1,'a'), (2,'b')] |
| 构建字典 | dict(zip(['a','b'], [1,2])) | {'a':1, 'b':2} |
| 矩阵转置 | list(zip(*[[1,2],[3,4]])) | [(1,3), (2,4)] |
| 解压 | zip(*[(1,'a'), (2,'b')]) | (1,2), ('a','b') |
| 长度对齐 | zip_longest([1,2,3], ['a']) | [(1,'a'), (2,None), (3,None)] |
| 并行遍历 | for a,b in zip(L1, L2) | 同时取两个列表的元素 |
四、@property:方法属性化
4.1 什么是方法属性化
@property 装饰器可以将一个方法变成属性来访问,调用时不需要加括号 ()。
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
"""面积:用属性方式访问,不需要 area()"""
import math
return math.pi * self.radius ** 2
c = Circle(5)
# 像属性一样访问,不加括号
print(c.area) # 78.53981633974483
对比没有 @property 的写法:
# 没有 @property
print(c.area()) # 需要加括号,像个方法调用
# 有 @property
print(c.area) # 不加括号,像个属性
4.2 为什么用 @property
| 场景 | 不用 @property | 用 @property |
|---|---|---|
| 访问方式 | obj.method() | obj.method |
| 语义 | 这是一个动作 | 这是一个属性 |
| 修改内部实现 | 改了属性要改所有调用处 | 内部计算逻辑变了,调用处不用改 |
| 参数 | 可以带参数 | 不带参数(纯计算/取值) |
4.3 完整的三件套:getter / setter / deleter
class Temperature:
"""温度类,内部用摄氏度存储,对外暴露华氏度"""
def __init__(self, fahrenheit=32):
self._celsius = (fahrenheit - 32) * 5 / 9
@property
def celsius(self):
"""获取摄氏度"""
return self._celsius
@celsius.setter
def celsius(self, value):
"""设置摄氏度,自动校验范围"""
if value < -273.15:
raise ValueError("温度不能低于绝对零度 (-273.15°C)")
self._celsius = value
@celsius.deleter
def celsius(self):
"""删除温度(重置为默认值)"""
print("温度已重置")
self._celsius = 0
# 使用
t = Temperature(fahrenheit=100)
# getter —— 像属性一样读
print(f"当前温度: {t.celsius:.1f}°C") # 当前温度: 37.8°C
# setter —— 像属性一样写
t.celsius = 50
print(f"修改后: {t.celsius:.1f}°C") # 修改后: 50.0°C
# setter 带校验
try:
t.celsius = -300 # 低于绝对零度
except ValueError as e:
print(f"设置失败: {e}") # 设置失败: 温度不能低于绝对零度
# deleter
del t.celsius # 温度已重置
print(f"删除后: {t.celsius:.1f}°C") # 删除后: 0.0°C
4.4 实战:只读计算属性
最常见的用法——把计算逻辑封装成只读属性:
class Student:
def __init__(self, name, scores):
self.name = name
self.scores = scores # list[float]
@property
def average(self):
"""平均分(只读计算属性)"""
return sum(self.scores) / len(self.scores) if self.scores else 0
@property
def highest(self):
"""最高分(只读)"""
return max(self.scores) if self.scores else 0
@property
def grade(self):
"""等级(只读)"""
avg = self.average
if avg >= 90:
return "A"
elif avg >= 80:
return "B"
elif avg >= 70:
return "C"
elif avg >= 60:
return "D"
return "F"
s = Student("Alice", [88, 92, 79, 95, 86])
# 像属性一样访问计算结果
print(f"学生: {s.name}")
print(f"平均分: {s.average:.1f}") # 88.0
print(f"最高分: {s.highest}") # 95
print(f"等级: {s.grade}") # B
# 只读属性不能赋值
try:
s.average = 90 # TypeError: can't set attribute
except AttributeError as e:
print(f"无法设置: {e}")
4.5 实战:带校验的属性
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self._balance = balance
@property
def balance(self):
"""余额(对外只读)"""
return self._balance
def deposit(self, amount):
"""存款"""
if amount <= 0:
raise ValueError("存款金额必须大于 0")
self._balance += amount
print(f" 存入 {amount},余额 {self._balance}")
def withdraw(self, amount):
"""取款"""
if amount <= 0:
raise ValueError("取款金额必须大于 0")
if amount > self._balance:
raise ValueError(f"余额不足!当前余额 {self._balance}")
self._balance -= amount
print(f" 取出 {amount},余额 {self._balance}")
acc = BankAccount("Alice", 1000)
# 余额是只读属性,不能直接赋值
print(f"当前余额: {acc.balance}") # 1000
acc.deposit(500) # 存入 500,余额 1500
acc.withdraw(200) # 取出 200,余额 1300
# 直接修改余额 → 被拦截
try:
acc.balance = 99999 # AttributeError: can't set attribute
except AttributeError as e:
print(f"无法直接修改余额: {e}")
4.6 property 的等价写法
@property 本质上是 property() 类的语法糖:
# 语法糖写法(推荐)
class MyClass1:
def __init__(self):
self._x = 0
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
# 等价的 property() 写法(了解即可)
class MyClass2:
def __init__(self):
self._x = 0
def _get_x(self):
return self._x
def _set_x(self, value):
self._x = value
x = property(_get_x, _set_x)
# 两种写法效果完全一样
a = MyClass1()
a.x = 10
print(a.x) # 10
b = MyClass2()
b.x = 20
print(b.x) # 20
property 四个参数:
property(fget=None, fset=None, fdel=None, doc=None)
| 参数 | 对应装饰器 | 作用 |
|---|---|---|
fget | @property | 读取时调用 |
fset | @xxx.setter | 赋值时调用 |
fdel | @xxx.deleter | del 时调用 |
doc | 文档字符串 | help() 中显示 |
五、zip + @property 综合实战
把两个知识点结合起来——封装一个数据统计类:
class DataAnalyzer:
"""数据分析工具:用 zip 做并行统计,用 @property 暴露结果"""
def __init__(self, labels: list[str], values: list[float]):
if len(labels) != len(values):
raise ValueError("labels 和 values 长度必须一致")
self._data = list(zip(labels, values))
@property
def count(self):
"""数据条数"""
return len(self._data)
@property
def total(self):
"""总和"""
return sum(v for _, v in self._data)
@property
def average(self):
"""平均值"""
return self.total / self.count if self.count else 0
@property
def max_item(self):
"""最大值项 (label, value)"""
if not self._data:
return None
# zip(*self._data) 解压成 labels 和 values 两组
labels, values = zip(*self._data)
max_idx = values.index(max(values))
return (labels[max_idx], values[max_idx])
@property
def ranking(self):
"""按值降序排列"""
return sorted(self._data, key=lambda x: x[1], reverse=True)
def summary(self):
"""打印汇总报告"""
print(f"数据条数: {self.count}")
print(f"总和: {self.total:.2f}")
print(f"平均值: {self.average:.2f}")
print(f"最大值: {self.max_item[0]} = {self.max_item[1]}")
print("排名:")
for i, (label, value) in enumerate(self.ranking, 1):
print(f" {i}. {label}: {value}")
# 使用
analyzer = DataAnalyzer(
labels=["Python", "Java", "Go", "Rust", "JavaScript"],
values=[85.5, 72.3, 68.0, 91.2, 78.5],
)
analyzer.summary()
输出:
数据条数: 5
总和: 395.5
平均值: 79.1
最大值: Rust = 91.2
排名:
1. Rust: 91.2
2. Python: 85.5
3. JavaScript: 78.5
4. Java: 72.3
5. Go: 68.0
六、小结
zip() 要点
| 技巧 | 代码模式 |
|---|---|
| 并行遍历 | for a, b in zip(L1, L2) |
| 构建字典 | dict(zip(keys, values)) |
| 矩阵转置 | zip(*matrix) |
| 解压 | zip(*pairs) |
| 批量分组 | zip(*[iter(lst)] * n) |
@property 要点
| 要点 | 说明 |
|---|---|
| 基本用途 | 方法变属性,访问不加 () |
| 只读属性 | 只写 @property,不写 @xxx.setter |
| 带校验赋值 | 在 @xxx.setter 中做校验 |
| 三件套 | @property + @xxx.setter + @xxx.deleter |
| 本质 | property(fget, fset, fdel) 的语法糖 |
如果本文对你有帮助,欢迎点赞收藏!

1万+

被折叠的 条评论
为什么被折叠?



