找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3007

积分

0

好友

413

主题
发表于 13 小时前 | 查看: 3| 回复: 0

咱做Python开发的,谁没见过几行让人头疼的“烂代码”?接手老项目时,满屏的魔法数字、嵌套八层的循环、几百行功能混杂的巨型函数,改一个Bug可能引出一串新问题。再看看自己半年前写的代码,有时也会困惑:这玩意儿是我写的?怎么这么难懂?

更关键的是,糟糕的代码不仅自己维护起来费劲,团队协作效率低下,线上问题排查更是耗时耗力。Python本身是一门以简洁、优雅为设计哲学的语言,写出“烂代码”往往不是能力问题,而是缺乏良好的编码习惯和基础的重构意识。

代码重构并非炫技或无意义的重写,其核心是在不改变外部行为的前提下,优化代码的内部结构,使其更易读、易维护、更健壮。本文整理了10个立即可用的Python重构技巧,每个技巧都通过 “问题代码”与“优雅实现” 的对比来呈现。掌握它们,你的代码质量和工程效率将显著提升。

一、提取魔法数/字符串为常量

问题场景

代码中随处可见无意义的字面量数字、硬编码的字符串。其他人阅读时难以理解其含义,需要修改时则必须全局搜索,漏改一处就可能引发Bug,典型的“编写一时爽,维护火葬场”。

Before 问题代码

# 谁知道3.1415926是啥?60*60又是什么意思?
# 接口地址硬编码,改地址要改遍所有代码
def calculate_circle_area(radius):
    return 3.1415926 * radius * radius

def get_user_info(user_id):
    res = requests.get("https://api.xxx.com/v1/user/info", params={"id": user_id}, timeout=30)
    if res.status_code == 200:
        return res.json()

def cal_oneday_seconds():
    return 60 * 60 * 24

After 优雅代码

import requests

# 提取所有魔法数/字符串为常量,大写命名+注释说明,语义清晰
PI = 3.1415926 # 圆周率
ONE_DAY_SECONDS = 86400 # 一天的秒数
USER_INFO_API = "https://api.xxx.com/v1/user/info" # 用户信息接口地址
REQUEST_TIMEOUT = 30 # 接口请求超时时间
HTTP_SUCCESS_CODE = 200 # HTTP请求成功状态码

def calculate_circle_area(radius):
    return PI * radius * radius

def get_user_info(user_id):
    res = requests.get(USER_INFO_API, params={"id": user_id}, timeout=REQUEST_TIMEOUT)
    if res.status_code == HTTP_SUCCESS_CODE:
        return res.json()

def cal_oneday_seconds():
    return ONE_DAY_SECONDS

改进原理

  1. 语义化:常量命名配合注释,使代码自解释,降低对额外文档的依赖。
  2. 易维护:修改时只需调整常量定义一处,即可全局生效,避免遗漏。
  3. 规范统一:符合 Python PEP8 规范,常量使用全大写加下划线分隔,保持代码风格一致。

适用场景

代码中出现的、固定不变且可能被多次使用的数字、字符串,例如配置参数、状态码、数学常量、API地址等。

二、用列表推导式替换循环

问题场景

为了生成一个新列表,编写冗长的 for 循环配合 append 操作,代码行数多且逻辑不够直观,未能体现Python的简洁特性。

Before 问题代码

# 生成1-10的偶数列表,写了3行代码,存在冗余
even_nums = []
for num in range(1, 11):
    if num % 2 == 0:
        even_nums.append(num)

# 提取列表中所有字符串的长度,又是一堆循环
str_list = ["Python", "重构", "技巧", "优雅"]
len_list = []
for s in str_list:
    len_list.append(len(s))

After 优雅代码

# 列表推导式,一行搞定,逻辑直观
even_nums = [num for num in range(1, 11) if num % 2 == 0]

# 映射操作也能轻松处理
str_list = ["Python", "重构", "技巧", "优雅"]
len_list = [len(s) for s in str_list]

# 甚至可以包含计算逻辑
num_list = [1,2,3,4]
square_list = [n**2 for n in num_list] # 生成平方数列表

改进原理

  1. 简洁高效:将多行循环浓缩为一行,减少模板代码,提升可读性。
  2. 执行更快:列表推导式在Python解释器层面进行了优化,通常比手动 for 循环加 append 效率更高。
  3. 逻辑连贯:将数据遍历、条件判断、结果处理集中在一处表达,无需在代码行间跳转阅读。

适用场景

简单的列表生成与转换场景,特别是涉及遍历、过滤和简单映射的情况。注意:推导式嵌套不宜超过两层,否则会影响可读性。

三、用上下文管理器 (with) 管理资源

问题场景

手动打开文件、建立数据库连接后,可能忘记调用 close() 方法导致资源泄漏;或者即使写了 close(),但在代码执行过程中抛出异常,close() 未能执行,资源同样无法释放。

Before 问题代码

# 手动打开文件,若忘记或异常跳过close(),将导致文件句柄泄漏
f = open("test.txt", "r", encoding="utf-8")
content = f.read()
# 此处如果抛出异常,下面的close()根本执行不到
print(content)
# f.close() # 容易被遗忘或跳过

# 数据库连接同理,手动关闭极易遗漏
import pymysql
conn = pymysql.connect(host="localhost", user="root", password="123456", db="test")
cursor = conn.cursor()
cursor.execute("select * from user")
conn.commit()
# 忘记关闭游标和连接,数据库连接池资源被占满
# cursor.close()
# conn.close()

After 优雅代码

# with上下文管理器,自动确保文件关闭,无论是否抛出异常
with open("test.txt", "r", encoding="utf-8") as f:
    content = f.read()
    print(content)

# 数据库连接也能用with管理(部分库需适配上下文协议)
import pymysql
with pymysql.connect(host="localhost", user="root", password="123456", db="test") as conn:
    with conn.cursor() as cursor:
        cursor.execute("select * from user")
        conn.commit()
    # 退出with块后,cursor和conn会自动关闭,无需手动操作

改进原理

上下文管理器实现了 __enter____exit__ 魔法方法。进入 with 块时执行 __enter__,退出时(包括因异常退出)自动执行 __exit__,从而保证资源被正确释放。这是Python中管理资源的首选模式。

适用场景

所有需要显式申请和释放的资源,如文件操作、数据库连接、网络套接字、线程锁等。

四、用装饰器封装通用逻辑

问题场景

多个函数包含相同的前置或后置逻辑,例如日志记录、性能计时、参数校验、异常捕获等。如果每个函数都复制粘贴这些代码,会导致高度冗余,且修改逻辑时需要改动多处。

Before 问题代码

import time
import logging

logging.basicConfig(level=logging.INFO)

# 函数1:包含耗时统计和日志打印
def add(a, b):
    start = time.time()
    logging.info(f"开始执行add函数,参数:a={a}, b={b}")
    res = a + b
    end = time.time()
    logging.info(f"add函数执行完成,结果:{res},耗时:{end-start:.2f}s")
    return res

# 函数2:完全相同的日志和计时逻辑,代码重复
def multiply(a, b):
    start = time.time()
    logging.info(f"开始执行multiply函数,参数:a={a}, b={b}")
    res = a * b
    end = time.time()
    logging.info(f"multiply函数执行完成,结果:{res},耗时:{end-start:.2f}s")
    return res

After 优雅代码

import time
import logging
from functools import wraps

logging.basicConfig(level=logging.INFO)

# 定义通用装饰器,封装重复的日志+耗时统计逻辑
def log_and_calc_time(func):
    @wraps(func)  # 保留原函数的元信息(如函数名、文档字符串)
    def wrapper(*args, **kwargs):
        start = time.time()
        logging.info(f"开始执行{func.__name__}函数,参数:args={args}, kwargs={kwargs}")
        res = func(*args, **kwargs)  # 执行原函数核心逻辑
        end = time.time()
        logging.info(f"{func.__name__}函数执行完成,结果:{res},耗时:{end-start:.2f}s")
        return res
    return wrapper

# 使用装饰器修饰函数,一行搞定,消除重复代码
@log_and_calc_time
def add(a, b):
    return a + b

@log_and_calc_time
def multiply(a, b):
    return a * b

# 新增函数只需添加装饰器,逻辑统一,修改装饰器即可全局生效
@log_and_calc_time
def subtract(a, b):
    return a - b

改进原理

  1. 遵循 DRY 原则:将多个函数的共性横切关注点(如日志、计时)抽取到装饰器中,实现代码的高度复用。
  2. 解耦:业务核心逻辑与通用辅助逻辑分离,函数职责更单一、更清晰。
  3. 易扩展与维护:需要修改通用逻辑时,仅需改动装饰器定义一处。

适用场景

多个函数共享相同的非业务核心逻辑,如日志、性能监控、权限校验、缓存、重试机制等。这是实现 代码复用 和关注点分离的利器。

五、遵循单一职责原则(拆分巨型函数)

问题场景

编写了一个长达数百行、功能混杂的“巨型函数”,它可能同时负责数据读取、清洗、计算、存储和日志。调试时难以定位问题,修改一处逻辑可能无意间影响其他功能。

Before 问题代码

# 一个函数包揽所有事:读文件→处理数据→计算统计→保存结果,难以维护
def handle_data(file_path):
    # 1. 读取文件
    with open(file_path, "r", encoding="utf-8") as f:
        lines = f.readlines()
    # 2. 处理数据:去除空行、分割
    data = []
    for line in lines:
        line = line.strip()
        if not line:
            continue
        data.append(line.split(","))
    # 3. 计算统计:求第二列的平均值
    total = 0
    count = 0
    for row in data[1:]:  # 跳过表头
        total += float(row[1])
        count += 1
    avg = total / count if count > 0 else 0
    # 4. 保存结果
    with open("result.txt", "w", encoding="utf-8") as f:
        f.write(f"平均值:{avg:.2f}")
    return avg

After 优雅代码

# 单一职责:每个函数只做一件明确的事,函数名即文档
def read_file(file_path):
    """读取文件,返回行列表"""
    with open(file_path, "r", encoding="utf-8") as f:
        lines = f.readlines()
    return lines

def process_data(lines):
    """处理数据,去除空行并分割,返回处理后的二维列表"""
    data = []
    for line in lines:
        line = line.strip()
        if not line:
            continue
        data.append(line.split(","))
    return data

def calculate_average(data):
    """计算第二列的平均值,返回平均值"""
    total = 0
    count = 0
    for row in data[1:]:
        total += float(row[1])
        count += 1
    return total / count if count > 0 else 0

def save_result(avg):
    """将平均值保存到结果文件"""
    with open("result.txt", "w", encoding="utf-8") as f:
        f.write(f"平均值:{avg:.2f}")

# 主协调函数:逻辑清晰,像阅读流程图
def handle_data(file_path):
    lines = read_file(file_path)
    data = process_data(lines)
    avg = calculate_average(data)
    save_result(avg)
    return avg

改进原理

  1. 单一职责:每个函数负责一个独立、完整的功能模块,符合高内聚的设计思想。
  2. 易于测试与调试:每个小函数都可以进行独立的单元测试,问题定位速度快。
  3. 高复用性:拆分后的功能模块(如 read_file, process_data)更容易在其他上下文中被复用。
  4. 易于维护与扩展:修改特定功能时,只需关注对应的单一函数,影响范围可控。

适用场景

任何代码行数过多(例如超过50行)、函数名无法清晰概括其所有行为、内部包含多个逻辑阶段的函数。

六、用枚举 (Enum) 替换魔法字符串

问题场景

代码中使用字符串字面量来表示状态、类型等有限集合的值(如 "success", "failed")。拼写错误无法在编码阶段发现,只能在运行时出错,且缺乏类型安全性和编辑器智能提示。

Before 问题代码

# 魔法字符串表示订单状态,拼写错误将导致Bug
def handle_order(order_id, status):
    if status == "success":
        print(f"订单{order_id}支付成功")
    elif status == "failed":
        print(f"订单{order_id}支付失败")
    elif status == "pending":
        print(f"订单{order_id}待支付")
    else:
        print(f"订单{order_id}状态未知")

# 调用时拼错,运行时才会抛出异常或逻辑错误
handle_order(1001, "succes") # 漏了一个's',静默失败或错误处理

After 优雅代码

from enum import Enum, unique

# 用枚举定义订单状态,@unique确保值唯一
@unique
class OrderStatus(Enum):
    SUCCESS = "success" # 支付成功
    FAILED = "failed"  # 支付失败
    PENDING = "pending" # 待支付

# 使用枚举类型作为参数,类型明确
def handle_order(order_id, status: OrderStatus):
    if status == OrderStatus.SUCCESS:
        print(f"订单{order_id}支付成功")
    elif status == OrderStatus.FAILED:
        print(f"订单{order_id}支付失败")
    elif status == OrderStatus.PENDING:
        print(f"订单{order_id}待支付")
    else:
        print(f"订单{order_id}状态未知")

# 正确调用:编辑器提供代码补全,避免拼写错误
handle_order(1001, OrderStatus.SUCCESS)
# 错误调用:OrderStatus.SUCCES 在编码或静态检查阶段就会报错

改进原理

  1. 类型安全:枚举成员是强类型的单例,拼写错误在编码或静态类型检查(如mypy)阶段即可发现。
  2. 增强的 IDE 支持:主流编辑器(PyCharm, VSCode)可对枚举成员进行自动补全和跳转。
  3. 集中化管理:所有可能的状态值在一个地方定义和管理,修改和扩展方便。
  4. 提升可读性OrderStatus.SUCCESS"success" 语义更明确,自解释性更强。

适用场景

代码中需要表示固定的、有限集合的状态、类型、类别或返回码,例如订单状态、用户角色、错误类型、API动作等。

七、用生成器优化内存使用

问题场景

需要处理或生成一个非常大的数据集时,直接使用列表推导式或循环 append一次性在内存中创建完整的列表,可能导致内存占用过高,甚至引发 MemoryError

Before 问题代码

# 生成1000万个数字的平方列表,直接占用数百MB内存
def generate_big_list(n):
    return [i**2 for i in range(n)]

big_list = generate_big_list(10**7) # 瞬间消耗大量内存
# 遍历列表时,所有数据已加载在内存中
for num in big_list:
    if num > 1000:
        break # 实际上可能只需要前几个元素

After 优雅代码

# 生成器表达式:用圆括号替代方括号,惰性求值
def generate_big_generator(n):
    return (i**2 for i in range(n))

# 生成器对象本身占用极小内存,与n大小无关
big_generator = generate_big_generator(10**7)
# 遍历时按需生成数据,用完即弃,内存友好
for num in big_generator:
    if num > 1000:
        break

# 函数式生成器:使用yield关键字,更灵活
def my_generator(n):
    for i in range(n):
        yield i**2  # 每次产生一个值后暂停

改进原理

  1. 惰性求值:生成器不会预先计算所有结果,而是在迭代时按需生成下一个值。
  2. 极致的内存效率:生成器对象只保存当前的迭代状态和生成规则,不保存结果集合,内存占用恒定且极小。
  3. 适用于流式处理:非常适合处理无法一次性装入内存的超大文件、数据库查询结果或网络数据流。

适用场景

需要处理超大数据集、数据只需被顺序遍历一次、或数据来源本身是流式/按需生成的情况。注意:生成器只能迭代一次,如需多次使用,需重新创建或转换为列表。

八、用字典映射替换多重 if-elif

问题场景

编写了冗长的 if-elif-else 链(通常超过3个分支)来进行条件分发。代码结构呈“阶梯状”,可读性差,添加新分支时需要修改函数内部结构,违反开闭原则。

Before 问题代码

# 多重if-elif,分支增多后代码冗长且不易读
def get_operation_result(op, a, b):
    if op == "add":
        return a + b
    elif op == "subtract":
        return a - b
    elif op == "multiply":
        return a * b
    elif op == "divide":
        if b == 0:
            return "除数不能为0"
        return a / b
    else:
        return "不支持的操作"

After 优雅代码

# 首先,定义每个分支对应的处理函数(职责单一)
def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

def multiply(a, b):
    return a * b

def divide(a, b):
    if b == 0:
        return "除数不能为0"
    return a / b

# 使用字典建立操作符到处理函数的映射
OPERATION_MAP = {
    "add": add,
    "subtract": subtract,
    "multiply": multiply,
    "divide": divide
}

def get_operation_result(op, a, b):
    # 从字典中获取处理函数,未找到则返回None
    func = OPERATION_MAP.get(op)
    if func:
        return func(a, b)
    return "不支持的操作"

# 进阶:对于简单逻辑,可直接使用lambda表达式
OPERATION_MAP_SIMPLE = {
    "add": lambda a,b: a+b,
    "subtract": lambda a,b: a-b,
    "multiply": lambda a,b: a*b
}

改进原理

  1. 扁平化结构:将线性的条件判断转换为查表操作,代码更加扁平、清晰。
  2. 符合开闭原则:新增操作类型时,只需向映射字典添加新的键值对,无需修改 get_operation_result 函数的内部逻辑。
  3. 逻辑集中:所有的业务分支映射关系在一个数据结构中一目了然,便于管理和审查。

适用场景

基于固定值(字符串、数字等)进行多路分发的场景,例如命令解析、状态机、策略模式、简单路由等。

九、使用数据类 (dataclass) 简化对象定义

问题场景

为了定义一个主要用来存储数据的简单类(如DTO、实体类),需要手动编写大量的模板代码:__init____repr____eq__ 等。这不仅繁琐,而且容易出错。

Before 问题代码

# 手动编写所有样板代码,冗长且易错
class User:
    def __init__(self, id: int, name: str, age: int, email: str):
        self.id = id
        self.name = name
        self.age = age
        self.email = email

    def __repr__(self):
        return f"User(id={self.id}, name='{self.name}', age={self.age}, email='{self.email}')"

    def __eq__(self, other):
        if not isinstance(other, User):
            return False
        return (self.id == other.id and self.name == other.name
                and self.age == other.age and self.email == other.email)

u1 = User(1, "张三", 20, "zhangsan@xxx.com")
u2 = User(1, "张三", 20, "zhangsan@xxx.com")
print(u1)  # 需要自定义__repr__
print(u1 == u2)  # 需要自定义__eq__

After 优雅代码

from dataclasses import dataclass

# @dataclass装饰器自动生成__init__, __repr__, __eq__等方法
@dataclass
class User:
    id: int
    name: str
    age: int
    email: str

# 高级用法:通过参数启用更多特性
@dataclass(frozen=True, order=True) # frozen表示不可变,order支持排序比较
class Order:
    order_id: str
    amount: float
    create_time: str

# 使用方式完全一致,但定义代码量减少80%以上
u1 = User(1, "张三", 20, "zhangsan@xxx.com")
u2 = User(1, "张三", 20, "zhangsan@xxx.com")
print(u1)  # 自动生成友好的表示
print(u1 == u2)  # 自动基于字段值进行判等

o1 = Order("OD1001", 99.9, "2026-02-04")
o2 = Order("OD1002", 199.9, "2026-02-04")
print(o1 < o2)  # 启用了order=True,支持比较(默认按字段声明顺序)

改进原理

dataclasses 是 Python 3.7+ 的标准库。@dataclass 装饰器会根据类中定义的类型注解,自动生成包括 __init____repr____eq__ 在内的常用魔法方法,彻底解放开发者,让其专注于定义数据本身。

适用场景

定义主要用于存储数据、而非包含复杂行为的类,例如配置对象、API请求/响应模型、数据库实体等。

十、为代码添加类型注解

问题场景

函数参数和返回值的类型模糊不清,调用者只能通过阅读函数体或文档来猜测。传递错误类型的参数只能在运行时抛出异常,静态检查工具无法提供帮助,降低了代码的可靠性和开发体验。

Before 问题代码

# 无类型提示,a和b的类型?返回值类型?全靠猜测或运行时报错
def add(a, b):
    return a + b

# 类型错误直到运行时才暴露
result = add(1, "2")  # TypeError: unsupported operand type(s) for +: 'int' and 'str'

def process_data(data, limit, offset):
    return data[offset:offset+limit] # data的类型?是列表吗?

After 优雅代码

# 添加基础类型注解,意图一目了然
def add(a: int, b: int) -> int:
    return a + b

# 使用typing模块处理复杂类型
from typing import List, Dict, Optional, Any

def process_data(
    data: List[Any],
    limit: int = 10,           # 带默认值的参数
    offset: Optional[int] = 0  # 可选参数,允许传入None
) -> List[Any]:
    if offset is None:
        offset = 0
    return data[offset:offset+limit]

def get_user_dict() -> Dict[int, str]:
    return {1: "张三", 2: "李四"}

# 调用时,支持类型检查的编辑器(如PyCharm, VSCode)或mypy会提前警告类型不匹配
add(1, "2")                # 编辑器通常会标出类型错误
process_data([1,2,3], "10", 0) # 提示limit参数应为int

改进原理

  1. 代码即文档:类型注解清晰说明了接口契约,无需额外注释即可理解函数用法。
  2. 早期错误检测:配合静态类型检查工具(如 mypy),可以在代码运行前发现大量的类型错误。
  3. 提升 IDE 体验:编辑器能基于类型注解提供更准确的代码补全、参数提示和重构支持。
  4. 便于重构:修改函数签名后,类型检查器能快速定位所有不兼容的调用点。

适用场景

所有生产环境代码,尤其是团队合作项目。从Python 3.9开始,可以使用内置的 list, dict 等直接作为类型注解,更加简洁。

重构的核心原则

掌握具体技巧后,理解其背后的原则更能指导实践:

  1. 可读性优先:代码首先是写给人看的,其次才是让机器执行。重构的首要目标是提升代码的可读性和可理解性。
  2. 遵循 DRY 原则:消除重复是减少错误、提高可维护性的关键。发现重复逻辑,就应考虑抽象和封装。
  3. 小步迭代,避免过度设计:重构应是持续、渐进的过程,而非推翻重来的“大爆炸”。在满足当前需求的前提下保持代码整洁,无需为未知的未来需求做过度抽象。

日常重构自查清单

开发完成后,可以快速对照以下清单检查自己的代码:

  1. 是否有未提取的魔法数字或字符串?
  2. 是否有可用列表推导式简化的循环?
  3. 资源管理是否都使用了 with 语句?
  4. 多个函数的通用逻辑是否可用装饰器封装?
  5. 是否有超过50行、职责过多的函数需要拆分?
  6. 固定状态集是否用枚举而非字符串表示?
  7. 处理大数据集时是否考虑了生成器?
  8. 多重 if-elif 是否可用字典映射优化?
  9. 简单数据类是否使用了 @dataclass
  10. 函数和方法的参数、返回值是否添加了类型注解?

希望这10个Python代码重构技巧能为你带来实质性的帮助。编程能力的提升是一个不断反思和改进的过程。如果你有自己独特的重构案例或心得,欢迎在技术社区如云栈社区与更多开发者交流分享,共同进步。




上一篇:从Win32消息循环到AI Agent:OpenClaw PI引擎揭示的永恒事件驱动架构
下一篇:高性能HTTP服务器开发语言选型:C++、Golang、Rust、Java全方位对比与实战建议
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-10 18:05 , Processed in 0.316669 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表