前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >两种方法自定义Python上下文管理器-contextlib

两种方法自定义Python上下文管理器-contextlib

作者头像
somenzz
发布2022-05-24 14:43:36
3360
发布2022-05-24 14:43:36
举报
文章被收录于专栏:Python七号

你好,我是 somenzz,可以叫我征哥,今天分享两种自定义上下文管理器的方法,并比较它们的性能。

上下文管理器相信你也用过,就是 with 开头的 Python 代码块,通常在读写文件、数据库的时候必用,但是我自己很少自己写,主要还是没有意识到它的好,但是今天,我意识到了。

比如说,编程时经常要处理的路径问题,通常程序工作在一个目录,但是过程中要去另一个目录处理一些文件,处理完还要切换回原目录,这就需要不停的调用 os.chdir:

代码语言:javascript
复制
import os

origin_path = os.getcwd()

os.chdir(new_path)
do_something()
os.chdir(origin_path)

...

os.chdir(new_path2)
do_something2()
os.chdir(origin_path)
...

如果切换多次,你会看到满天飞的 os.chdir,这一点也不 Pythonic。接下来分享两种自定义上下文管理器的方法,然后你就会知道上下文管理器(contextlib) 的优雅和便捷。

方法一、使用 contextlib.contextmanager 装饰器

代码语言:javascript
复制
from contextlib import contextmanager
import os

@contextmanager
def change_path(new_path):
    origin_path = os.getcwd()
    os.chdir(new_path)
    try:
        yield os.getcwd()
    finally:
        os.chdir(origin_path)

if __name__ == "__main__":
    print(f"with 之前的目录 {os.getcwd()}")
    with change_path("/Users/aaron/tmp") as new_path:
        print(f"with 之中的目录 {os.getcwd()}")
        print("do something...")
    print(f"with 之后的目录 {os.getcwd()}")

上面的代码中 change_path 就是一个上下文管理器,在 mian 中,完全不需要对目录切换来切换去,运行结果如下:

代码语言:javascript
复制
with 之前的目录 /Users/aaron/gitee/somenzz
with 之中的目录 /Users/aaron/tmp
do something...
with 之后的目录 /Users/aaron/gitee/somenzz

方法二、使用类来实现装饰器

代码语言:javascript
复制
import os

class ChangePath:
    def __init__(self, new_path) -> None:
        self.new_path = new_path

    def __enter__(self):
        self.origin_path = os.getcwd()
        os.chdir(self.new_path)
        return os.getcwd()
    
    def __exit__(self,exc_type,exc_value,traceback):
        os.chdir(self.origin_path)


if __name__ == "__main__":
    print(f"with 之前的目录 {os.getcwd()}")
    with ChangePath("/Users/aaron/tmp") as new_path:
        print(f"with 之中的目录 {os.getcwd()}")
        print("do something...")
    print(f"with 之后的目录 {os.getcwd()}")

可以看出,只要一个类实现了 __enter____exit__ 方法,就可以当做上下文管理器。运行结果是一样的:

代码语言:javascript
复制
with 之前的目录 /Users/aaron/gitee/somenzz
with 之中的目录 /Users/aaron/tmp
do something...
with 之后的目录 /Users/aaron/gitee/somenzz

哪种方法更快?

测试之前,我直觉认为,方法二会稍微快一点,因为我很清楚进入和离开 contextlib 时,程序都做了些什么。不过还是用事实说话,测试完整代码:

代码语言:javascript
复制
from contextlib import contextmanager
import os
from timeit import timeit


@contextmanager
def change_path(new_path):
    origin_path = os.getcwd()
    os.chdir(new_path)
    try:
        yield os.getcwd()
    finally:
        os.chdir(origin_path)


class ChangePath:
    def __init__(self, new_path) -> None:
        self.new_path = new_path

    def __enter__(self):
        self.origin_path = os.getcwd()
        os.chdir(self.new_path)
        return os.getcwd()

    def __exit__(self, exc_type, exc_value, traceback):
        os.chdir(self.origin_path)


def method1():
    os.getcwd()
    with change_path("/Users/aaron/tmp") as new_path:
        os.getcwd()
    os.getcwd()


def method2():
    os.getcwd()
    with ChangePath("/Users/aaron/tmp") as new_path:
        os.getcwd()
    os.getcwd()


if __name__ == "__main__":
    number = 1000
    x1 = timeit(
        "method1()", setup="from __main__ import method1", number=number
    )
    x2 = timeit(
        "method2()", setup="from __main__ import method2", number=number
    )
    print(f"方法一执行 {number} 次耗时 {x1 :.8f}")
    print(f"方法二执行 {number} 次耗时 {x2 :.8f}")

我执行了三次,分别测试了 1000,10000,100000 次,均是方法二更快:

最后的话

进入代码前需要做一些预处理,离开后需要再次处理的时候,上下文管理器就可以出场了,如果这样的处理变多的时候,你会发现 contextlib 真的是个好东西,大大减轻了程序员的心智和记忆负担。本文分享了两种实现 contextlib 的方法,推荐使用类来实现 contextlib。

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

本文分享自 Python七号 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 方法一、使用 contextlib.contextmanager 装饰器
  • 方法二、使用类来实现装饰器
  • 哪种方法更快?
  • 最后的话
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档