为什么Python大神都在用with?看完我悟了

你有没有遇到过这种崩溃的情况:

python 复制代码
f = open('data.txt', 'w')
f.write('重要数据')
# 程序在这里崩了,文件没关闭!

然后你发现文件损坏了,数据丢了,客户在骂娘,老板在瞪你...

更绝的是这种情况:

python 复制代码
conn = sqlite3.connect('database.db')
cursor = conn.cursor()
cursor.execute('UPDATE users SET money = money + 1000 WHERE id = 1')
# 这里抛异常了,连接没关闭,数据库锁死了!

整个应用卡住,用户投诉电话被打爆,你在凌晨3点重启服务器...

今天咱们就来彻底搞懂Python的with语句,让你告别这些资源泄漏的噩梦。

什么是with语句?

简单来说,with语句就是Python的"自动保姆"。

它会在你使用资源的时候自动打开,用完了自动关闭,中间出异常了也自动关闭。

就像雇了个保姆,你出门前告诉他"帮我照顾孩子",你回家时孩子已经被哄睡着了,不管中间孩子怎么闹腾,保姆都搞定了。

没用with的惨痛教训

❌ 新手写法(我当年也这么写过)

python 复制代码
def process_data():
    file = open('important.txt', 'w')
    file.write('关键数据')
    # 忘记关闭文件了!
    # 文件句柄泄漏,系统资源被占用

结果: 文件可能没保存成功,其他程序无法访问这个文件。

❌ 进阶写法(看起来很安全,实则坑爹)

python 复制代码
def process_data():
    try:
        file = open('important.txt', 'w')
        file.write('关键数据')
        # 这里如果抛异常,file.close()不会执行!
        1 / 0  # 假设这里出异常了
        file.close()
    except Exception as e:
        print(f'出错了: {e}')
        # 文件没关闭!资源泄漏!

结果: 异常处理了,但资源没释放。

❌ 老手写法(终于知道要close了)

python 复制代码
def process_data():
    try:
        file = open('important.txt', 'w')
        file.write('关键数据')
    except Exception as e:
        print(f'出错了: {e}')
    finally:
        file.close()  # 但是如果open就失败了怎么办?

结果: 如果open('important.txt')本身就失败,file.close()会报错NameError

with语句的正确姿势

✅ 大神写法(优雅、安全、简洁)

python 复制代码
def process_data():
    with open('important.txt', 'w') as file:
        file.write('关键数据')
        1 / 0  # 就算这里崩了,文件也会自动关闭

就这么简单!不管中间发生什么,文件都会被正确关闭。

这就是为什么Python大神都在用with的原因------它能让你睡个安稳觉。

with的工作原理

with语句背后是两个魔术方法:__enter____exit__

python 复制代码
class FileManager:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None

    def __enter__(self):
        print('准备打开文件...')
        self.file = open(self.filename, self.mode)
        return self.file  # 这个返回值会赋给as后面的变量

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('准备关闭文件...')
        if self.file:
            self.file.close()

        # 如果返回True,异常会被忽略
        # 如果返回False或None,异常会继续传播
        return False

# 使用方式
with FileManager('test.txt', 'w') as f:
    f.write('Hello World')
    # 这里就算抛异常,__exit__也会被调用

你可以把with想象成一个超级靠谱的管家:

  1. 你要进门前,管家提前帮你开门(__enter__
  2. 你在屋里随便折腾,管家在门口等着
  3. 你要出门时,管家帮你关门(__exit__
  4. 就算你在屋里摔倒了,管家也会先扶你起来,再帮你关门

实战场景:什么时候用with?

1. 文件操作(最常见)

python 复制代码
# 读取大文件,不用担心忘记关闭
with open('big_data.csv', 'r') as f:
    for line in f:
        process_line(line)  # 就算这里内存溢出,文件也会关闭

2. 数据库连接

python 复制代码
import sqlite3

def update_user_money(user_id, amount):
    with sqlite3.connect('app.db') as conn:
        cursor = conn.cursor()
        cursor.execute('UPDATE users SET money = money + ? WHERE id = ?',
                      (amount, user_id))
        # 自动提交和关闭,就算这里抛异常也不会锁死数据库

3. 线程锁

python 复制代码
import threading

lock = threading.Lock()

def critical_section():
    with lock:
        # 临界区代码,同时只能一个线程访问
        shared_resource += 1
        # 就算这里抛异常,锁也会被释放,不会死锁

4. 网络请求

python 复制代码
import requests

def download_data():
    with requests.Session() as session:
        session.auth = ('user', 'pass')
        response = session.get('https://api.example.com/data')
        # 连接池自动管理,不用担心资源泄漏

自定义上下文管理器

有时候你想创建自己的"自动管家",很简单:

python 复制代码
from contextlib import contextmanager

@contextmanager
def timer(name):
    import time
    start = time.time()
    print(f'{name} 开始执行...')
    try:
        yield  # 这里是with块中的代码
    finally:
        end = time.time()
        print(f'{name} 执行完毕,耗时:{end - start:.2f}秒')

# 使用方式
with timer('数据处理'):
    time.sleep(2)  # 模拟耗时操作
    # 这里会自动计算并打印执行时间

踩坑指南

1. __exit__的返回值陷阱

python 复制代码
class BadContext:
    def __enter__(self):
        print('进入')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('退出')
        return True  # 这行是陷阱!

with BadContext():
    1 / 0  # 这个异常会被"吃掉"!

print('程序继续运行,你都不知道出过异常!')

切记: 除非你真的知道自己在做什么,否则__exit__应该返回FalseNone

2. 异常信息丢失

python 复制代码
def debug_context():
    try:
        with some_context():
            risky_operation()
    except Exception as e:
        # 这里的e可能是上下文管理器抛出的异常
        # 而不是你真正想捕获的异常
        logger.error(f'出错了: {e}')

3. 嵌套with的性能

python 复制代码
# ❌ 这样写性能不好
with open('file1.txt') as f1:
    with open('file2.txt') as f2:
        with open('file3.txt') as f3:
            process_files(f1, f2, f3)

# ✅ 推荐写法
with open('file1.txt') as f1, \
     open('file2.txt') as f2, \
     open('file3.txt') as f3:
    process_files(f1, f2, f3)

进阶技巧

1. 使用contextlib.suppress

python 复制代码
from contextlib import suppress

# 有时候你就是想忽略某些异常
with suppress(FileNotFoundError):
    os.remove('temp_file.txt')  # 文件不存在也没关系

2. 使用contextlib.nullcontext

python 复制代码
from contextlib import nullcontext

# 有时候你条件性地需要上下文管理器
context_manager = lock if use_lock else nullcontext()

with context_manager:
    shared_operation()

总结

记住一句话:涉及资源的获取和释放,就考虑用with语句。

  • 文件操作?用with
  • 数据库连接?用with
  • 网络请求?用with
  • 线程锁?用with
  • 临时目录?用with

with不是语法糖,是你的保险单。

下次再看到open()后面没有with,你就知道该说什么了:

"兄弟,你这代码能跑,但是我建议你买个保险..."


你在项目里遇到过哪些资源泄漏的坑?评论区聊聊,看看谁的故事更惨!

相关推荐
mudtools1 小时前
一分钟实现.NET与飞书长连接的WebSocket架构
后端·c#·.net
Mcband1 小时前
【Spring Boot】Interceptor的原理、配置、顺序控制及与Filter的关键区别
java·spring boot·后端
qq_348231851 小时前
Spring Boot 体系核心全解
java·spring boot·后端
shengjk11 小时前
一文搞定 NTP 三大核心问题:是什么、为何不准、来自何方
后端
不会玩电脑的Xin.1 小时前
Spring框架入门:IOC与AOP实战
java·后端·spring
SnrtIevg1 小时前
Spring Modulith :构建模块化单体应用
后端
LSTM971 小时前
用 Python 自动化编辑 Word 文档
后端
王中阳Go2 小时前
手把手教你用 GoFrame 实现 RBAC 权限管理,从零到一搞定后台权限系统
后端
苏三说技术2 小时前
try...catch真的影响性能吗?
后端