目录
Python编程规范绝非对开发者个性的束缚,恰恰相反,它是保障代码质量、提升协作效率和维系项目生命力的基石。其核心重要性体现在以下几个方面:
首先,它极大地提升了代码的可读性与可维护性。 Python哲学强调“代码是写给人看的,只是顺带让机器执行”。统一的命名、一致的缩进、清晰的结构使得代码如同一本排版精美的书,任何开发者都能迅速理解其意图,而非在混乱的符号中艰难解密。这在人员更替或长期维护时至关重要,能显著降低“技术债务”和心智负担。
其次,它是团队协作的“通用语言”和“润滑剂”。 在多人参与的项目中,若没有统一的规范,每位成员的编码风格都会为项目注入不确定性,导致沟通成本急剧上升,代码评审沦为格式纠错的拉锯战。而遵循PEP 8等公认规范,相当于所有成员遵守同一份蓝图,使得协作焦点可以集中于逻辑与架构等更高层次的问题,而非琐碎的格式分歧,从而大幅提升团队整体效能。
再者,它是软件质量与可靠性的前置保障。 许多规范条目,如明确的异常处理、合理的函数长度、避免魔法数字等,本身就是长期实践总结出的最佳实践。它们能有效规避常见陷阱,减少隐藏的Bug,引导开发者写出更健壮、更安全的代码。配合pylint
、flake8
、black
等自动化工具,规范检查能被集成到开发流程中,在问题发生前就予以纠正。
最后,它体现了专业工程师的素养与职业精神。 编写符合规范的代码,是对同事、对项目、也是对自己职业的尊重。它展现了一种严谨、负责的态度,是个人技术品牌的重要组成部分,也是高级工程师与初学者的关键区别之一。
总而言之,投资于学习并践行编程规范,短期看是规则的适应,长期看则是一项回报极高的投资。它所带来的代码一致性、可维护性和团队协作效率的提升,是任何一个希望持续发展、规模化的软件项目所不可或缺的底层支撑。
根据小编在工作中的项目实践经验,以及网上各位前辈的归纳总结,整理了一份Python编程规范供大家参考使用。(规范内容如有不合理之处,请各位不吝赐教,大家共同进步~)
1.代码风格
1.1 行长度
≤120字符(注释/字符串除外)
1.2 空行
模块级函数/类之间:2行
类内方法之间:1行
1.3 库/模块导入顺序:
标准库 -> 第三方库 -> 本地模块
# 示例
import os
import sys
import numpy as np
import pandas as pd
from . import utils
from .config import settings
模块导入规范:
1.3.1 禁止使用 import *
污染命名空间:导入大量不需要的名称
可读性降低:无法追踪名称的来源
命名冲突:不同模块的同名对象会互相覆盖
破坏 IDE 的智能提示和静态分析
# 禁止
from module import *
# 推荐
import module
from module import specific_function, SpecificClass
1.3.2 禁止隐式相对导入
污染命名空间:导入大量不需要的名称
可读性降低:无法追踪名称的来源
# 禁止 (Python 2 风格)
import sibling_module
# 推荐 - 绝对导入
from package import module
from . import sibling_module # 显式相对导入
1.4 缩进
使用4个空格(禁用Tab)
1.5 模块公共接口控制
使用__all__模块级变量对当前py文件的公共接口进行管控,明确声明模块的公共 API,未声明的模块仅存在于当前文件内的调用,无法提供给外部。
备注:私有函数命名用_开头
import inspect
# 自动导出所有不以 _ 开头的函数
__all__ = [name for name, obj in inspect.getmembers(sys.modules[__name__])
if inspect.isfunction(obj) and not name.startswith('_')]
# 导出指定的函数
__all__ = ['module1', 'module2']
2. 命名规范
驼峰命名法:使用大小写字母区分单词,常用于类名、枚举、特征等类型定义
蛇形命名法:使用下划线分隔单词, 适用于变量、函数、模块名等值类型定义
命名方法 | 示例 | |
类名 | 大驼峰法(所有单词首字母大写) | DataBaseUser |
变量名 | 蛇形命名(全小写,单词通过_串联,最后单词为变量类型,如list,dict,cls等) | count_list |
函数 | 蛇形命名(全小写,单词通过_串联),尽量动词开头 | get_valid_data |
常量 | 蛇形命名,全部大写 | DATA_TYPE_MAP |
私有成员 | 蛇形命名,第一个字符为_,且所有字符均小写 | _internal_cache |
3 函数设计
单一职责原则:每个函数只做一件事
最小惊奇原则:函数行为应直观可预测
开闭原则:对扩展开放,对修改关闭
3.1 函数参数
3.1.1 函数参数顺序
# 推荐顺序
def process_data(
required_arg, # 必需位置参数
*args, # 可变位置参数,
*, # 如果存在则表示*前面的参数为位置参数(也可以指定为关键字参数),*后面的参数必 须显式地使用参数名来传递
optional_arg=None, # 可选关键字参数
**kwargs # 可变关键字参数
) -> ResultType:
pass
4.1.2 函数参数个数
理想参数个数为0-3个,最多不超过5个,超过时应考虑使用对象参数
4.1.3 函数参数类型提示
所有参数应指定类型提示,包括返回值
多返回值的情况:
# (1)使用命名元组
from collections import namedtuple
Result = namedtuple('Result', ['success', 'data', 'message'])
def processData() -> Result:
...
return Result(success=True, data=processed, message="OK")
# (2)自定义类型ServiceLengthDict = Dict[ServiceName, Dict[OperationName, LengthValue]]
UserDefine = Tuple[str, Dict[str, Union[Dict[str, str], LengthValue]], float]
4.2 文档字符串(docstring)
统一采用Google 风格文档,模板格式:
"""函数注释
Attributes:
参数 (变量类型): 参数说明
Raises:
异常类型(继承Exception类或其子类): 异常类型说明
Return:
返回值类型:返回值说明
Examples:
>>> instance = 函数名(入参)
返回值
"""
# 示例
def byte_order(data: int, msb_index: int, data_len: int, byte_order_type: str = "motorola") -> list[int]:
"""
Attributes:
data(int): data to convert
msb_index(int): bit index of MSB
data_len(int): data length
byte_order_type(str): motorola or big_endian / intel or little_endian
Returns:
big_endian list
Examples:
>>> byte_order_result = byte_order(data=12, msb_index=14, data_len=4, byte_order_type="motorola")
"""
...
4.3 lru_cache装饰器
使用@lru_cache(maxsize=None)装饰器,会自动缓存函数的计算结果,当使用相同的参数再次调用时直接返回缓存结果,避免重复计算。
5 类设计
5.1 文档字符串(docstring)
统一采用Google 风格文档,模板格式:
class DocumentStringDemo:
"""一个演示 Google 风格文档字符串用法的类。
该类展示了如何为类和方法编写规范的文档字符串,
包括参数说明、返回值说明和示例代码等。
Attributes:
name (str): 实例名称,用于标识对象。
value (int): 实例值,用于存储数值数据。
"""
def __init__(self, name, value):
"""初始化实例。
Args:
name (str): 实例名称,长度不超过20个字符。
value (int): 实例值,必须为正整数。
Raises:
ValueError: 如果value不是正整数时抛出。
"""
if not isinstance(value, int) or value <= 0:
raise ValueError("value必须是正整数")
self.name = name
self.value = value
def calculate(self, factor):
"""基于实例值和输入因子计算结果。
该方法将实例值与输入因子相乘,返回计算结果。
Args:
factor (float): 计算因子,可以是任意浮点数。
Returns:
float: 计算结果,保留2位小数。
Examples:
>>> demo = DocumentStringDemo("test", 10)
>>> demo.calculate(1.5)
15.0
"""
return round(self.value * factor, 2)
def get_info(self):
"""获取实例的描述信息。
Returns:
str: 包含实例名称和值的格式化字符串。
"""
return f"Name: {self.name}, Value: {self.value}"
5.2 类的定义方式
5.2.1 “半结构化类”或 “手动初始化数据类”
一种介于传统 Python 类和现代数据类之间的混合设计模式,通过__init__手动实现构造器,并完成类成员属性的初始化操作。
所有的初始化参数通过__init__传入,并完成类的初始化构造
5.2.2 使用dataclass进行类的修饰定义
自动构造器生成,无需手动编写 __init__,直接定义类的成员变量(包括初始化成员),禁止初始化的成员通过dataclasses.field(init=False)来禁止。
使用def __post_init__(self) 进行后初始化操作,在类的实例被初始化之后立即调用的一个特殊方法。它允许在实例创建之后执行一些额外的初始化逻辑。这对于需要在基本初始化之后进行复杂计算或设置的情况非常有用,场景有:
(1)禁止初始化的类型成员,在此过程中进行逻辑赋值
(2)规则校验(如类的实例化对象是否满足基础规则,验证属性的有效性)
Python dataclasses 详解:简化类定义与高级技巧-CSDN博客
5.3 类的成员变量设计
5.3.1 命名
公有成员:直接使用snake_case命名,无前缀。应通过属性(property)或方法控制访问,避免直接暴露内部状态。
受保护成员:单下划线前缀(_snake_case),表示仅供子类或内部使用,外部不应访问。
私有成员:双下划线前缀(__snake_case),会触发名称改写(name mangling),避免子类意外覆盖。
class DataProcessor:
# 公有变量 (外部可直接访问)
api_endpoint: str
# 受保护变量 (子类可访问,约定不直接外部访问)
_cache_size: int
# 私有变量 (仅类内访问,会触发名称改写)
__internal_counter: int = 0
# 常量 (全大写命名)
MAX_RETRIES: ClassVar[int] = 3
5.3.2 类型注解
所有成员变量必须使用类型注解(如: str, int, List[Dict]等)。
复杂类型使用typing模块(如Optional, Union, Tuple等)。
from typing import Optional, Union, TypedDict
class ConnectionConfig(TypedDict):
host: str
port: int
class NetworkClient:
# 基本类型
timeout: float
# 复合类型
endpoints: list[ConnectionConfig]
# 可选类型
fallback_server: Optional[ConnectionConfig] = None
# 联合类型
credentials: Union[str, tuple[str, str]]
# 泛型集合
error_counts: dict[str, int] = field(default_factory=dict)
5.3.3 默认值设置
简单默认值:直接在类型注解后赋值,如 count: int = 0。
可变默认值:使用dataclasses.field的default_factory,避免可变对象的共享问题。
例如:items: list[str] = field(default_factory=list)
from dataclasses import field
class UserProfile:
# 简单默认值
is_verified: bool = False
# 避免可变默认值陷阱
access_history: list[datetime] = field(default_factory=list)
# 延迟初始化
_avatar_data: bytes = field(init=False, repr=False)
def __post_init__(self):
self._avatar_data = self._load_default_avatar()
5.3.4 常量
使用全大写SCREAMING_SNAKE_CASE,并在类内直接定义。
类型注解可选,但推荐加上,如:MAX_SIZE: ClassVar[int] = 1024
5.3.5 属性(property)
用于封装成员变量的访问,添加逻辑验证或计算属性。
只读属性:使用@property装饰器,不提供setter。
读写属性:提供setter,并在setter中进行验证。
class TemperatureSensor:
# 私有变量
_current_temp: float
@property
def temperature(self) -> float:
"""当前温度(只读)"""
return self._current_temp
@property
def status(self) -> str:
"""设备状态(计算属性)"""
return "ONLINE" if self._is_active else "OFFLINE"
@temperature.setter
def temperature(self, value: float):
"""温度设置器(带验证)"""
if not -40 <= value <= 150:
raise ValueError("温度超出有效范围")
self._current_temp = value
5.3.6文档字符串
每个公有成员和受保护成员应在类文档字符串的Attributes部分说明。
私有成员可在内部注释中说明。
5.4 类的成员方法设计
5.4.1 参数和返回值
所有参数和返回值必须使用类型注解。
参数命名:snake_case,避免单个字符(除非是数学中的常规用法,如x,y)。
默认参数:不可变类型可直接设置默认值,可变类型使用None,并在函数内初始化。
5.4.2 文档字符串(Google风格)
简要说明:单行概述功能。
详细说明:可选,多行描述,同Google风格文档。
5.4.3 方法设计原则
单一职责:一个方法只做一件事。
方法长度:尽量不超过50行,过长应考虑拆分。
5.4.4 静态方法和类方法
静态方法(@staticmethod):与类相关但不需要访问实例或类状态的方法。通常用于工具函数。
类方法(@classmethod):用于操作类状态(类变量)或创建替代构造函数。
5.4.5 私有方法
仅在类内部使用,外部不应调用。
命名以单下划线_开头,但不会触发名称改写。
可在文档字符串中简单说明,但不必在类文档中公开。
6 存根文件(.pyi)
存根文件(.pyi) 是Python用于定义接口类型但不包含具体实现的特殊文件。它提供了一种独立于实现的类型定义方式,核心特点:
- 纯接口声明:只包含函数签名、类结构和变量类型注释
- 运行时忽略:Python解释器不会加载执行.pyi文件
- 类型检查器专用:供mypy、pyright等工具执行类型检查
- 三斜杠占位:使用...替代具体实现代码
Python 存根文件(.pyi)详解:类型提示的高级指南-CSDN博客
以socket类为例:
class socket(_socket.socket):
def __init__(
self, family: AddressFamily | int = -1, type: SocketKind | int = -1, proto: int = -1, fileno: int | None = None
) -> None: ...
def __enter__(self) -> Self: ...
def __exit__(self, *args: Unused) -> None: ...
def dup(self) -> Self: ... # noqa: F811
def accept(self) -> tuple[socket, _RetAddress]: ...
# Note that the makefile's documented windows-specific behavior is not represented
# mode strings with duplicates are intentionally excluded
@overload
def makefile(
self,
mode: Literal["b", "rb", "br", "wb", "bw", "rwb", "rbw", "wrb", "wbr", "brw", "bwr"],
buffering: Literal[0],
*,
encoding: str | None = None,
errors: str | None = None,
newline: str | None = None,
) -> SocketIO: ...
@overload
def makefile(
self,
mode: Literal["rwb", "rbw", "wrb", "wbr", "brw", "bwr"],
buffering: Literal[-1, 1] | None = None,
*,
encoding: str | None = None,
errors: str | None = None,
newline: str | None = None,
) -> BufferedRWPair: ...
@overload
def makefile(
self,
mode: Literal["rb", "br"],
buffering: Literal[-1, 1] | None = None,
*,
encoding: str | None = None,
errors: str | None = None,
newline: str | None = None,
) -> BufferedReader: ...
@overload
def makefile(
self,
mode: Literal["wb", "bw"],
buffering: Literal[-1, 1] | None = None,
*,
encoding: str | None = None,
errors: str | None = None,
newline: str | None = None,
) -> BufferedWriter: ...
@overload
def makefile(
self,
mode: Literal["b", "rb", "br", "wb", "bw", "rwb", "rbw", "wrb", "wbr", "brw", "bwr"],
buffering: int,
*,
encoding: str | None = None,
errors: str | None = None,
newline: str | None = None,
) -> IOBase: ...
@overload
def makefile(
self,
mode: Literal["r", "w", "rw", "wr", ""] = "r",
buffering: int | None = None,
*,
encoding: str | None = None,
errors: str | None = None,
newline: str | None = None,
) -> TextIOWrapper: ...
def sendfile(self, file: _SendableFile, offset: int = 0, count: int | None = None) -> int: ...
@property
def family(self) -> AddressFamily: ...
@property
def type(self) -> SocketKind: ...
def get_inheritable(self) -> bool: ...
def set_inheritable(self, inheritable: bool) -> None: ...
7 模块与包
7.1 模块
一个 .py 文件就是一个模块
模块名就是文件名(不含 .py 后缀)
模块可以包含函数、类、变量和可执行代码
7.2 包
包含 __init__.py 文件的目录
可以包含多个模块和子包
用于组织相关模块的层次结构
包的结构:
my_package/
├── __init__.py # 可以理解为包的标志性文件,一个文件夹下有此文件,即此文件夹即可以作为一个包被导入
├── module_a.py
├── module_b.py
└── sub_package/
├── __init__.py
└── module_c.py
7.3 包的导入方式
7.3.1 相对导入
相对导入只能在包内使用(即包含__init__.py
的目录)
假设有一个项目结构如下:
my_project/
├── package/
│ ├── __init__.py
│ ├── module_a.py
│ ├── module_b.py
│ └── subpackage/
│ ├── __init__.py
│ └── module_c.py
└── script.py
在module_a.py
中导入同级的module_b.py
:
from . import module_b
在module_a.py
中导入子包subpackage
中的module_c.py
:
from .subpackage import module_c
在subpackage/module_c.py
中导入父级目录的module_a.py
:
from .. import module_a
7.3.2 动态导入
from importlib import import_module
_NAME_TO_MODULE = {
# Com_Arxml_Extraction 模块的属性
"func1": ".Module",
"func2": ".Module",
"func3": ".Module",
"func4": ".Module",
"func5": ".Module"
}
__all__ = list(_NAME_TO_MODULE.keys())
def __getattr__(name: str):
if name not in _NAME_TO_MODULE:
raise AttributeError(f"模块 {__name__} 没有属性 {name}")
try:
module = import_module(_NAME_TO_MODULE[name], package=__package__)
except ImportError as e:
raise ImportError(f"无法导入子模块 {_NAME_TO_MODULE[name]},请检查路径或依赖:{e}") from e
try:
return getattr(module, name)
except AttributeError as e:
raise AttributeError(f"子模块 {module.__name__} 中未找到属性 {name}") from e
8 Path库
涉及文件系统路径操作,尽可能使用Path
Python中的pathlib和Path(面向对象的文件系统路径操作库)-CSDN博客