Python 的最佳实践林林总总,数量可达数百甚至数千条,每位开发者都有自己的经验与见解。
本文为你精心梳理了50 条经得住时间考验的Google Python 编码规范—— 它们的价值不会因技术趋势迭代而衰减,既是区分专业开发者与业余爱好者的核心标准,其中多数原则更可跨语言复用,将为你的代码质量保驾护航。
注释的初衷是向其他开发者传递代码意图,但实际使用中常引发问题:
1.信息滞后:代码迭代后,注释若未同步更新,会误导后续开发者;
2.干扰阅读:过多注释会割裂代码逻辑,降低可读性;
3.弱化思考:读者易依赖文字说明,忽略对代码本身逻辑的理解。
因此,应优先通过清晰的代码结构、直观的命名、合理的抽象让代码“自解释”。例如,用calculate_user_total_score()替代calc(),无需注释即可明确功能。
当然,注释并非完全无用—— 法则14、15将详细说明“该用”与“不该用”注释的场景。
部分开发者习惯用user_name_str、user_age_int这类命名标注变量类型,但多数情况下完全多余:
4.若变量名本身可推断类型(如user_name自然指向字符串,user_age默认是整数),额外添加_str/_int只会让命名冗长;
5.若类型无法通过名称推断(如复杂自定义对象),用注释说明类型更灵活,还能保持变量名简洁。
# ❌冗余:类型可通过名称推断 user_name_str = "John Doe" user_age_int = 30 # ✅简洁:命名已隐含类型 user_name = "John Doe" user_age = 30 |
类代表“一类对象或概念”,包含属性(特征)与方法(行为),因此类名必须是名词,且需准确反映其核心职责:
6.例如Goat类代表“山羊”,包含horn_length(属性)与get_horn_length()(方法);
7.避免用动词或冗余前缀命名,Goat.get_horn_length()比GetGoat.get_horn_length()更符合面向对象直觉,可读性更高。
函数的核心是“执行操作或行为”,命名需直接体现其功能,减少对注释的依赖:
8.用process_user_data()、save_order_to_db()替代user_data()、order_db();
9.复杂操作可加修饰词,如process_and_validate_user_input(),清晰说明操作步骤。
示例:
# ✅动词短语命名,功能一目了然 def process_and_save_user_data(input_data): # 处理并保存用户数据的逻辑 ... |
函数签名(参数+ 返回值定义)是代码可读性的关键,需明确标注类型:
10.提升可读性:开发者无需查看函数内部,通过签名即可知道“需传入什么类型参数”“会返回什么结果”;
11.减少错误:IDE(如PyCharm、VSCode)可基于类型标注做静态检查,提前发现类型不匹配问题;
12.降低协作成本:团队成员无需反复沟通,直接通过签名理解函数用法。
# ❌模糊:无法确定num类型与返回值类型 def convert_to_string(num): return "My new string is " + str(num) # ✅清晰:明确参数为int,返回值为str def convert_to_string(num: int) -> str: return "My new string is " + str(num) |
函数设计需遵循“单一职责原则(SRP)”—— 每个函数仅负责一项具体任务,不混杂多个业务逻辑:
13.易理解:单一功能的函数逻辑更简单,阅读时无需兼顾多任务;
14.易测试:可独立验证函数是否正确完成目标,无需考虑其他关联逻辑;
15.易维护:修改时仅影响当前函数,不会引发连锁反应;
16.高复用:专注单一任务的函数,可在更多场景中复用。
反例与正例对比:
# ❌违规:同时做“地址验证”和“经纬度获取” def check_if_address_is_valid(address): if address.is_valid: latitude = address.get_latitude() longitude = address.get_longitude() return (latitude, longitude) # ✅合规:拆分为3个单一功能函数 def check_if_address_is_valid(address) -> bool: # 仅验证地址有效性 return address.is_valid def get_latitude(address) -> float: # 仅获取纬度 return address.get_latitude() def get_longitude(address) -> float: # 仅获取经度 return address.get_longitude() |
判断函数是否“单一职责”的关键:检查是否同时处理不同抽象层次的任务。例如,“处理订单”(高抽象)与 “拼接 SQL 语句”(低抽象)混在一个函数中,就需拆分(见法则7)。
抽象层次是衡量代码复杂性的核心概念:
17.高抽象层次:对应业务目标,如 “处理订单”“更新用户信息”,不涉及具体实现;
18.低抽象层次:对应具体操作,如 “从数据库读数据”“计算折扣金额”,是实现高抽象的步骤。
函数若混合不同抽象层次,会导致职责模糊、可读性骤降。例如:
# ❌违规:混合高、中、低3个抽象层次 def calculate_average(): numbers = get_numbers() # 高抽象:获取数据(不关心如何获取) numbers_plus_30 = [num + 30 for num in numbers] # 中抽象:数据转换 total = sum(numbers_plus_30) # 低抽象:计算总和 count = len(numbers) # 低抽象:统计数量 average = total / count # 低抽象:计算平均值 return average # ✅合规:仅包含低抽象层次 def calculate_average(numbers: list[float]) -> float: total = sum(numbers) # 低抽象:计算总和 count = len(numbers) # 低抽象:统计数量 average = total / count # 低抽象:计算平均值 return average |
函数名与参数应像“兄弟姐妹”一样紧密配合,看到命名就能推断功能,无需查看实现:
19.反例:write(True)——True无法说明 “写什么”,开发者需猜 “是写日志?还是写配置?”;
20.正例:write_user_name(name="John")——函数名+ 参数明确“写入用户名”,逻辑一目了然。
函数的核心价值是“复用”与“可维护”,体积越大,复用性越低、维护成本越高:
21.建议单个函数代码量控制在20 行以内(不含注释);
22.若超过限制,优先拆分:例如将“数据验证→数据转换→数据存储” 拆分为 3 个小函数。
冗余词语会让命名模糊,增加理解成本。例如:
23.反例:getProfile()、getProfileInfo()、getProfileInfos()——无法区分“获取个人资料”“获取资料详情”“获取多份资料”;
24.正例:用具体修饰词明确差异,get_user_profile()(获取用户个人资料)、get_user_account()(获取用户账户信息),无需额外解释。
开闭原则是面向对象设计的基石—— 代码应支持“新增功能时扩展代码,而非修改已有逻辑”,避免修改引发未知 bug。
# ❌违规:新增国家需修改get_capital()方法 class Address: def __init__(self, country: str): self.country = country def get_capital(self) -> str: if self.country == 'canada': return "Ottawa" if self.country == 'nigeria': return "Abuja" # 新增国家需加新if语句,修改已有代码 |
用字典存储“国家- 首都”映射,新增国家只需扩展字典,无需修改类:
# 扩展时仅需新增键值对,无需修改Address类 capitals = { 'canada': "Ottawa", 'nigeria': "Abuja", 'america': "Washington D.C", 'united kingdom': "London" } class Address: def __init__(self, country: str): self.country = country def get_capital(self) -> str: return capitals.get(self.country, "Capital not found") |
支付方式可能新增(如从“借记卡/ 信用卡”扩展到“加密货币/ PayPal”),需通过抽象类+ 子类实现扩展:
from abc import ABC, abstractmethod # 抽象父类:定义支付接口,不包含具体实现 class PaymentProcessor(ABC): @abstractmethod def pay(self, amount: float) -> None: pass # 子类1:借记卡支付(扩展时新增子类) class DebitCardPayment(PaymentProcessor): def pay(self, amount: float) -> None: print(f"借记卡支付:{amount}元") # 子类2:信用卡支付(扩展时新增子类) class CreditCardPayment(PaymentProcessor): def pay(self, amount: float) -> None: print(f"信用卡支付:{amount}元") # 新增加密货币支付:仅需加子类,无需修改父类 class CryptoPayment(PaymentProcessor): def pay(self, amount: float) -> None: print(f"加密货币支付:{amount}元") |
里氏替换原则要求:若程序依赖父类,那么用任意子类替换父类后,程序逻辑仍能正常运行,不会出现异常。
例如,Bird类有fly()方法,Sparrow(麻雀)作为子类可正常实现fly();但Penguin(企鹅)作为子类无法飞行,若强行实现fly()(如抛出异常),则违反LSP—— 用Penguin替换Bird后,调用fly()会报错。
正确做法:拆分父类,FlyingBird(会飞的鸟)包含fly(),Penguin继承Bird而非FlyingBird,确保子类可安全替换父类。
类的“大小”不取决于代码行数,而取决于职责数量:
25.若类名模糊(如UserHandler),或包含“用户信息管理+ 订单处理+ 日志记录”等多职责,即使代码短,也是“大而杂”的类;
26.遵循单一职责原则(SRP):一个类仅负责一项核心任务,例如UserProfileManager(用户资料管理)、OrderProcessor(订单处理),职责清晰易维护。
实例变量存储类的状态,数量过多意味着类承担过多职责:
27.例如,User类若包含name/age(用户基本信息)、order_history(订单记录)、login_logs(登录日志),则实例变量冗余,状态管理复杂;
28.优化方案:拆分变量到对应类,User仅存name/age,Order存订单信息,LoginLog存日志,通过关联(如User包含order_ids列表)实现数据关联。
凝聚力指类内部属性与方法的关联程度—— 高凝聚力的类,所有属性和方法都围绕“同一目标”:
29.反例:User类包含calculate_order_discount()(计算订单折扣),属性与方法无直接关联,凝聚力低;
30.正例:Rectangle类包含width/height(属性)和calculate_area()/calculate_perimeter()(方法),所有成员都服务于 “矩形计算”,凝聚力高。
# ✅高凝聚力:属性与方法强关联 class Rectangle: def __init__(self, width: float, height: float): self.width = width self.height = height def calculate_area(self) -> float: return self.width * self.height def calculate_perimeter(self) -> float: return 2 * (self.width + self.height) |
异常处理是代码健壮性的核心,但需“按需使用”:
31.必用场景:可能发生不可控错误的操作(如文件读写、API请求、数据库连接),需用try-catch捕获异常,finally做清理(如关闭文件、释放连接);
32.不用场景:简单逻辑(如整数加减、列表取值),不会抛出异常,无需加try-catch,避免代码冗余。
示例:安全的文件读取
try: file = open("data.txt", "r") data = file.read() # 可能抛出FileNotFoundError except FileNotFoundError: print("错误:文件不存在,请检查路径!") finally: file.close() # 无论是否报错,确保文件关闭 |
捕获异常时,仅输出“出错了”无法定位问题,需补充上下文:
33.说明“在哪出错”(如 “读取用户数据文件时”);
34.携带原始异常信息(如{e}),便于调试。
# ❌模糊:无上下文,无法定位问题 try: file = open("data.txt", "r") data = file.read() except Exception as e: print("出错了") # ✅清晰:包含场景与原始异常 try: file = open("data.txt", "r") data = file.read() except Exception as e: print(f"读取data.txt文件时出错:{e}") # 示例输出:读取data.txt文件时出错:[Errno 2] No such file or directory: 'data.txt' |
无需为每种异常单独写except块,多数情况用“通用异常(Exception)” 处理,特殊异常单独捕获:
35.特殊异常(如FileNotFoundError)需定制处理(如提示检查路径),放前面;
36.其他异常用通用except捕获,减少代码冗余。
# ❌冗余:多异常类重复捕获 try: file = open("data.txt", "r") data = file.read() except ValueError: pass except TypeError: pass except FileNotFoundError: print("文件不存在!") # ✅简洁:特殊+通用结合 try: file = open("data.txt", "r") data = file.read() except FileNotFoundError: print("文件不存在,请检查路径!") # 特殊处理 except Exception as e: print(f"其他错误:{e}") # 通用处理 |
assert(断言)仅适合开发调试,不适合生产环境的数据验证:
37.assert可通过-O(优化模式)禁用,导致验证逻辑失效;
38.生产环境需用raise显式抛出异常(如ValueError),确保验证不被跳过。
# ❌危险:生产环境可能失效 assert x > 0, "x应该为正数" # ✅安全:显式抛出异常,必执行 if x <= 0: raise ValueError("x应该为正数") |
源文件过长会导致:
39.查找代码困难(需频繁滚动);
40.职责混杂(一个文件包含多个类/ 函数,逻辑混乱)。
建议:
41.单个文件代码量控制在100-200 行,最多不超过500 行;
42.超过限制时拆分文件,如将user_manage.py拆分为user_profile.py、user_login.py。
空行是“视觉分隔符”,可清晰划分代码逻辑段落:
43.函数内:不同功能步骤用空行分隔(如“数据获取→数据处理→结果返回”);
44.函数间/ 类间:用1-2 个空行分隔,避免代码拥挤。
# ❌拥挤:无空行,逻辑不分段 def calculate(a, b): sum_result = a + b print("总和:", sum_result) diff_result = a - b print("差值:", diff_result) # ✅清晰:空行分隔“计算和”与“计算差” def calculate(a, b): sum_result = a + b print("总和:", sum_result) diff_result = a - b print("差值:", diff_result) |
空格的作用是“区分代码元素的关联程度”:
45.无空格:表示强关联,如函数名与参数(def create(name))、运算符与操作数(x+1);
46.有空格:表示弱关联,如参数之间(greet(first_name, last_name))、关键字与表达式(if x > 0)。
反例与正例:
# ❌混乱:关联强弱不清晰 def create (name): # 函数名与参数不该有空格 print (name) # 函数与参数不该有空格 # ✅规范:关联强弱明确 def create(name): # 无空格:强关联 print(name) # 无空格:强关联 def greet(first_name, last_name): # 有空格:参数间弱关联 if first_name == "John" and last_name == "Doe": # 有空格:关键字与表达式弱关联 print("Hello, John!") |
团队协作中,“统一风格” 比 “个人习惯”更重要:
47.若团队要求“函数名用蛇形命名(calculate_sum)”,就不要用驼峰命名(calculateSum);
48.若团队要求“缩进用4 个空格”,就不要用 Tab。
统一风格可避免“读别人代码像读外语”,提升协作效率。示例:
# ❌违规:团队要求蛇形命名,却用驼峰 def calculateSum(a, b): return a + b # ✅合规:遵循团队蛇形命名 def calculate_sum(a, b): return a + b |
“魔数”是指直接写在代码中的无意义数值(如50、365),存在两大问题:
49.难理解:开发者不知道50是“最近订单数”还是“分页大小”;
50.难维护:需修改时,要在代码中逐个查找,易遗漏。
解决方案:将魔数定义为常量,用大写字母+ 下划线命名(如NUM_OF_RECENT_ORDERS)。
# ❌魔数:50的含义不明确 def get_recent_orders(): return db.query("SELECT TOP 50 * FROM orders") # ✅常量:含义清晰,易维护 NUM_OF_RECENT_ORDERS = 50 # 常量:最近订单数量 def get_recent_orders(): return db.query(f"SELECT TOP {NUM_OF_RECENT_ORDERS} * FROM orders") |
嵌套层级越多(如if套if套for),代码逻辑越复杂,可读性越低。优化方法:
51.早期返回:满足条件时直接return,减少嵌套;
52.合并条件:将if x: if y:合并为if x and y:;
53.拆分函数:将嵌套内的复杂逻辑拆为小函数。
示例:
# ❌深层嵌套:2层if,逻辑绕 def check_user(user): if user.is_active: if user.has_permission("edit"): return True return False # ✅优化:合并条件+早期返回,无嵌套 def check_user(user): if not user.is_active or not user.has_permission("edit"): return False return True |
晦涩缩写(如calc、usr、qty)会增加理解成本,命名应使用完整单词:
54.用calculate替代calc;
55.用user替代usr;
56.用quantity替代qty。
# ❌晦涩:calc、x、y含义不明 def calc(x, y): pass # ✅清晰:完整单词,功能明确 def calculate_total_price(quantity, unit_price): return quantity * unit_price |
硬编码路径(如/path/to/file.txt)会导致 “环境不兼容”:
57.开发者A 的电脑路径是C:/data/file.txt,开发者 B 的是D:/docs/file.txt,代码运行报错;
58.解决方案:用环境变量(如os.getenv("FILE_PATH"))或配置文件管理路径,适配不同环境。
# ❌硬编码:环境不兼容 file_path = "/path/to/file.txt" # ✅环境变量:适配不同环境 import os file_path = os.getenv("FILE_PATH") # 从环境变量读取路径 |
文件、数据库连接等资源需手动关闭,若忘记或报错中断,会导致资源泄漏。with语句可“自动管理资源”——代码块结束后,无论是否报错,都会释放资源。
# ❌危险:可能忘记close(),导致文件泄漏 file = open("example.txt", "r") data = file.read() file.close() # 若中间报错,此句不执行 # ✅安全:with自动关闭文件 with open("example.txt", "r") as file: data = file.read() # 代码块结束,file自动关闭 |
三元表达式适合简单判断(如result = "even" if x%2==0 else "odd"),复杂逻辑需用if-elif-else,避免可读性下降。
# ❌复杂:嵌套三元,逻辑绕 result = "even" if number % 2 == 0 else "odd" if number % 3 == 0 else "neither" # ✅清晰:if-elif,逻辑直观 if number % 2 == 0: result = "even" elif number % 3 == 0: result = "odd" else: result = "neither" |
Python 中==判断“值是否相等”,is判断 “是否引用同一个对象”(内存地址相同):
59.不可变类型(如 str、int):==与is结果通常一致(Python优化共享内存),用==更符合直觉;
60.可变类型(如 list、dict):即使值相同,也可能是不同对象,用is判断是否为“同一个对象”。
示例:
# 可变类型:list list1 = [1, 2, 3] list2 = [1, 2, 3] # ❌错误:用==判断是否为同一个对象(实际是不同对象) if list1 == list2: print("同一个对象") # 不会打印,因为==仅判断值相等 # ✅正确:用is判断是否为同一个对象 if list1 is list2: print("同一个对象") # 不会打印,list1和list2是不同对象 |
依赖倒置原则要求:
61.高级模块(如Calculator)不依赖低级模块(如FileLogger);
62.两者都依赖抽象(如LoggerInterface)。
这样可降低耦合,便于扩展—— 例如,将“文件日志”改为“数据库日志”,只需新增DBLogger类,无需修改Calculator。
# 低级模块:文件日志 class FileLogger: def log(self, message: str): with open("log.txt", "a") as f: f.write(message + "\n") # 高级模块:计算器,直接依赖FileLogger(具体实现) class Calculator: def __init__(self): self.logger = FileLogger() # 依赖具体,无法替换为其他日志 def add(self, x: int, y: int) -> int: result = x + y self.logger.log(f"计算:{x}+{y}={result}") return result |
from abc import ABC, abstractmethod # 抽象接口:日志抽象 class LoggerInterface(ABC): @abstractmethod def log(self, message: str) -> None: pass # 低级模块1:文件日志(实现抽象) class FileLogger(LoggerInterface): def log(self, message: str): with open("log.txt", "a") as f: f.write(message + "\n") # 低级模块2:数据库日志(新增扩展,无需修改其他代码) class DBLogger(LoggerInterface): def log(self, message: str): # 数据库日志逻辑 pass # 高级模块:计算器,依赖抽象(LoggerInterface) class Calculator: # 通过构造函数注入抽象,不依赖具体实现 def __init__(self, logger: LoggerInterface): self.logger = logger def add(self, x: int, y: int) -> int: result = x + y self.logger.log(f"计算:{x}+{y}={result}") return result # 使用时可灵活切换日志实现 calc1 = Calculator(FileLogger()) # 文件日志 calc2 = Calculator(DBLogger()) # 数据库日志 |
DRY(Don't Repeat Yourself)原则要求:避免重复编写相同逻辑,将公共代码提取到函数/ 类中复用—— 修改时只需改一处,减少错误。
# ❌重复:两个函数逻辑完全相同 def calculate_book_price(quantity: int, price: float) -> float: return quantity * price def calculate_laptop_price(quantity: int, price: float) -> float: return quantity * price # ✅复用:提取公共函数 def calculate_product_price(quantity: int, price: float) -> float: # 公共逻辑:计算商品总价 return quantity * price # 调用公共函数,避免重复 book_total = calculate_product_price(2, 50.0) laptop_total = calculate_product_price(1, 5000.0) |
PEP 8 是Python 官方编码规范,是团队协作的“通用语言”,核心要点:
1.命名风格:
1.变量/ 函数/ 模块:蛇形命名(user_name、calculate_sum);
2.类:驼峰命名(UserProfile、OrderProcessor)。
1.缩进:用 4 个空格,不用 Tab(避免不同编辑器显示差异)。
2.行长度:每行不超过 79 个字符,长代码换行时 “运算符放开头”。
3.导入:仅导入需要的符号,不使用from module import *。
示例:符合PEP 8 的代码
# 导入:仅导入需要的函数 from math import sqrt, pow # 类:驼峰命名 class CircleCalculator: # 函数:蛇形命名,参数间有空格 def calculate_area(self, radius: float) -> float: # 长表达式换行:运算符放开头,缩进对齐 area = 3.14159 * pow(radius, 2) \ + sqrt(radius) # 示例:实际无需换行,仅演示格式 return area |
迪米特法则(最少知识原则)要求:一个类仅与“直接朋友”(直接依赖的类 / 方法)交互,不深入 “朋友的朋友” 的内部结构 —— 降低耦合,提升灵活性。
# 朋友的朋友:Profile是Customer的内部类 class Profile: def __init__(self, name: str): self.name = name def get_name(self) -> str: return self.name class Customer: def __init__(self): self.profile = Profile("John") # Customer包含Profile def get_profile(self) -> Profile: return self.profile # Order类直接访问Customer的Profile(朋友的朋友),耦合过高 class Order: def __init__(self, customer: Customer): self.customer = customer def get_customer_name(self) -> str: # 违规:Order深入Customer的内部结构(调用get_profile()) return self.customer.get_profile().get_name() |
class Profile: def __init__(self, name: str): self.name = name def get_name(self) -> str: return self.name class Customer: def __init__(self): self.profile = Profile("John") # 新增直接方法,隐藏内部结构 def get_name(self) -> str: return self.profile.get_name() class Order: def __init__(self, customer: Customer): self.customer = customer # 仅与Customer交互(直接朋友),不依赖Profile def get_customer_name(self) -> str: return self.customer.get_name() |
代码的首要目标是“让人理解”,其次才是 “简洁”:
4.计算机不会抱怨代码长,但开发者会抱怨“看不懂”;
5.避免用“炫技式简洁”(如一行复杂推导式),优先用清晰的分步逻辑。
示例:
# ❌简洁但晦涩:a、b含义不明,逻辑难理解 def add(a, b): return a + b # ✅可读:参数名与逻辑分步,一目了然 def calculate_total_price(price_per_unit: float, quantity: int) -> float: # 计算商品总价:单价 × 数量 total = price_per_unit * quantity return total |
导入模块时需“按需导入”,避免from module import *:
6.问题1:无法追踪符号来源(不知道func来自哪个模块);
7.问题2:命名冲突(多个模块有func,后导入的覆盖先导入的);
8.问题3:IDE提示混乱(补全列表充斥无用符号)。
# ❌危险:导入所有符号,易冲突 from some_module import * # ✅安全:仅导入需要的符号 from some_module import process_data, UserValidator |
法则44 指出,所有编码规范的本质是“简约设计”——用最简洁的方式实现功能,同时确保可维护性,核心要点:
1.通过所有测试:用单元测试、集成测试验证代码正确性,覆盖正常/ 异常/ 边界场景;
2.无重复代码:提取公共逻辑,避免复制粘贴;
3.清晰表达意图:用直观命名、合理结构让代码自解释;
4.最小化类与方法数量:仅创建必要的类和方法,避免冗余抽象。
最后,法则47 强调:新手应严格遵循这些规范,积累经验后再灵活调整—— 规范不是束缚,而是提升代码质量的“脚手架”。
作者:场长
本篇文章为 @ 场长 创作并授权 21CTO 发布,未经许可,请勿转载。
内容授权事宜请您联系 webmaster@21cto.com或关注 21CTO 公众号。
该文观点仅代表作者本人,21CTO 平台仅提供信息存储空间服务。
请扫描二维码,使用微信支付哦。