首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >为什么你的Python总报错?80%是这几个习惯导致的

为什么你的Python总报错?80%是这几个习惯导致的

作者头像
用户11081884
发布2026-07-20 20:29:04
发布2026-07-20 20:29:04
290
举报

上周排查bug,一个简单的数据处理脚本,居然有5个不同的错误。看着满屏的报错信息,我突然意识到:很多Python错误,其实不是技术问题,而是习惯问题

今天就来盘点那些导致Python频繁报错的坏习惯,以及如何改正它们。相信我,改掉这些习惯,你的代码报错率至少能降低80%。


坏习惯1:变量命名随意,自己都看不懂

错误示范:

代码语言:javascript
复制
# 这是什么意思?三个月后你还记得吗?
a = 10
b = 20
c = a+b  # c是什么?总和?平均值?

x = get_data()  # x是什么数据?
y = process(x)  # process做了什么?
z = save(y)     # 保存到哪里?

# 更糟糕的:使用内置函数名
list = [1, 2, 3]  # 覆盖了list()函数
str = "hello"     # 覆盖了str()函数

导致的问题:

  1. TypeError: 'list' object is not callable(覆盖了内置函数)
  2. NameError: name 'total' is not defined(变量名记错)
  3. 代码难以理解,调试困难

正确做法:

代码语言:javascript
复制
# 使用有意义的变量名
student_count = 10
average_score = 85.5
total_amount = calculate_total(price, quantity)

# 集合类型加后缀
user_list = ["张三", "李四"]
config_dict = {"host": "localhost", "port": 8080}
name_set = {"Alice", "Bob"}

# 布尔值用is_、has_开头
is_valid = True
has_permission = False
is_connected = check_connection()

# 函数名用动词开头
defcalculate_average(scores):
    returnsum(scores) /len(scores)

defvalidate_email(email):
    return"@"inemail

# 常量用大写
MAX_RETRIES = 3
DEFAULT_TIMEOUT = 30
API_BASE_URL = "https://api.example.com"

命名规范检查:

代码语言:javascript
复制
# 安装pylint检查命名
# pip install pylint
# pylint your_script.py

# 或者使用flake8插件
# pip install flake8-naming
# flake8 your_script.py

坏习惯2:不处理None,导致AttributeError

错误示范:

代码语言:javascript
复制
# API返回可能是None
user_data = fetch_user_data(user_id)
# 直接访问属性,可能报错
email = user_data["email"]  # 如果user_data是None,TypeError
name = user_data.get("name")  # 如果user_data是None,AttributeError

# 函数可能返回None
result = find_user_by_email(email)
result.send_notification()  # 如果result是None,AttributeError

导致的问题:

  • AttributeError: 'NoneType' object has no attribute 'xxx'
  • TypeError: 'NoneType' object is not subscriptable

正确做法:

代码语言:javascript
复制
# 方法1:明确检查None
user_data = fetch_user_data(user_id)
ifuser_dataisnotNone:
    email = user_data.get("email", "default@example.com")
    name = user_data.get("name", "未知用户")
else:
    # 处理None情况
    email = "default@example.com"
    name = "未知用户"

# 方法2:使用or提供默认值
user_data = fetch_user_data(user_id) or {}
email = user_data.get("email", "default@example.com")

# 方法3:使用Walrus运算符(Python 3.8+)
if (data := fetch_user_data(user_id)) isnotNone:
    process_data(data)

# 方法4:使用try-except
try:
    user_data = fetch_user_data(user_id)
    email = user_data["email"]
except (TypeError, KeyError):
    email = "default@example.com"

# 方法5:使用Optional类型提示
fromtypingimportOptional

deffind_user(user_id: str) ->Optional[dict]:
    """明确表示可能返回None"""
    # 实现...
    pass

# 使用时IDE会提示可能为None
user = find_user("123")
ifuser:  # IDE会提示检查None
    email = user["email"]

None安全工具:

代码语言:javascript
复制
# 使用pydantic确保数据完整性
frompydanticimportBaseModel, Field
fromtypingimportOptional

classUser(BaseModel):
    name: str
    email: str
    age: Optional[int] = None  # 明确标注可选字段
    
# 使用后不会出现意外的None
user = User(name="张三", email="zhang@example.com")
print(user.age)  # 输出: None,但不会报错

坏习惯3:错误使用可变默认参数

经典坑爹错误:

代码语言:javascript
复制
defadd_item(item, items=[]):
    """添加项目到列表"""
    items.append(item)
    returnitems

# 看起来没问题?
print(add_item(1))  # [1]
print(add_item(2))  # 期望[2],实际[1, 2]!
print(add_item(3))  # 期望[3],实际[1, 2, 3]!

# 原因:默认参数在函数定义时只创建一次
# 所有调用共享同一个列表对象

导致的问题:

  • 数据污染,不同调用之间相互影响
  • 难以发现的bug,测试时正常,生产环境异常
  • UnboundLocalError(在函数内修改后又被读取)

正确做法:

代码语言:javascript
复制
# 方法1:使用None作为默认值
defadd_item(item, items=None):
    ifitemsisNone:
        items = []  # 每次调用创建新列表
    items.append(item)
    returnitems

# 方法2:使用不可变类型
defcreate_user(name, permissions=()):  # 元组是不可变的
    return {"name": name, "permissions": list(permissions)}

# 方法3:使用functools.partial
fromfunctoolsimportpartial

defprocess_data(data, config=None):
    ifconfigisNone:
        config = {}
    # 处理数据
    returnprocessed_data

# 创建特定配置的版本
process_with_defaults = partial(process_data, config={"mode": "fast"})

# 方法4:使用数据类
fromdataclassesimportdataclass, field
fromtypingimportList

@dataclass
classShoppingCart:
    items: List[str] = field(default_factory=list)  # 每次创建新列表
    
    defadd_item(self, item):
        self.items.append(item)

检测工具

代码语言:javascript
复制
# 使用pylint检测
# pylint会警告:Dangerous default value [] as argument

# 使用flake8插件
# pip install flake8-bugbear
# 会检测到:B006 Do not use mutable data structures for argument defaults

坏习惯4:异常处理过于宽泛

错误示范:

代码语言:javascript
复制
# 捕获所有异常,隐藏真正问题
try:
    result = risky_operation()
    save_to_database(result)
    send_email_notification()
exceptException:  # 捕获所有异常
    print("出错了")  # 什么错?不知道
    
# 捕获异常但不处理
try:
    process_file("data.txt")
except:
    pass  # 静默失败,难以调试

# 错误的异常顺序
try:
    parse_json(data)
exceptExceptionase:  # 太宽泛,放在前面
    print(f"错误: {e}")
exceptjson.JSONDecodeError:  # 永远不会执行
    print("JSON解析错误")

导致的问题:

  1. 隐藏真正的bug
  2. 难以调试和定位问题
  3. 资源泄漏(文件、连接未关闭)
  4. 程序状态不一致

正确做法:

代码语言:javascript
复制
# 原则1:只捕获你能处理的异常
try:
    config = load_config("config.yaml")
exceptFileNotFoundError:
    # 文件不存在,使用默认配置
    config = get_default_config()
exceptyaml.YAMLErrorase:
    # 配置文件格式错误,记录日志
    logger.error(f"配置文件格式错误: {e}")
    # 可以选择退出或使用默认值
    config = get_default_config()
# 让其他异常(如内存不足)向上传播

# 原则2:从具体到一般
try:
    response = requests.get(url, timeout=10)
    response.raise_for_status()  # 检查HTTP状态
    data = response.json()
exceptrequests.Timeout:
    logger.warning(f"请求超时: {url}")
    returnNone
exceptrequests.HTTPErrorase:
    logger.error(f"HTTP错误 {e.response.status_code}: {url}")
    returnNone
exceptjson.JSONDecodeError:
    logger.error(f"响应不是有效的JSON: {url}")
    returnNone
exceptExceptionase:
    # 最后捕获一般异常,记录详细信息
    logger.exception(f"未知错误: {e}")
    raise  # 重新抛出,让上层处理

# 原则3:使用else和finally
try:
    connection = create_database_connection()
    cursor = connection.cursor()
exceptConnectionErrorase:
    logger.error(f"数据库连接失败: {e}")
    raise
else:
    # 只有try成功时才执行
    try:
        cursor.execute("SELECT * FROM users")
        results = cursor.fetchall()
    exceptDatabaseErrorase:
        logger.error(f"查询失败: {e}")
        results = []
    finally:
        # 确保游标关闭
        cursor.close()
finally:
    # 无论成功失败都执行
    ifconnection:
        connection.close()

# 原则4:使用上下文管理器
fromcontextlibimportsuppress

# 忽略特定异常(明确知道可以忽略)
withsuppress(FileNotFoundError):
    os.remove("temp_file.txt")

# 使用with语句自动管理资源
withopen("data.txt", "r") asf:
    content = f.read()  # 文件会自动关闭

异常处理工具:

代码语言:javascript
复制
# 自定义异常类,建立异常层次
classAppError(Exception):
    """应用基础异常"""
    pass

classValidationError(AppError):
    """数据验证错误"""
    pass

classDatabaseError(AppError):
    """数据库错误"""
    def__init__(self, message, sql=None, params=None):
        self.sql = sql
        self.params = params
        super().__init__(message)

# 使用时
defsave_user(user_data):
    ifnotvalidate_user(user_data):
        raiseValidationError("用户数据无效")
    
    try:
        db.execute("INSERT INTO users VALUES (?, ?)", 
                  [user_data["name"], user_data["email"]])
    exceptsqlite3.Errorase:
        raiseDatabaseError("保存用户失败", sql=sql, params=params) frome

坏习惯5:不理解Python的作用域规则

常见错误:

代码语言:javascript
复制
# 错误1:在函数内修改全局变量
count = 0

defincrement():
    count += 1  # UnboundLocalError: local variable 'count' referenced before assignment

# 错误2:循环变量泄漏
foriinrange(5):
    process(i)
print(i)  # 输出4,i仍然存在!

# 错误3:闭包变量捕获
defcreate_multipliers():
    return [lambdax: i*xforiinrange(5)]

multipliers = create_multipliers()
print(multipliers[0](2))  # 期望0,实际8(i=4)
print(multipliers[1](2))  # 期望2,实际8(i=4)

# 错误4:类属性与实例属性混淆
classUser:
    users = []  # 类属性
    
    def__init__(self, name):
        self.name = name
        self.users.append(self)  # 修改的是类属性!

user1 = User("张三")
user2 = User("李四")
print(user1.users)  # [user1, user2]
print(user2.users)  # [user1, user2] 同一个列表!

正确做法:

代码语言:javascript
复制
# 解决方案1:明确声明全局变量
count = 0

defincrement():
    globalcount  # 明确声明使用全局变量
    count += 1

# 更好的方案:使用返回值
defincrement_counter(current):
    returncurrent+1

count = increment_counter(count)

# 解决方案2:避免循环变量泄漏
foriinrange(5):
    process(i)
# i在这里不可访问(Python 3中,for循环变量作用域仅限于循环内)

# 或者使用列表推导式
results = [process(i) foriinrange(5)]

# 解决方案3:闭包变量立即绑定
defcreate_multipliers():
    return [lambdax, i=i: i*xforiinrange(5)]  # 使用默认参数立即绑定

# 或者使用partial
fromfunctoolsimportpartial

defcreate_multipliers():
    return [partial(lambdai, x: i*x, i) foriinrange(5)]

# 解决方案4:正确使用类属性和实例属性
classUser:
    all_users = []  # 类属性,所有实例共享
    
    def__init__(self, name):
        self.name = name  # 实例属性,每个实例独立
        self.friends = []  # 实例属性
        User.all_users.append(self)  # 修改类属性要指定类名
    
    @classmethod
    defget_user_count(cls):
        returnlen(cls.all_users)  # 使用cls访问类属性

# 使用property管理属性访问
classUser:
    def__init__(self, name):
        self._name = name  # 私有属性
        
    @property
    defname(self):
        returnself._name
    
    @name.setter
    defname(self, value):
        ifnotvalue:
            raiseValueError("姓名不能为空")
        self._name = value

作用域调试技巧:

代码语言:javascript
复制
# 查看局部变量和全局变量
defmy_function():
    x = 10
    y = 20
    print(locals())  # 输出局部变量
    print(globals().keys())  # 输出全局变量名

# 使用dis模块查看字节码
importdis

deftest_scope():
    x = 1
    definner():
        returnx+1
    returninner()

dis.dis(test_scope)  # 查看变量作用域

坏习惯6:忽略类型提示,运行时才发现类型错误

错误示范:

代码语言:javascript
复制
# 没有类型提示,只能运行时才发现错误
defcalculate_total(price, quantity):
    returnprice*quantity

# 调用时
result = calculate_total("100", 2)  # 返回"100100",不是200!
result = calculate_total(100, "2")  # TypeError: can't multiply sequence by non-int

# 更复杂的例子
defprocess_data(data):
    # data是什么类型?字典?列表?字符串?
    returndata["value"] *2  # 如果data是列表,KeyError!

正确做法:

代码语言:javascript
复制
# 添加类型提示
fromtypingimportList, Dict, Optional, Union

defcalculate_total(price: float, quantity: int) ->float:
    """计算总价
    
    Args:
        price: 单价,浮点数
        quantity: 数量,整数
        
    Returns:
        总价,浮点数
    """
    returnprice*quantity

# 复杂类型提示
defprocess_users(
    users: List[Dict[str, Union[str, int]]],
    filter_active: bool = True
) ->List[str]:
    """处理用户列表
    
    Args:
        users: 用户字典列表,每个字典包含name和age
        filter_active: 是否只过滤活跃用户
        
    Returns:
        用户名列表
    """
    iffilter_active:
        return [user["name"] foruserinusersifuser.get("active", True)]
    return [user["name"] foruserinusers]

# 使用mypy进行静态检查
# pip install mypy
# mypy your_script.py

# 运行时类型检查(使用pydantic)
frompydanticimportBaseModel, validator

classUser(BaseModel):
    name: str
    age: int
    email: str
    
    @validator('age')
    defvalidate_age(cls, v):
        ifv<0:
            raiseValueError("年龄不能为负数")
        returnv

# 自动验证类型
user = User(name="张三", age=25, email="zhang@example.com")
# 如果类型错误,会在创建时立即报错

坏习惯7:文件操作不关闭资源

错误示范:

代码语言:javascript
复制
# 忘记关闭文件
file = open("data.txt", "r")
content = file.read()
# 忘记file.close(),可能导致资源泄漏

# 在异常情况下文件未关闭
file = open("data.txt", "r")
try:
    content = file.read()
    process(content)  # 可能抛出异常
finally:
    file.close()  # 正确,但容易忘记

导致的问题:

  • 文件描述符泄漏
  • 数据可能未写入磁盘
  • 其他进程无法访问文件

正确做法:

代码语言:javascript
复制
# 方法1:使用with语句(推荐)
withopen("data.txt", "r") asfile:
    content = file.read()
# 文件会自动关闭

# 方法2:处理多个文件
withopen("input.txt", "r") asinfile, open("output.txt", "w") asoutfile:
    content = infile.read()
    outfile.write(content.upper())

# 方法3:使用pathlib(更现代)
frompathlibimportPath

file_path = Path("data.txt")
content = file_path.read_text()  # 自动管理文件

# 写入文件
file_path.write_text("新内容")

# 方法4:数据库连接等资源
importsqlite3

withsqlite3.connect("database.db") asconn:
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM users")
    results = cursor.fetchall()
# 连接会自动关闭和提交

如何改掉这些坏习惯?

第一步:代码审查清单每次提交代码前,检查这些问题:

  1. [ ] 变量命名是否清晰?
  2. [ ] 是否处理了可能的None值?
  3. [ ] 是否使用了可变默认参数?
  4. [ ] 异常处理是否恰当?
  5. [ ] 是否添加了类型提示?
  6. [ ] 资源是否正确关闭?

第二步:使用自动化工具

代码语言:javascript
复制
# 安装代码检查工具
pip install pylint flake8 mypy black

# 配置预提交钩子
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.4.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml

  - repo: https://github.com/psf/black
    rev: 23.3.0
    hooks:
      - id: black

  - repo: https://github.com/pycqa/flake8
    rev: 6.0.0
    hooks:
      - id: flake8

第三步:建立团队规范

代码语言:javascript
复制
# coding_standards.py
"""
团队编码规范
"""

# 1. 所有函数必须有类型提示
# 2. 所有异常必须明确处理
# 3. 所有资源必须使用with语句
# 4. 变量名必须清晰
# 5. 提交前必须通过mypy检查

第四步:定期重构每周花1小时,重构一段旧代码:

  1. 添加缺失的类型提示
  2. 改进变量命名
  3. 优化异常处理
  4. 添加文档字符串

好的编程习惯就像好的卫生习惯:每天刷牙很麻烦,但能避免牙疼。每天多花5分钟检查代码,能避免5小时的调试。优秀的开发者不是不写bug,而是不让bug活到生产环境。

“无他,惟手熟尔”!有需要的用起来!

如果你觉得这篇文章有用,欢迎点赞、转发、收藏、留言、推荐❤!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-05-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Nicholas与Pypi 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 坏习惯1:变量命名随意,自己都看不懂
  • 坏习惯2:不处理None,导致AttributeError
  • 坏习惯3:错误使用可变默认参数
  • 坏习惯4:异常处理过于宽泛
  • 坏习惯5:不理解Python的作用域规则
  • 坏习惯6:忽略类型提示,运行时才发现类型错误
  • 坏习惯7:文件操作不关闭资源
  • 如何改掉这些坏习惯?
  • 好的编程习惯就像好的卫生习惯:每天刷牙很麻烦,但能避免牙疼。每天多花5分钟检查代码,能避免5小时的调试。优秀的开发者不是不写bug,而是不让bug活到生产环境。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档