Python 语法速通 02
梳理 Python 解包、生成器、装饰器、上下文管理、面向对象与类型标注,记录常见陷阱及与 Java 的差异。
Python高频特性
1. 解包
把列表、元组或字典里的多个值,一次拆出来分别使用。
1.1 序列解包
左边有几个变量,右边就把对应位置的值分别赋给它们。
user = ("Alice", 18)
name, age = user
print(name, age) # Alice 18
first, *middle, last = [1, 2, 3, 4, 5]
print(first, middle, last) # 1 [2, 3, 4] 5
a, b = 1, 2
a, b = b, a
print(a, b) # 2 1
1.2 函数调用解包
调用函数时,* 把列表或元组拆成多个普通参数;** 把字典拆成多个“参数名=值”。
def create_user(name, age, city):
return {"name": name, "age": age, "city": city}
args = ("Alice", 18, "Shanghai")
user = create_user(*args)
print(user) # {'name': 'Alice', 'age': 18, 'city': 'Shanghai'}
options = {"name": "Bob", "age": 20, "city": "Beijing"}
user = create_user(**options)
print(user) # {'name': 'Bob', 'age': 20, 'city': 'Beijing'}
1.3 容器合并
base = {"host": "localhost", "port": 8080}
custom = {"port": 9000, "debug": True}
config = {**base, **custom}
print(config) # {'host': 'localhost', 'port': 9000, 'debug': True}
两个字典合并时,如果有相同的键,后面的值会覆盖前面的值。上面两个字典都有 "port":base 中是 8080,custom 中是 9000,所以 config 中最终是 "port": 9000。
列表合并时用 * 把两个列表中的元素逐个展开:
first = [1, 2]
second = [3, 4]
merged = [*first, *second]
print(merged) # [1, 2, 3, 4]
2. 推导式与常用函数
2.1 推导式
推导式可以快速生成新列表、字典或集合。
numbers = range(1, 6)
squares = [n * n for n in numbers]
even_map = {n: n * n for n in numbers if n % 2 == 0}
unique_remainders = {n % 3 for n in numbers}
print(squares) # [1, 4, 9, 16, 25]
print(even_map) # {2: 4, 4: 16}
print(unique_remainders) # {0, 1, 2},顺序不固定
推导式应保持短小。超过一层循环或包含复杂分支时,普通循环通常更清晰。
2.2 map()、filter() 与 sorted()
numbers = [1, 2, 3, 4]
doubled = list(map(lambda n: n * 2, numbers))
even = list(filter(lambda n: n % 2 == 0, numbers))
users = [{"name": "Bob", "age": 20}, {"name": "Alice", "age": 18}]
ordered = sorted(users, key=lambda user: user["age"])
print(doubled) # [2, 4, 6, 8]
print(even) # [2, 4]
print(ordered) # [{'name': 'Alice', 'age': 18}, {'name': 'Bob', 'age': 20}]
实际开发中,简单转换和过滤通常优先使用推导式:
doubled = [n * 2 for n in numbers]
even = [n for n in numbers if n % 2 == 0]
排序常用参数:
words = ["Python", "java", "Go"]
print(sorted(words, key=str.lower)) # ['Go', 'java', 'Python']
print(sorted(words, key=len, reverse=True)) # ['Python', 'java', 'Go']
3. 函数进阶与常见坑
3.1 仅限关键字参数
在参数列表中使用 *,可以强制后面的参数必须通过关键字传递:
def request(url, *, timeout=5, verify=True):
print(url, timeout, verify)
request("https://example.com", timeout=10) # https://example.com 10 True
这能避免多个同类型参数按位置传递时产生误用。
3.2 外层变量与 nonlocal
嵌套函数想修改外层函数中的变量时,使用 nonlocal:
message = "global"
def outer():
message = "enclosing"
def inner():
nonlocal message
message = "local to outer"
inner()
return message
print(outer()) # local to outer
- 函数内部创建的变量,只在函数内部使用。
nonlocal表示修改外层函数中的同名变量。
3.3 Lambda
lambda 是一行写完的小函数,常用在排序;逻辑复杂时正常写 def:
users = [("Alice", 20), ("Bob", 18)]
users.sort(key=lambda item: item[1])
print(users) # [('Bob', 18), ('Alice', 20)]
3.4 可变默认参数陷阱
函数默认参数不要直接写 [] 或 {},否则多次调用可能共用同一份数据。
错误写法:
def add_item(item, items=[]):
items.append(item)
return items
print(add_item("a")) # ['a']
print(add_item("b")) # ['a', 'b'],不符合预期
正确写法:
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
默认参数对象在函数定义时创建,而不是每次调用时创建。
3.5 循环中创建函数
循环里创建 lambda 时,它可能在最后才读取变量,导致所有函数拿到同一个最终值:
functions = [lambda: i for i in range(3)]
print([function() for function in functions]) # [2, 2, 2]
可以使用默认参数保存当前值:
functions = [lambda i=i: i for i in range(3)]
print([function() for function in functions]) # [0, 1, 2]
4. 迭代器与生成器
生成器按需处理大量数据,yield 会一个一个地给出结果。
4.1 for 能遍历的对象
字符串、列表、元组、集合、字典和文件,都可以直接放进 for 循环。iter() 可以手动取得一个一个读取数据的工具:
items = [1, 2, 3]
iterator = iter(items)
print(next(iterator)) # 1
print(next(iterator)) # 2
每调用一次 next(),就取下一个值;全部取完后不能再继续取。日常遍历直接用 for 即可。
4.2 生成器
函数中写了 yield,它就会每次给出一个值,而不是一次性把所有结果都准备好:
def read_numbers(limit):
for number in range(limit):
yield number
for number in read_numbers(3):
print(number) # 依次显示:0、1、2
生成器按需产生数据,适合处理大文件、大数据流或无限序列:
def non_empty_lines(path):
with open(path, encoding="utf-8") as file:
for line in file:
line = line.strip()
if line:
yield line
生成器表达式:
numbers = (number * number for number in range(1, 4))
print(sum(numbers)) # 14
列表推导式会立即创建完整列表;生成器表达式按需计算,内存占用更低。
5. 装饰器
给已有函数额外加功能,例如记录耗时。 装饰器本质上是接收函数并返回新函数的函数,常用于日志、计时、权限校验和重试。
from functools import wraps
from time import perf_counter
def timer(function):
@wraps(function)
def wrapper(*args, **kwargs):
start = perf_counter()
result = function(*args, **kwargs)
elapsed = perf_counter() - start
print(f"{function.__name__} 用时 {elapsed:.4f}s")
return result
return wrapper
@timer
def add(a, b):
return a + b
print(add(1, 2)) # 先显示类似“add 用时 0.0000s”,再显示 3
装饰器执行顺序:
- 定义函数时,先执行装饰器表达式。
@timer等价于add = timer(add)。- 调用
add()时,实际调用的是wrapper()。 @wraps用于保留原函数的名称和文档信息。
带参数的装饰器需要再增加一层函数:
from functools import wraps
def repeat(times):
def decorator(function):
@wraps(function)
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
result = function(*args, **kwargs)
return result
return wrapper
return decorator
6. with:自动收尾
读写文件时最常用。代码块执行结束后,Python 会自动关闭文件。
除了文件,锁和数据库连接也常用 with;核心作用都是结束后自动清理:
with open("data.txt", encoding="utf-8") as file:
content = file.read()
自己定义能配合 with 使用的类时,要写 __enter__() 和 __exit__():
class Resource:
def __enter__(self):
print("打开资源") # 打开资源
return self
def __exit__(self, exc_type, exc_value, traceback):
print("释放资源") # 释放资源
return False
with Resource() as resource:
print("使用资源") # 使用资源
7. 类:把数据和操作放在一起
面向对象
7.1 类与实例
class User:
platform = "web"
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
return f"我是{self.name},今年{self.age}岁"
user = User("Alice", 18)
print(user.introduce()) # 我是Alice,今年18岁
self就是“当前这个对象自己”。__init__()会在创建对象时自动执行,用来保存数据。self.name = name表示把传入的name保存到当前对象中。- 类属性属于整个类;实例属性属于某一个对象。
7.2 类方法与静态方法
类方法用于读取或修改“整个类”的数据;静态方法只是放在类里的普通工具函数。
class User:
count = 0
def __init__(self, name):
self.name = name
User.count += 1
@classmethod
def total(cls):
return cls.count
@staticmethod
def is_valid_name(name):
return bool(name and name.strip())
- 实例方法第一个参数是
self。 - 类方法使用
@classmethod,第一个参数通常是cls,适合工厂方法或操作类状态。 - 静态方法使用
@staticmethod,不自动接收实例或类,逻辑上属于类的工具函数即可使用。
7.3 属性与特殊方法
property 可以让方法像普通属性一样使用;双下划线开头的方法是 Python 预留的特殊方法。
class Account:
def __init__(self, balance=0):
self._balance = balance
@property
def balance(self):
return self._balance
def __repr__(self):
return f"Account(balance={self._balance!r})"
def __str__(self):
return f"余额:{self._balance}"
常见特殊方法:
__init__:创建对象时自动执行。__str__:执行print(对象)时显示的文本。__repr__:调试时显示对象的信息。__eq__:决定两个对象用==比较时是否相等。 其余特殊方法按需了解。
7.4 继承与组合
继承表示“Dog 是 Animal”;实际业务中,优先把需要的对象组合起来,避免继承层级太深。
class Animal:
def speak(self):
raise NotImplementedError
class Dog(Animal):
def speak(self):
return "汪"
Python 更鼓励组合和鸭子类型:只要对象提供所需方法,就可以使用,不必强制继承同一个父类。
8. 少写样板代码
dataclass 用于存放数据,枚举用于固定选项,类型标注用于说明变量和函数的类型。
8.1 dataclass
如果类主要是保存数据,可以用 dataclass,它会自动生成常用代码:
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
active: bool = True
user = User("Alice", 18)
print(user) # User(name='Alice', age=18, active=True)
列表这类默认值要用 default_factory 单独创建:
from dataclasses import dataclass, field
@dataclass
class Team:
name: str
members: list[str] = field(default_factory=list)
8.2 枚举
枚举用于限制可选值,避免到处手写字符串。
from enum import Enum
class Status(str, Enum):
PENDING = "pending"
SUCCESS = "success"
FAILED = "failed"
status = Status.SUCCESS
print(status.value) # success
8.3 类型标注
类型标注是说明,不会改变代码的运行结果。
from collections.abc import Iterable
def total(values: Iterable[int]) -> int:
return sum(values)
def find_user(user_id: int) -> dict[str, str] | None:
if user_id == 1:
return {"name": "Alice"}
return None
list[str] 是“字符串列表”、str | None 是“字符串或没有值”。
常见写法:
list[str]:字符串列表。dict[str, int]:键为字符串、值为整数的字典。str | None:字符串或None。Iterable[int]:可以遍历并产生整数的对象。Callable[[int], str]:接收整数并返回字符串的函数。
9. 虚拟环境与项目结构
虚拟环境让每个项目拥有独立的第三方库,避免不同项目的依赖互相影响。 创建和使用虚拟环境:
python -m venv .venv
source .venv/bin/activate
python -m pip install requests
python -m pip freeze > requirements.txt
deactivate
Windows 激活命令:
.venv\Scripts\activate
推荐的最小项目结构:
project/
├── pyproject.toml
├── README.md
├── src/
│ └── project_name/
│ ├── __init__.py
│ └── main.py
└── tests/
└── test_main.py
项目做大后,运行包中的文件可以使用 python -m:
python -m project_name.main
10. 常见避坑
这些问题代码不一定立刻报错,但很容易得到意外结果。
10.1 is 不用于比较值
# 错误倾向
if value is 100:
pass
# 正确
if value == 100:
pass
if value is None:
pass
比较数字、字符串、列表等内容时用 ==;只有判断是否为 None 时,习惯用 is None。
10.2 浅拷贝与深拷贝
浅拷贝只复制最外层,嵌套对象仍可能共享:
import copy
original = [[1, 2], [3, 4]]
shallow = original.copy()
shallow[0].append(9)
print(original) # [[1, 2, 9], [3, 4]],嵌套列表也变了
deep = copy.deepcopy(original)
deep[0].append(8)
print(original) # [[1, 2, 9], [3, 4]],这次原数据不变
10.3 重复引用的嵌套列表
# 错误:三行实际上引用同一个列表
matrix = [[0] * 3] * 3
matrix[0][0] = 1
print(matrix) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]
# 正确:每一行单独创建
matrix = [[0] * 3 for _ in range(3)]
10.4 不要在遍历时直接修改容器
numbers = [1, 2, 3, 4]
numbers = [number for number in numbers if number % 2 == 0]
如果必须修改字典,先复制键或构造新字典:
data = {"a": 1, "b": 2, "c": 3}
for key in list(data):
if data[key] % 2 == 1:
del data[key]
10.5 不要捕获后什么都不做
# 不推荐
try:
load_config()
except Exception:
pass
应捕获能处理的具体异常,并保留上下文:
try:
load_config()
except FileNotFoundError as error:
print(f"配置文件不存在:{error}")
10.6 sort() 与 sorted() 不同
numbers = [3, 1, 2]
result = sorted(numbers)
print(result) # [1, 2, 3]
print(numbers) # [3, 1, 2],原列表不变
numbers.sort()
print(numbers) # [1, 2, 3],原列表被修改
list.sort() 返回 None,不要写成 numbers = numbers.sort()。
10.7 真假值不等于严格的空值判断
items = []
if not items:
print("空列表或其他假值") # 空列表或其他假值
if items is None:
print("只有 None 才进入") # items 是 [],所以这里不显示
根据业务需求区分“没有值”和“值为空”。
11. 与Java 的差异
- Python 变量不需要声明固定类型;类型标注主要用于提示和检查。
- 花括号和分号不是代码块语法,代码块由缩进决定。
null对应None,判断使用is None。ArrayList、HashSet、HashMap常分别对应list、set、dict。- 实例方法通常显式写
self,但调用时不需要手动传入。 - Python 没有 Java 式方法重载,通常使用默认参数、关键字参数、
*args或不同函数名。 - Python 常使用鸭子类型;对象有需要的方法即可,不一定需要实现接口。
==比较值,is比较对象身份。- 没有 checked exception;异常通常在真正能处理的位置捕获。
- Stream 中常见的映射、过滤、排序,通常使用推导式、生成器和内置函数完成。
- 不要为了模仿 Java 而给每个字段都创建 getter/setter;优先直接使用属性,必要时使用
property。