百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

Python 上下文管理器魔法手册:with 语句的终极艺术

wptr33 2025-05-11 01:42 13 浏览

对话实录

小白:(抓狂)我写了f = open("data.txt"),结果忘记关闭文件了!

专家:(掏出魔法书)用 with 语句,文件自动关闭,永不泄露

上下文管理器基础三连击

1. 基本用法

# 传统写法(危险!)
f = open("data.txt")
try:
 content = f.read()
finally:
 f.close()

#with写法(推荐!)
with open("data.txt") as f:
 content = f.read() # 自动关闭文件

专家提醒:任何需要清理的资源,如文件、数据库连接、网络套接字等,都要用with语句!传统写法中,若在try块内发生异常,文件可能无法正常关闭,导致资源泄露。而with语句通过上下文管理器,无论代码块内是否发生异常,都会自动关闭文件,确保资源被正确释放。

2. 自定义上下文管理器

class Timer:
    def __enter__(self):
        self.start = time.time()
        return self
    
    def __exit__(self, *args):
        self.duration = time.time() - self.start
        print(f"耗时:{self.duration:.2f}秒")

with Timer():
    time.sleep(1)  # → 耗时:1.00秒

通过实现__enter__和__exit__方法,我们可以创建自定义的上下文管理器。__enter__方法在进入with代码块时被调用,通常用于初始化资源;__exit__方法在离开with代码块时被调用,用于清理资源。

六大实战案例

案例 1:数据库连接

import sqlite3

class DBConnection:
    def __enter__(self):
        self.conn = sqlite3.connect("test.db")
        return self.conn.cursor()
    
    def __exit__(self, type, value, traceback):
        self.conn.commit()
        self.conn.close()

with DBConnection() as cursor:
    cursor.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY)")

在数据库操作中,使用上下文管理器可以确保数据库连接在使用完毕后被正确关闭,同时自动提交事务,避免数据丢失或不一致的问题。

案例 2:线程锁管理

import threading
lock = threading.Lock()
with lock: # 自动获取和释放锁
	# 执行线程安全操作
	print("临界区代码")

在多线程编程中,线程锁用于保护共享资源,防止多个线程同时访问导致数据不一致。with语句结合线程锁,在进入代码块时自动获取锁,离开时自动释放锁,确保临界区代码的线程安全性。

案例 3:临时目录

from contextlib import contextmanager
import tempfile
import shutil

@contextmanager
def temp_dir():
    dirpath = tempfile.mkdtemp()
    try:
        yield dirpath
    finally:
        shutil.rmtree(dirpath)

with temp_dir() as tmp:
    print(f"使用临时目录:{tmp}")  # 自动清理

contextlib.contextmanager装饰器提供了一种更简洁的方式来创建上下文管理器。在这个案例中,temp_dir上下文管理器创建一个临时目录,在with代码块结束后自动删除该目录,避免在磁盘上留下不必要的文件。

案例 4:文件备份

import shutil

class FileBackup:
    def __init__(self, source, target):
        self.source = source
        self.target = target

    def __enter__(self):
        shutil.copy2(self.source, self.target)
        return self

    def __exit__(self, type, value, traceback):
        pass

with FileBackup("data.txt", "data_backup.txt"):
    print("数据已备份")

在进行文件操作时,有时需要先对文件进行备份。FileBackup上下文管理器在进入with代码块时,将源文件复制到目标位置,实现文件备份功能。

案例 5:日志记录

import logging

class LoggingContext:
    def __init__(self, level):
        self.level = level

    def __enter__(self):
        self.old_level = logging.getLogger().getEffectiveLevel()
        logging.getLogger().setLevel(self.level)
        return self

    def __exit__(self, type, value, traceback):
        logging.getLogger().setLevel(self.old_level)

with LoggingContext(logging.DEBUG):
    logging.debug("这是一条调试日志")

在开发过程中,我们可能需要根据不同的场景调整日志级别。LoggingContext上下文管理器在进入with代码块时,将日志级别设置为指定值,离开时恢复原来的日志级别,方便灵活地控制日志输出。

案例 6:资源监控

import resource

class ResourceMonitor:
    def __enter__(self):
        self.start_usage = resource.getrusage(resource.RUSAGE_SELF)
        return self

    def __exit__(self, type, value, traceback):
        end_usage = resource.getrusage(resource.RUSAGE_SELF)
        print(f"CPU时间:{end_usage.ru_utime - self.start_usage.ru_utime}秒")
        print(f"内存使用:{end_usage.ru_maxrss - self.start_usage.ru_maxrss}KB")

with ResourceMonitor():
    for _ in range(1000000):
        pass

ResourceMonitor上下文管理器可以在代码块执行前后监控 CPU 时间和内存使用情况,帮助我们分析代码的性能,找出潜在的性能瓶颈。

四大血泪陷阱

忘记实现__exit__

class BadManager:
    def __enter__(self):
        print("进入")
        # 忘记实现__exit__ 

with BadManager():  报错

如果自定义上下文管理器没有实现__exit__方法,在使用with语句时会抛出AttributeError异常。确保在创建上下文管理器时,正确实现__enter__和__exit__方法。

异常处理不当

class DangerManager:
    def __exit__(self, type, val, tb):
        return True  #  吞掉所有异常

with DangerManager():
    1 / 0  # 错误被静默吞掉!

__exit__方法的返回值决定了是否抑制异常。如果返回True,异常将被抑制,导致错误信息无法被正确处理。在处理异常时,应根据实际需求合理返回,避免吞掉重要的错误信息。

资源泄漏

# 错误示范
file = open("data.txt")
content = file.read()
# 忘记file.close()
# 正确做法
with open("data.txt") as file:
	content = file.read()

在使用资源时,如果忘记手动关闭资源,可能会导致资源泄漏。使用with语句可以有效避免这种情况,确保资源在使用完毕后被正确释放。

上下文管理器嵌套错误

#错误示范
with open("a.txt") as f1:
    with open("b.txt") as f2:
        pass

# 正确做法
with open("a.txt") as f1, open("b.txt") as f2:
    pass

虽然多层嵌套的with语句可以正常工作,但使用多重上下文语法可以使代码更加简洁,同时减少缩进层级,提高代码的可读性。

专家工具箱

1. contextlib模块

from contextlib import contextmanager

@contextmanager
def tag(name):
    print(f"<{name}>")
    yield
    print(f"</{name}>")

with tag("html"):
    print("网页内容")  # → <html>网页内容</html>

contextlib.contextmanager装饰器简化了上下文管理器的创建过程。通过定义一个生成器函数,在yield语句前后分别编写进入和退出上下文的逻辑,即可创建一个上下文管理器。

2. 异步上下文

class AsyncManager:
    async def __aenter__(self):
        print("异步进入")
    
    async def __aexit__(self, *args):
        print("异步退出")

async def main():
    async with AsyncManager():
        print("执行操作")

在异步编程中,异步上下文管理器通过__aenter__和__aexit__方法实现类似的功能。async with语句用于管理异步资源,确保资源在异步操作结束后被正确清理。

小白:(献上膝盖)原来上下文管理器这么强大!

专家:(扶起小白)记住:with语句是 Python 的优雅之道!

冷知识:上下文管理器的隐藏特性

  • 可重入性:某些上下文管理器支持在同一个线程中多次进入和退出,确保资源的正确管理。
  • 上下文栈:Python 维护一个上下文栈,用于管理多个上下文管理器的嵌套调用,保证资源的清理顺序正确。
  • try - finally的关系:with语句本质上是try - finally的语法糖,通过上下文管理器简化了资源管理的代码。

相关推荐

redis的八种使用场景

前言:redis是我们工作开发中,经常要打交道的,下面对redis的使用场景做总结介绍也是对redis举报的功能做梳理。缓存Redis最常见的用途是作为缓存,用于加速应用程序的响应速度。...

基于Redis的3种分布式ID生成策略

在分布式系统设计中,全局唯一ID是一个基础而关键的组件。随着业务规模扩大和系统架构向微服务演进,传统的单机自增ID已无法满足需求。高并发、高可用的分布式ID生成方案成为构建可靠分布式系统的必要条件。R...

基于OpenWrt系统路由器的模式切换与网页设计

摘要:目前商用WiFi路由器已应用到多个领域,商家通过给用户提供一个稳定免费WiFi热点达到吸引客户、提升服务的目标。传统路由器自带的Luci界面提供了工厂模式的Web界面,用户可通过该界面配置路...

这篇文章教你看明白 nginx-ingress 控制器

主机nginx一般nginx做主机反向代理(网关)有以下配置...

如何用redis实现注册中心

一句话总结使用Redis实现注册中心:服务注册...

爱可可老师24小时热门分享(2020.5.10)

No1.看自己以前写的代码是种什么体验?No2.DooM-chip!国外网友SylvainLefebvre自制的无CPU、无操作码、无指令计数器...No3.我认为CS学位可以更好,如...

Apportable:拯救程序员,IOS一秒变安卓

摘要:还在为了跨平台使用cocos2d-x吗,拯救objc程序员的奇葩来了,ApportableSDK:FreeAndroidsupportforcocos2d-iPhone。App...

JAVA实现超买超卖方案汇总,那个最适合你,一篇文章彻底讲透

以下是几种Java实现超买超卖问题的核心解决方案及代码示例,针对高并发场景下的库存扣减问题:方案一:Redis原子操作+Lua脚本(推荐)//使用Redis+Lua保证原子性publicbo...

3月26日更新 快速施法自动施法可独立设置

2016年3月26日DOTA2有一个79.6MB的更新主要是针对自动施法和快速施法的调整本来内容不多不少朋友都有自动施法和快速施法的困扰英文更新日志一些视觉BUG修复就不翻译了主要翻译自动施...

Redis 是如何提供服务的

在刚刚接触Redis的时候,最想要知道的是一个’setnameJhon’命令到达Redis服务器的时候,它是如何返回’OK’的?里面命令处理的流程如何,具体细节怎么样?你一定有问过自己...

lua _G、_VERSION使用

到这里我们已经把lua基础库中的函数介绍完了,除了函数外基础库中还有两个常量,一个是_G,另一个是_VERSION。_G是基础库本身,指向自己,这个变量很有意思,可以无限引用自己,最后得到的还是自己,...

China&#39;s top diplomat to chair third China-Pacific Island countries foreign ministers&#39; meeting

BEIJING,May21(Xinhua)--ChineseForeignMinisterWangYi,alsoamemberofthePoliticalBureau...

移动工作交流工具Lua推出Insights数据分析产品

Lua是一个适用于各种职业人士的移动交流平台,它在今天推出了一项叫做Insights的全新功能。Insights是一个数据平台,客户可以在上面实时看到员工之间的交流情况,并分析这些情况对公司发展的影响...

Redis 7新武器:用Redis Stack实现向量搜索的极限压测

当传统关系型数据库还在为向量相似度搜索的性能挣扎时,Redis7的RedisStack...

Nginx/OpenResty详解,Nginx Lua编程,重定向与内部子请求

重定向与内部子请求Nginx的rewrite指令不仅可以在Nginx内部的server、location之间进行跳转,还可以进行外部链接的重定向。通过ngx_lua模块的Lua函数除了能实现Nginx...