Dev

Python 语法速通 02

上传于 2026年5月17日38 个章节

Python 语法速通 02

38 个章节

梳理 Python 解包、生成器、装饰器、上下文管理、面向对象与类型标注,记录常见陷阱及与 Java 的差异。

  • #Python
  • #语法速通

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。

搜索文章与随记

输入关键词搜索全部公开内容