首页
学习
活动
专区
圈层
工具
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

让你的 Python 代码更优雅:10 个实用技巧,助你从“能跑”到“可维护”

如果你已经用 Python 编码有一段时间了,可能已经掌握了基础知识,也做过一些项目。现在你看着自己的代码,心里想着:“虽然能用,但……好像还不敢在代码评审时自信地展示。”我们都经历过这种阶段。

但随着你不断编码,写出整洁的代码就和写出能用的代码一样重要。本文为你汇总了一些实用技巧,帮助你的代码从“能跑,别动它”进阶到“这代码值得维护”。

1. 明确定义数据模型,别到处传递 dict

Python 的字典非常灵活,但这恰恰是问题所在。到处传递原始字典会引发拼写错误、键不存在等问题,让人搞不清到底应该有哪些数据字段。

不推荐这样写:

def process_user(user_dict):

  if user_dict['status'] == 'active':  # 如果 'status' 缺失会怎样?

      send_email(user_dict['email'])   # 有些地方写成 'mail' 怎么办?

      # 是 'name'、'full_name' 还是 'username'?谁知道呢!

      log_activity(f"Processed {user_dict['name']}")

这段代码假定字典里的键一定存在,没有任何校验,容易在运行时抛出 KeyError 异常,也没有文档说明期望有哪些字段。

推荐这样做:

from dataclasses import dataclass

from typing import Optional

from datetime import datetime

@dataclass

class User:

  id: int

  email: str

  full_name: str

  status: str

  last_login: Optional[datetime] = None

def process_user(user: User):

  if user.status == 'active':

      send_email(user.email)

      log_activity(f"Processed {user.full_name}")

Python 的@dataclass装饰器让你用很少的样板代码定义清晰、明确的数据结构。你的 IDE 现在可以自动补全属性,缺失字段会立刻报错。

需要复杂校验时,可以考虑 Pydantic:

from pydantic import BaseModel, EmailStr, validator

class User(BaseModel):

  id: int

  email: EmailStr  # 校验邮箱格式

  full_name: str

  status: str

  @validator('status')

  def status_must_be_valid(cls, v):

      if v not in {'active', 'inactive', 'pending'}:

          raise ValueError('Must be active, inactive or pending')

      return v

这样你的数据会自我校验,能早期发现错误,也清楚地记录了字段要求。

2. 用枚举类型管理固定选项

字符串字面量易拼错,也没自动补全,只有在运行时才能发现问题。

不推荐这样写:

def process_order(order, status):

  if status == 'pending':

      # 处理逻辑

  elif status == 'shipped':

      # 不同逻辑

  elif status == 'delivered':

      # 更多逻辑

  else:

      raise ValueError(f"Invalid status: {status}")

# 后面调用

process_order(order, 'shiped')  # 拼写错了!IDE 没提示

推荐这样做:

from enum import Enum

class OrderStatus(Enum):

  PENDING = 'pending'

  SHIPPED = 'shipped'

  DELIVERED = 'delivered'

def process_order(order, status: OrderStatus):

  if status == OrderStatus.PENDING:

      # 处理逻辑

  elif status == OrderStatus.SHIPPED:

      # 不同逻辑

  elif status == OrderStatus.DELIVERED:

      # 更多逻辑

# 调用

process_order(order, OrderStatus.SHIPPED)  # IDE 自动补全,避免拼写错!

用枚举(Enum)类型:

IDE 能自动补全

拼写错误几乎不可能

可以遍历所有选项

类型提示更清晰,文档化参数类型

3. 用关键字参数提升调用可读性

Python 的参数系统很灵活,但当函数有多个可选布尔参数时,调用代码容易让人迷惑。

不推荐这样写:

def create_user(name, email, admin=False, notify=True, temporary=False):

  # 实现

# 调用时

create_user("John Smith", "john@example.com", True, False)

看到True, False,你还记得哪个代表 admin,哪个代表 notify 吗?没有查函数定义根本看不出来。

推荐这样做:

def create_user(name, email, *, admin=False, notify=True, temporary=False):

  # 实现

# 现在必须用关键字指定可选参数

create_user("John Smith", "john@example.com", admin=True, notify=False)

*语法强制之后的参数必须用关键字指定。这样调用函数时,自文档化,避免了“神秘布尔值”问题。

这个模式在 API 调用等场景尤其有用,能显著提升代码可读性。

4. 用 Pathlib 替代 os.path

Python 的os.path已经能用,但操作繁琐。pathlib更简洁、面向对象且更易移植。

不推荐这样写:

import os

data_dir = os.path.join('data', 'processed')

if not os.path.exists(data_dir):

  os.makedirs(data_dir)

filepath = os.path.join(data_dir, 'output.csv')

with open(filepath, 'w') as f:

  f.write('results\n')

json_path = os.path.splitext(filepath)[0] + '.json'

if os.path.exists(json_path):

  with open(json_path) as f:

      data = json.load(f)

字符串拼接、路径操作分散,语义不直观。

推荐这样做:

from pathlib import Path

import json

data_dir = Path('data') / 'processed'

data_dir.mkdir(parents=True, exist_ok=True)

filepath = data_dir / 'output.csv'

filepath.write_text('results\n')

json_path = filepath.with_suffix('.json')

if json_path.exists():

  data = json.loads(json_path.read_text())

为什么 pathlib 更好:

路径拼接用/更直观

方法如mkdir()、exists()、read_text()都是对象方法

改扩展名用with_suffix()语义更清晰

跨平台更健壮

5. 用 Guard Clauses 尽早返回,减少嵌套

多层嵌套的 if 语句难以阅读和维护。用“守卫子句”提前返回,让主逻辑清晰可见。

不推荐这样写:

def process_payment(order, user):

  if order.is_valid:

      if user.has_payment_method:

          payment_method = user.get_payment_method()

          if payment_method.has_sufficient_funds(order.total):

              try:

                  payment_method.charge(order.total)

                  order.mark_as_paid()

                  send_receipt(user, order)

                  return True

              except PaymentError as e:

                  log_error(e)

                  return False

          else:

              log_error("Insufficient funds")

              return False

      else:

          log_error("No payment method")

          return False

  else:

      log_error("Invalid order")

      return False

深层嵌套难以追踪逻辑分支。

推荐这样做:

def process_payment(order, user):

  if not order.is_valid:

      log_error("Invalid order")

      return False

  if not user.has_payment_method:

      log_error("No payment method")

      return False

  payment_method = user.get_payment_method()

  if not payment_method.has_sufficient_funds(order.total):

      log_error("Insufficient funds")

      return False

  try:

      payment_method.charge(order.total)

      order.mark_as_paid()

      send_receipt(user, order)

      return True

  except PaymentError as e:

      log_error(e)

      return False

守卫子句让错误情况一开始就被处理,主流程清晰明了,缩进层数大大减少。逻辑越复杂,这种风格越明显优于深嵌套。

6. 避免滥用列表推导式

列表推导(List Comprehension)是 Python 优雅的语法特性,但当逻辑变复杂时,过度使用会让代码难以阅读和维护。

不推荐这样写:

# 一行中塞入太多逻辑,难以一眼读懂

active_premium_emails = [user['email'] for user in users_list

                       if user['status'] == 'active' and

                       user['subscription'] == 'premium' and

                       user['email_verified'] and

                       not user['email'] in blacklisted_domains]

这种写法把多重条件和转换全部塞进一行,条件一多就很难理解和调试。

推荐这样做:

方案一:用具名函数提取复杂条件

将复杂的过滤条件提到一个具名函数中,使列表推导更专注于“做什么”而不是“怎么做”。

def is_valid_premium_user(user):

  return (user['status'] == 'active' and

          user['subscription'] == 'premium' and

          user['email_verified'] and

          not user['email'] in blacklisted_domains)

active_premium_emails = [user['email'] for user in users_list if is_valid_premium_user(user)]

方案二:当逻辑复杂时用传统循环

用 for 循环和提前 continue 语句,每个条件单独判断,便于调试和理解。

active_premium_emails = []

for user in users_list:

  if user['status'] != 'active':

      continue

  if user['subscription'] != 'premium':

      continue

  if not user['email_verified']:

      continue

  if user['email'] in blacklisted_domains:

      continue

  # 可以在此做更多复杂的转换

  email = user['email'].lower().strip()

  active_premium_emails.append(email)

总之,列表推导式应提升可读性,不应让代码变难懂。复杂的逻辑建议:

拆成具名函数

用传统循环

拆分为多步操作

记住,代码的最终目的是让人易读易维护。

7. 写可复用的纯函数

纯函数(Pure Function)指:同样输入永远得到同样输出,且无副作用。

不推荐这样写:

total_price = 0  # 全局状态

def add_item_price(item_name, quantity):

  global total_price

  # 从全局库存查价

  price = inventory.get_item_price(item_name)

  # 应用折扣

  if settings.discount_enabled:

      price *= 0.9

  # 修改全局状态

  total_price += price * quantity

# 后面调用

add_item_price('widget', 5)

add_item_price('gadget', 3)

print(f"Total: ${total_price:.2f}")

这种写法依赖和修改全局状态,难以测试和复用。

推荐这样做:

def calculate_item_price(item, price, quantity, discount=0):

  """计算某商品数量的最终价格,可选折扣。"""

  discounted_price = price * (1 - discount)

  return discounted_price * quantity

def calculate_order_total(items, discount=0):

  """计算一组商品的总价。"""

  return sum(

      calculate_item_price(item, price, quantity, discount)

      for item, price, quantity in items

  )

# 调用

order_items = [

  ('widget', inventory.get_item_price('widget'), 5),

  ('gadget', inventory.get_item_price('gadget'), 3),

]

total = calculate_order_total(order_items,

                           discount=0.1 if settings.discount_enabled else 0)

print(f"Total: ${total:.2f}")

这种做法让所有依赖通过参数传递,不依赖外部状态,更易于测试和复用。

8. 为所有公共函数和类写文档字符串(Docstring)

文档不是事后补充,而是可维护代码的核心部分。好的 docstring 不仅说明做什么,还解释为什么要这样做,以及如何正确使用。

不推荐这样写:

def celsius_to_fahrenheit(celsius):

  """Convert Celsius to Fahrenheit."""

  return celsius * 9/5 + 32

只有一句无实际信息的描述。

推荐这样做:

def celsius_to_fahrenheit(celsius):

  """

  将摄氏温度转换为华氏温度。

  使用公式:F = C × (9/5) + 32

  参数:

      celsius: 摄氏温度(float 或 int)

  返回:

      对应的华氏温度

  示例:

      >>> celsius_to_fahrenheit(0)

      32.0

      >>> celsius_to_fahrenheit(100)

      212.0

      >>> celsius_to_fahrenheit(-40)

      -40.0

  """

  return celsius * 9/5 + 32

好的 docstring 应该:

说明参数和返回值

说明可能抛出的异常

给出用法示例

你的 docstring 就是代码的实时文档。

9. 自动化代码风格检查与格式化

不要仅靠人工检查去发现代码风格问题和常见错误。自动化工具能帮你保证代码质量和风格一致性,大幅减轻维护负担。

你可以尝试以下代码检查和格式化工具:

Black

—— 代码格式化工具

Ruff

—— 高效的静态代码检查工具

mypy

—— 静态类型检查器

isort

—— 导入语句自动排序工具

可以通过 pre-commit 钩子集成这些工具,实现代码提交前自动检查和格式化:

安装 pre-commit:

pip install pre-commit

创建.pre-commit-config.yaml文件,配置好上述工具。

运行

pre-commit install

激活钩子。

这样可以在每次提交前自动检查并格式化代码,无需手动操作就能保证代码风格统一、早期发现潜在错误。

10. 避免“裸 except”捕获所有异常

通用异常捕获会隐藏 bug,给调试带来极大困难。它会捕获所有类型的异常,包括语法错误、内存错误和强制中断。

不推荐这样写:

try:

  user_data = get_user_from_api(user_id)

  process_user_data(user_data)

  save_to_database(user_data)

except:

  # 到底哪里出错了?完全不知道!

  logger.error("Something went wrong")

这样会把所有错误(包括编程错误、系统错误、用户中断、网络超时等)都吞掉,调试极为困难。

推荐这样做:

try:

  user_data = get_user_from_api(user_id)

  process_user_data(user_data)

  save_to_database(user_data)

except ConnectionError as e:

  logger.error(f"API connection failed: {e}")

  # 处理 API 连接失败

except ValueError as e:

  logger.error(f"Invalid user data received: {e}")

  # 处理数据校验问题

except DatabaseError as e:

  logger.error(f"Database error: {e}")

  # 处理数据库相关问题

except Exception as e:

  # 最后一层兜底

  logger.critical(f"Unexpected error processing user {user_id}: {e}", exc_info=True)

  raise

只捕获你预期会处理的异常类型,并针对不同错误分别处理和记录。最外层的except Exception只作为最后的保护措施,同时用exc_info=True记录完整堆栈信息,然后考虑是否需要重新抛出。

如果确实需要“兜底”捕获所有异常,请用except Exception as e:,绝不要用裸except:,并务必记录完整异常信息。

总结

希望你能在自己的代码中实践上述技巧。将这些最佳实践融入项目后,你会发现代码变得:

更易维护

更易测试

更易理解

下次你想走捷径时,请记住:代码的阅读次数远远多于编写次数。

愿你写出越来越优雅、整洁的 Python 代码!

  • 发表于:
  • 原文链接https://page.om.qq.com/page/O2yR6wGIhDuW5zydVeOk870g0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券