Python编程中需要避免的21个代码反模式实战详解
作者:Python资讯站
1.print 语句里手动四舍五入
错误示范:
value = 3.1415926 print(round(value, 2)) # 这样写并不会报错,但有更好的方式
为什么不推荐?
这种做法没错,但 Python 其实提供了更优雅的方法:
更好的写法:
print(f"{value:.2f}") # 格式化字符串,简洁又直观
{:.2f} 代表保留两位小数,既美观又避免不必要的 round() 调用。
2.频繁在 NumPy 和普通列表之间转换
错误示范:
import numpy as np data = [1, 2, 3, 4] data_np = np.array(data) max_value = max(data_np) # 这里用了 Python 内置的 max,而不是 NumPy 的
为什么是坑?
NumPy 主要用于高效的数值计算,它的"numpy.max()“速度远快于 Python 内置的"max()”,但上面的代码却使用了后者,导致性能下降。
更好的写法:
max_value = np.max(data_np) # 充分利用 NumPy 的优化
在数据分析中,最好一开始就决定用 NumPy 或 Pandas,并始终保持一致,而不是来回转换。
3.用字符串操作文件路径
错误示范:
filename = "C:\\Users\\Admin\\Documents\\file.txt" folder = "C:\\Users\\Admin\\Documents" path = folder + "\\" + "file.txt" # 这样拼接路径很容易出错
为什么不推荐?
直接用字符串拼接路径,不仅代码不优雅,而且不同系统的路径分隔符不同(Windows 用 \,Linux 和 macOS 用 /),这可能导致跨平台问题。
更好的写法:
from pathlib
import Path
folder = Path("C:/Users/Admin/Documents")
path = folder / "file.txt" # 使用 Pathlib 更安全、可读性更高
"Pathlib"让路径操作更直观,能自动适配不同系统的路径格式。
4.写 IO 函数时只支持文件路径
错误示范:
def save_data(path, data): with open(path, "w") as f: f.write(data)
为什么是坑?
上面的函数只支持文件路径,用户无法传入其他类型的 IO 对象,比如"StringIO"(内存中的文件),或者网络流。
更好的写法:
def save_data(file_obj, data): file_obj.write(data)
这样,用户既可以传"open(“file.txt”, “w”)“,也可以传"io.StringIO()”,更加灵活。
5.用"+"号拼接字符串
错误示范:
result = "" for i in range(100): result += str(i) # 每次拼接都会创建新字符串,效率极低
为什么是坑?
Python 的字符串是不可变对象,每次"+"拼接都会生成新字符串,导致性能问题。
更好的写法:
from io import StringIO buffer = StringIO() for i in range(100): buffer.write(str(i)) result = buffer.getvalue() # 这样不会频繁创建新字符串
或者:
result = "".join(str(i) for i in range(100)) # 使用 join 拼接更高效
6.用"eval()"解析字符串
错误示范:
data = "{'name': 'Alice', 'age': 25}"
parsed = eval(data) # 有安全风险!
为什么是坑?
"eval()"可能执行恶意代码,比如:
evil_data = "__import__('os').system('rm -rf /')"
eval(evil_data) # 可能导致灾难性后果!
更好的写法:
import json
parsed = json.loads(data.replace("'", '"')) # 更安全的解析方式
"json.loads()"只能解析 JSON,不会执行恶意代码。
7.依赖全局变量存储函数输入/输出
错误示范:
result = None # 全局变量 def compute(x, y): global result result = x + y # 改变全局变量,增加了函数的副作用
为什么不推荐?
全局变量会导致代码难以维护,函数变得不纯(即依赖外部状态),可能出现意想不到的 Bug。
更好的写法:
def compute(x, y): return x + y # 让函数返回值,而不是改写全局变量
这样,"compute()"不会影响外部变量,调用时更安全:
result = compute(3, 5)
8.误以为"and"和"or"只返回布尔值
错误示范:
result = 5 or 10 # 你以为 result 是 True?错,它是 5
为什么是坑?
在 Python 中,“or"和"and"不一定返回"True"或"False”,而是返回"第一个确定结果的值":
print(5 or 10) # 输出 5,因为 5 是真值,or 不再继续判断 print(0 or 10) # 输出 10,因为 0 是假值,or 继续检查 10 print(5 and 10) # 输出 10,因为 and 需要所有条件都为真 print(0 and 10) # 输出 0,因为 and 发现 0 是假值,直接返回
如何避免?
如果你只是想得到"True"或"False",请用"bool()":
is_valid = bool(5 or 10) # 这样才是标准布尔值
9.变量名全是单个字母
错误示范:
def calc(a, b, c): d = a * b + c return d
为什么是坑?
这种写法让代码难以阅读,别人(包括你自己)以后再看时,完全不知道 a、b 和 c 代表什么。
更好的写法:
def calc(price, quantity, discount): total = price * quantity + discount return total
变量名有意义,代码可读性就会大大提升!
10.“div"和"mod"分开计算,而不是用"divmod()”
错误示范:
quotient = 17 // 5 remainder = 17 % 5
为什么是坑?
Python 早就提供了"divmod()",可以一次性得到商和余数:
quotient, remainder = divmod(17, 5) # 代码更简洁
这不仅减少了计算次数,也让代码更 Pythonic。
11.不知道"@property",还在写 Getter/Setter
错误示范:
class Person: def __init__(self, name): self._name = name def get_name(self): return self._name def set_name(self, value): self._name = value
为什么是坑?
在 Python 里,属性访问应该尽量像直接访问变量那样自然。
更好的写法:
class Person: def __init__(self, name): self._name = name @property def name(self): return self._name @name.setter def name(self, value): self._name = value
现在,我们可以这样用:
p = Person("Alice")
print(p.name) # 直接访问,像变量一样
p.name = "Bob" # 直接赋值
这样写既符合 Python 风格,又方便后续扩展(比如添加数据验证)。
12.误把“属性”当“变量”,导致性能问题
错误示范:
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
print("Calculating area...") # 这行会频繁执行
return 3.14 * self.radius ** 2
c = Circle(10) print(c.area) # 每次访问都计算一次面积 print(c.area) # 计算了两次,浪费性能
为什么是坑?
有些计算量较大的属性(如"area"),不应该每次访问都重新计算。
更好的写法:
from functools
import cached_property
class Circle:
def __init__(self, radius):
self.radius = radius
@cached_property
def area(self):
print("Calculating area...") # 只计算一次
return 3.14 * self.radius ** 2
这样"area"只会在第一次访问时计算,之后直接返回缓存值,提升性能。
13.在遍历列表时修改它
错误示范:
numbers = [1, 2, 3, 4, 5] for num in numbers: if num % 2 == 0: numbers.remove(num)
为什么是坑?
遍历时修改列表,可能会跳过一些元素。例如:
numbers = [1, 2, 4, 5] for num in numbers: if num % 2 == 0: numbers.remove(num) print(numbers) # 结果为 [1, 4,5](漏删 4)
更好的写法:
numbers = [1, 2, 3, 4, 5] numbers = [num for num in numbers if num % 2 != 0] # 直接用列表推导式 print(numbers) # [1, 3, 5]
或者,先复制一份列表:
for num in numbers[:]: if num % 2 == 0: numbers.remove(num)
14.滥用"map()“和"filter()”
错误示范:
numbers = [1, 2, 3, 4, 5] squared = list(map(lambda x: x ** 2, numbers)) evens = list(filter(lambda x: x % 2 == 0, numbers))
为什么是坑?
虽然"map()"和"filter()"没错,但 Python 里有更好的方式——列表推导式。
更好的写法:
squared = [x ** 2 for x in numbers] # 代码更简洁 evens = [x for x in numbers if x % 2 == 0] # 也更易读
这样写既直观又符合 Pythonic 风格。
15.乱用 dunder 方法(魔法方法)
错误示范:
class Person:
def __init__(self, name):
self.name = name
def __iadd__(self, other):
print(f"{self.name} 和 {other.name} 成为了朋友!")
return self
p1 = Person("Alice")
p2 = Person("Bob")
p1 += p2 # 这真的合适吗??
为什么是坑?
Dunder 方法(即双下划线方法)应该遵循 Python 语言的预期行为,比如 __add__() 代表加法,而 __iadd__()(+=)本该用于数值运算。但这里却用它来实现“成为朋友”的逻辑,让 += 变成了一个不符合直觉的操作。
更好的写法:
class Person:
def __init__(self, name):
self.name = name
self.friends = []
def add_friend(self, other):
print(f"{self.name} 和 {other.name} 成为了朋友!")
self.friends.append(other)
p1.add_friend(p2) # 这样更直观
魔法方法要谨慎使用,否则会让代码变得奇怪且难以理解!
16.用正则解析 HTML / XML
错误示范:
import re html = "<div><p>Hello, world!</p></div>" match = re.search(r"<p>(.*?)</p>", html) print(match.group(1)) # 这样做并不靠谱
为什么是坑?
HTML 是上下文敏感的,不能用正则完美解析,除非页面结构极其简单,否则你迟早会翻车。
更好的写法:
from bs4 import BeautifulSoup html = "<div><p>Hello, world!</p></div>" soup = BeautifulSoup(html, "html.parser") print(soup.p.text) # 这样解析才靠谱
如果你需要处理 HTML / XML,请用"BeautifulSoup"或"lxml"这类专业的解析库,而不是正则表达式
17.不知道"r"“”(原始字符串)
错误示范:
pattern = "\\d+\\.\\d+" # 正则表达式匹配浮点数 print(re.findall(pattern, "The price is 3.14"))
为什么是坑?
\ 在字符串里是转义字符,如果写"\d+",Python 会误以为 \d 是转义字符,这会导致正则解析出错。
更好的写法:
pattern = r"\d+\.\d+" # 使用原始字符串
加个 r"",就能避免转义问题,写正则时 必须养成加 r 的习惯!
18.误解"super()"的行为
错误示范:
class A:
deff(self):
print("A.f")
classB(A):
deff(self):
print("B.f")
super().f()
classC(B): # 正确继承顺序:C -> B -> A
deff(self):
print("C.f")
super().f()
c = C()
c.f() # 输出顺序为 C.f -> B.f -> A.f
为什么是坑?
Python 采用"C3 线性化"(MRO 规则),"super()"并不只是简单地调用父类,而是根据 MRO 确定顺序。上面的代码会输出:
C.f B.f A.f # 你以为 B 之后是 A?其实是 B -> A
更好的写法:
print(C.mro()) # 用 .mro() 确认方法解析顺序
使用"super()"前,建议先查看 MRO,以免调用顺序与你想象的不同!
19.传递原始字典或元组,而不是用数据
错误示范:
def process_data(data):
return data["name"].upper(), data["age"] + 1
person = {"name": "Alice", "age": 25}
print(process_data(person))
为什么是坑?
如果字典键名变了,你的代码就会崩溃,而且代码可读性很差。
更好的写法:
from dataclasses
import dataclass
@dataclass
class Person:
name: str
age: int
def process_data(person: Person):
return person.name.upper(), person.age + 1
p = Person("Alice", 25)
print(process_data(p))
用"dataclass"代替字典,代码会更清晰,IDE 还能自动补全属性!
20.还在用"namedtuple()“,而不是"NamedTuple”
错误示范:
from collections
import namedtuple
Person = namedtuple("Person", ["name", "age"])
为什么是坑?
"namedtuple"需要用字符串定义字段,而且没有类型注解支持。
更好的写法:
from typing import NamedTuple class Person(NamedTuple): name: str age: int
使用"NamedTuple",不仅更易读,而且支持类型注解,适合现代 Python 代码。
21.在导入时执行代码(import-time side effects)
错误示范:
# utils.py
print("Utils module loaded!") # 只要 import 这个模块,就会执行这行代码
import utils # 这里会自动输出 "Utils module loaded!"
为什么是坑?
模块导入时不应该有副作用!这样会影响性能,并导致意想不到的行为。
更好的写法:
def main():
print("Utils module loaded!")
if __name__ == "__main__":
main() # 只有直接运行这个文件时才执行
用"if name == “main”"保护代码,确保它不会在 import 时执行!
以上就是Python编程中需要避免的21个代码反模式实战详解的详细内容,更多关于Python反模式的资料请关注脚本之家其它相关文章!
