首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >写了多年Python,我最后悔没早点养成这5个习惯

写了多年Python,我最后悔没早点养成这5个习惯

作者头像
用户11081884
发布2026-07-20 20:33:09
发布2026-07-20 20:33:09
230
举报

前几天翻自己多年前写的一个项目,差点没认出来。

没有类型标注,函数参数全靠猜;没有测试,改一行怕崩十行;日志全是print,排查问题靠肉眼扫描;配置硬编码在代码里,换个环境就得改源码;依赖管理一团浆糊,pip install装完不知道哪个版本能跑。

看着那段代码,我只有一个想法:要是早点养成几个习惯,后面能省多少事。

今天聊聊我写Python这些年,最后悔没早点做的5件事。不是什么高深技巧,就是几个习惯。但习惯这东西,早养成一天,后面就少踩一天坑。


一、写类型标注

我前几年写Python从来不加类型标注。理由很充分:Python是动态类型语言啊,加类型标注不是多此一举吗?

直到有一天,我调用一个同事写的函数:

代码语言:javascript
复制
defprocess_data(data, config, flag):
    # 这三个参数分别是什么类型?什么含义?
    # data是dict还是list?config是字符串还是对象?flag是bool还是int?
    ...

我盯着这三个参数看了五分钟,最后翻了半天源码才搞明白。那一刻我突然理解了,类型标注不是给编译器看的,是给人和IDE看的。

加上类型标注之后:

代码语言:javascript
复制
defprocess_data(
    data: list[dict[str, Any]],
    config: ProcessingConfig,
    flag: bool = False,
) ->ProcessResult:
    ...

一眼就知道传什么、返回什么。IDE能自动补全,mypy能帮你检查类型错误,代码review的时候不用再猜参数含义。

而且类型标注写起来没那么麻烦。简单的函数几秒钟就加完了,复杂函数加标注的过程本身就是梳理逻辑——你写着写着就会发现“这个参数到底该传什么”,比直接写代码想得更清楚。

我现在写代码的第一步就是加类型标注,不是最后一步。先定义类型,再写逻辑,思路反而更清晰。

代码语言:javascript
复制
# 以前:先写逻辑,类型靠猜
defget_users(role, active):
    query = db.query(User)
    ifrole:
        query = query.filter_by(role=role)
    ifactive:
        query = query.filter_by(is_active=True)
    returnquery.all()

# 现在:先定义类型,逻辑跟着类型走
defget_users(
    role: UserRole|None = None,
    active: bool = False,
) ->list[User]:
    query = db.query(User)
    ifrole:
        query = query.filter_by(role=role)
    ifactive:
        query = query.filter_by(is_active=True)
    returnquery.all()

别等“以后再加”。以后你不会加的,我试过了。


二、写测试,哪怕只写一个

我以前觉得写测试是“正经项目”才需要做的事,自己写的小工具、内部脚本不用那么讲究。结果就是,每次改代码都提心吊胆——改了A,B会不会挂?不知道,只能手动跑一遍。

后来有个项目上线后出了bug,我改了一行代码,自测没问题,上线后另一个功能崩了。原因是我改的那行影响了一个我没想到的分支。如果有个测试覆盖那个分支,上线前就能发现。

从那以后我给自己定了个规矩:每个新功能至少写一个测试。不是追求覆盖率,就是确保核心逻辑有人守着。

代码语言:javascript
复制
# 业务代码
defcalculate_discount(user: User, order: Order) ->float:
    ifuser.is_vip:
        return0.2
    iforder.total>1000:
        return0.1
    return0.0

# 对应的测试——就这几行,但核心逻辑都覆盖了
deftest_calculate_discount():
    vip_user = User(is_vip=True)
    normal_user = User(is_vip=False)
    big_order = Order(total=1500)
    small_order = Order(total=500)

    assertcalculate_discount(vip_user, big_order) == 0.2
    assertcalculate_discount(vip_user, small_order) == 0.2
    assertcalculate_discount(normal_user, big_order) == 0.1
    assertcalculate_discount(normal_user, small_order) == 0.0

就这么几行测试,以后改 calculate_discount 的逻辑,跑一下就知道有没有改出问题。

不用追求什么“测试驱动开发”,不用纠结覆盖率到多少。先写一个测试,哪怕只测最核心的那条路径,也比零测试强一百倍。

我现在的习惯是:写完一个函数,顺手写个测试。不是“等有空再补”,是写代码的时候一起写。因为一旦代码写完了,你大概率不会再回头补测试——这个我也试过了。


三、用logging,别用print

这个可能是最不起眼但影响最大的一个。

我前几年调试全靠print。开发的时候满屏print,上线前删掉,出了问题再加回来,改完再删……循环往复。更惨的是,有时候删print的时候不小心删了有用的代码,或者忘了删某几个print,线上日志里突然冒出一堆调试信息。

后来有个线上问题,排查的时候我加了一堆print,定位完问题又得一个个删。当时就想:要是这些调试信息能按需开关就好了。

logging就是干这个的:

代码语言:javascript
复制
importlogging

logger = logging.getLogger(__name__)

# 开发时看详细信息
logger.debug("处理用户请求: user_id=%s, params=%s", user_id, params)

# 正常业务日志
logger.info("订单创建成功: order_id=%s", order_id)

# 需要关注但不致命的问题
logger.warning("缓存命中率低于50%%: hit_rate=%.1f", hit_rate)

# 出了错,需要排查
logger.error("数据库连接失败: host=%s, error=%s", db_host, e)

# 严重问题,需要立即处理
logger.critical("支付服务不可用,影响所有订单")

关键区别:logging可以按级别控制输出,print要么全输出要么全删。

开发时设 DEBUG 级别,所有日志都看;上线设 INFO 级别,debug日志自动消失,不用删代码;排查问题时临时调到 DEBUG,看完再调回去。

代码语言:javascript
复制
# 开发环境
logging.basicConfig(level=logging.DEBUG)

# 生产环境
logging.basicConfig(level=logging.INFO)

而且logging的格式化比print好用得多。logger.info("用户%s下单成功", user_id)print(f"用户{user_id}下单成功") 多了个好处:如果当前日志级别不输出这条,格式化字符串根本不会执行,省性能。print的f-string每次都会格式化,不管你需不需要。

还有一个很多人忽略的点:logging会自动带上模块名、函数名、时间戳。你不用手动写 print(f"[{datetime.now()}] [order_service] 订单创建成功"),logging一行配置就搞定:

代码语言:javascript
复制
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
)

输出:

代码语言:javascript
复制
2026-06-05 19:10:31 [my_project.services.order] INFO: 订单创建成功: order_id=12345

从print换到logging,改动不大,但收益是持续的。我现在新项目第一件事就是配logging,print只在临时调试的时候用,调试完就删。


四、配置和代码分开

我早期写代码最喜欢干的事就是把数据库地址、API密钥、超时时间直接写在源码里。

代码语言:javascript
复制
DB_HOST = "192.168.1.100"
DB_PORT = 5432
API_KEY = "sk-xxxxxxxxxxxx"
TIMEOUT = 30

写的时候方便,改的时候痛苦。换个环境就得改源码,改完还得重新部署。更离谱的是有一次,我把包含API密钥的代码推到了公开仓库,还好同事及时发现,不然那个密钥就全网可见了。

后来学乖了,配置一律放环境变量或配置文件,代码里只读取:

代码语言:javascript
复制
frompydantic_settingsimportBaseSettings

classSettings(BaseSettings):
    db_host: str = "localhost"
    db_port: int = 5432
    api_key: str = ""
    timeout: int = 30

    model_config = {"env_file": ".env"}

settings = Settings()

代码里只写默认值和类型,实际值从 .env 文件读取。.env 不进Git(.gitignore 里加上),给新人一个 .env.example 当模板:

代码语言:javascript
复制
# .env.example
DB_HOST=localhost
DB_PORT=5432
API_KEY=your_api_key_here
TIMEOUT=30

这样换环境只改 .env,不动源码;密钥不会进Git;新人clone完复制 .env.example 填上值就能跑。

其实道理很简单:配置是会变的,代码是不该变的。把会变的东西和不该变的东西混在一起,迟早出事。


五、用虚拟环境,从第一个项目开始

我刚开始学Python的时候,所有包直接装在系统Python里。后来装了个Django 2.x的项目,又装了个Django 3.x的项目,版本冲突,两个项目都跑不了。删了重装,装完这个另一个又挂了。

折腾了一下午,最后有人告诉我:用虚拟环境。

代码语言:javascript
复制
python -m venv .venv
source .venv/bin/activate  # Windows: .venv\Scripts\activate
pip install django==3.2

每个项目一个独立环境,包版本互不干扰。简单到不行,但我愣是拖了好几个月才开始用。

后来更进了一步,用 pyproject.toml 管依赖:

代码语言:javascript
复制
[project]
name = "my-project"
version = "0.1.0"
dependencies = [
    "fastapi>=0.100.0",
    "sqlalchemy>=2.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "ruff",
    "mypy",
]

安装就一行:

代码语言:javascript
复制
pip install -e".[dev]"

依赖、版本、开发工具,全在一个文件里管着。比 requirements.txt 省心,比手动pip install靠谱。

虚拟环境这事,没什么技术难度,就是个习惯问题。但这个习惯没养成的时候,你付出的代价是实打实的——版本冲突、环境污染、项目跑不起来,都是因为全局装包。

我现在新建项目的第一步就是创建虚拟环境,不是等项目大了再搞。因为项目永远不会大到“需要虚拟环境”,而是从一开始就该用。


习惯这东西,越早养成越省事

回头看这5个习惯,每个都不复杂:

  1. 写类型标注 — 函数签名一眼就懂,IDE补全、类型检查都跟上
  2. 写测试 — 至少覆盖核心路径,改代码不用提心吊胆
  3. 用logging — 按级别控制输出,不用反复删print
  4. 配置和代码分开 — 换环境不改源码,密钥不进Git
  5. 用虚拟环境 — 项目间互不干扰,依赖管理有据可查

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

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、写类型标注
  • 二、写测试,哪怕只写一个
  • 三、用logging,别用print
  • 四、配置和代码分开
  • 五、用虚拟环境,从第一个项目开始
  • 习惯这东西,越早养成越省事
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档