V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
zictos
V2EX  ›  Python

Python 读写文件时不用 with 也不关闭文件,这样的风格好吗?

  •  
  •   zictos · 2021-03-27 12:13:58 +08:00 · 4867 次点击
    这是一个创建于 1335 天前的主题,其中的信息可能已经有所发展或是发生改变。

    读文件:

    text = open('test.txt', 'r', encoding='utf-8').read()
    

    写文件:

    open('test.txt', 'w', encoding='utf-8').write('123')
    

    遍历文件:

    for i in open('test.txt', 'r', encoding='utf-8'):
        print(i)
    

    .
    .

    发现有时候只是临时读一下文件的话这样方便不少。

    如果打开文件,但没有把文件对象赋值到变量的话是不需要关闭文件的
    比如输入下面一行代码的时候去删除 test.txt 文件时无法删除,提示被占用

    f = open('test.txt', 'r', encoding='utf-8')  
    

    但实际上并不需要输入 f.close(),比如直接给 f 赋值成别的数据类型 f=1,再去删除文件就不会提示占用了

    f = 1  
    
    39 条回复    2021-03-30 05:17:48 +08:00
    ClericPy
        1
    ClericPy  
       2021-03-27 12:19:59 +08:00   ❤️ 1
    Explicit is better than implicit.
    learningman
        2
    learningman  
       2021-03-27 12:21:20 +08:00 via Android
    都有上下文管理器这么好用的东西了,你还嫌麻烦。。。
    要不再封装成一个函数,传入文件名返回值,内部做好处理。
    AndyAO
        3
    AndyAO  
       2021-03-27 12:22:21 +08:00
    临时读一下文件没必要关闭,就那点资源根本就没必要释放
    v2sir
        4
    v2sir  
       2021-03-27 12:22:46 +08:00
    f = 1
    触发了 python 回收机制
    ljpCN
        5
    ljpCN  
       2021-03-27 12:24:41 +08:00 via iPhone
    得看下 f.close 到底做了什么,直觉上感觉不调用会内存泄漏。另外,更想知道的是用 with 和手动调 close 哪个是更好的实践呢?前者少写了代码省心了但多了一层缩进。如果要在过程中打开多个文件就缩得更多。
    geelaw
        6
    geelaw  
       2021-03-27 12:25:17 +08:00 via iPhone   ❤️ 27
    这样的写法在标准 Python 中可以导致文件在进程结束前不被关闭,因为在内存充足的时候,垃圾回收器可以选择完全不回收不可达对象。

    在目前的 CPython 实现中,这种写法可以保证文件立刻被关闭,因为 CPython 采用引用计数来辅助管理内存,引用计数到达 0 的时候会立刻回收,从而关闭文件。

    在 IronPython 中,则不一定,现在 CLR 提供要求暂时不运行 GC 的 API,如果在运行这段代码前,有人 commit 了足够的内存要求不运行 GC,且这段代码之后程序不再分配内存,则文件永远不会被关闭。

    不要面向巧合编程。
    GrayXu
        7
    GrayXu  
       2021-03-27 12:25:43 +08:00
    取决于场景,如果不需要在某个时刻保证资源被释放,那隐式完成其实也可以吧。。
    lqf96
        8
    lqf96  
       2021-03-27 12:26:07 +08:00
    @geelaw 都不需要 IronPython,PyPy 就已经火葬场了...
    zictos
        9
    zictos  
    OP
       2021-03-27 12:31:41 +08:00
    如果仅仅是用 open('test.txt', 'r', encoding='utf-8')打开文件,但没有做任何操作,发现 test.txt 文件是无法删除的。但只要做了别的操作或者一开始赋值给了变量后续又改变了变量的数据类型就可以删除了。
    zictos
        10
    zictos  
    OP
       2021-03-27 12:35:00 +08:00
    @ljpCN #5 问题在于文件能够删除了就肯定说明内存已经释放了
    有时候我反倒不想看到 with 的缩进,因为我就是读取一下文件,本来也一两行代码就可以搞定。只是用 with 的确不容易忘记 close,效果应该是一样的。
    Contextualist
        11
    Contextualist  
       2021-03-27 12:41:39 +08:00   ❤️ 3
    如果只是一次性读 / 写,建议使用 pathlib:
    from pathlib import Path
    Path('test.txt').write_text('123', encoding='utf-8')
    text = Path('test.txt').read_text(encoding='utf-8')

    这些方法都是自带关闭文件的。
    zhuangzhuang1988
        12
    zhuangzhuang1988  
       2021-03-27 12:42:05 +08:00
    接手的人 看了要流泪
    webshe11
        13
    webshe11  
       2021-03-27 12:42:14 +08:00
    @lqf96 #8 试了一下,确实,macOS 下 lsof 发现 PyPy 7.3.3 不会自动关闭,CPython 3.9.2 2.7.17 都会自动关闭文件
    个人觉得就写个 with 再缩进一下不算麻烦,如果实在嫌麻烦可以封装个 file_get_contents() 函数(滑稽)
    zictos
        14
    zictos  
    OP
       2021-03-27 12:45:50 +08:00
    @webshe11 #13 那看来还是跟解释器有关,兼容型不是很好。不过自己使用倒是没问题。
    因为我的需求也重点是用在小脚本,比如有时候打算写一个本来就只有十几行的脚本,或者在安卓手机上用 termux 运行 python,这样比较方便。
    kikikiabc
        15
    kikikiabc  
       2021-03-27 12:46:51 +08:00 via iPhone
    > 要不再封装成一个函数,传入文件名返回值,内部做好处理?

    有,自带的 pathlib 已经封装好了的,确实是“传入文件名返回值”, 很好用
    hxy100
        16
    hxy100  
       2021-03-27 12:51:45 +08:00   ❤️ 1
    @webshe11 PHP 乱入?
    xiaolinjia
        17
    xiaolinjia  
       2021-03-27 12:58:03 +08:00   ❤️ 1
    在流畅的 python 一书中有提到:
    CPython 中的垃圾回收主要依靠引用计数,这容易实现,但是遇到引用循环容易泄露
    内存,因此 CPython 2.0 ( 2000 年 10 月发布)实现了分代垃圾回收程序,它能把引用
    循环中不可获取的对象销毁。
    但是引用计数仍然作为一种基准存在,一旦引用数量归零,就立即销毁对象。这意味
    着,在 CPython 中,这样写是安全的(至少目前如此):
    open('test.txt', 'wt', encoding='utf-8').write('1, 2, 3')
    这行代码是安全的,因为文件对象的引用数量会在 write 方法返回后归零,Python
    在销毁内存中表示文件的对象之前,会立即关闭文件。然而,这行代码在 Jython 或
    IronPython 中却不安全,因为它们使用的是宿主运行时( Java VM 和 .NET CLR )中的
    垃圾回收程序,那些回收程序更复杂,但是不依靠引用计数,而且销毁对象和关闭文
    件的时间可能更长。在任何情况下,包括 CPython,最好显式关闭文件;而关闭文件
    的最可靠方式是使用 with 语句,它能保证文件一定会被关闭,即使打开文件时抛出
    了异常也无妨。
    AndyAO
        18
    AndyAO  
       2021-03-27 13:11:17 +08:00
    单纯是为了方便的话可以用 With 来写个 Snippet
    johnsona
        19
    johnsona  
       2021-03-27 13:21:45 +08:00 via iPhone
    @v2sir 好家伙 你这垃圾回收机制都学以致用了
    imn1
        20
    imn1  
       2021-03-27 13:46:03 +08:00
    “老子写的程序绝对没有 bug,也绝对不会耗时,锁文件又如何,爱咋咋地”
    ipwx
        21
    ipwx  
       2021-03-27 13:49:00 +08:00
    楼主已经有答案了,他承认不符合语言标准,但是就是爱这么干。你们回帖有啥意义?散了散了。。。
    zictos
        22
    zictos  
    OP
       2021-03-27 13:54:55 +08:00
    @Contextualist #11 pathlib 能直接遍历每一行(针对大文件)吗?
    zictos
        23
    zictos  
    OP
       2021-03-27 13:58:46 +08:00
    @ipwx #21 其实如果没有其他坑的话这样用也没问题,但目前看来是有坑的。只是我不发这个帖子的话我以为不会有坑的
    ipwx
        24
    ipwx  
       2021-03-27 13:59:08 +08:00
    @zictos 想要遍历每一行又能确保关闭文件,你自己写个函数呗:

    def iter_lines(path, encoding=None):
    ....f = open(path, 'r', encoding=encoding)
    ....try:
    ........for line in f:
    ............yield line
    ....finally:
    ........f.close()
    zictos
        25
    zictos  
    OP
       2021-03-27 14:06:49 +08:00
    @ipwx #24 只是要保证随时能用,不会每次都去写函数。如果放到 python 第三方模块的文件夹的话每次使用时引入也可以,只是不能保证去任何有 python 环境的电脑都能用。
    ipwx
        26
    ipwx  
       2021-03-27 14:10:26 +08:00
    @zictos 要求真多。with 不过多一行。问就是标准库没有
    abersheeran
        27
    abersheeran  
       2021-03-27 14:12:33 +08:00
    @zictos 楼上说的 pathlib 就是标准库。任何 Python3 的环境你都可以用。
    zictos
        28
    zictos  
    OP
       2021-03-27 14:15:57 +08:00
    @ipwx #26 嗯,with 也没多麻烦,用其他途径去弄最后可能比 with 还麻烦。
    只是看到有人推荐 pathlib,而我题中也说了遍历的例子,所以才随口问一下 pathlib 是否能遍历而已,也不是一定要用 pathlib 遍历
    zictos
        29
    zictos  
    OP
       2021-03-27 14:18:46 +08:00
    @abersheeran #27 嗯,但 pathlib 还是不一样的,不能直接遍历。读取或写入倒是没问题,以后读取或写入可以选择用 pathlib
    crclz
        30
    crclz  
       2021-03-27 16:08:52 +08:00
    如果你嫌 with 多了一层缩进的话,C#的 using 很满足你的需求。using var reader = xxx;
    laike9m
        31
    laike9m  
       2021-03-27 16:52:52 +08:00 via Android
    这个问题还有争议也是我没想到的。。
    chenqh
        32
    chenqh  
       2021-03-27 17:42:57 +08:00
    @crclz python 的 with,不就相当于 C#的 using(){}吗?
    mrchi
        33
    mrchi  
       2021-03-27 22:41:27 +08:00   ❤️ 1
    @ljpCN 多个文件不必多层缩进,参考文档: https://docs.python.org/3/reference/compound_stmts.html#the-with-statement

    with A() as a, B() as b:
    SUITE
    Contextualist
        34
    Contextualist  
       2021-03-28 00:06:55 +08:00
    @zictos #22 pathlib 不会提供像 @ipwx #24 描述的文件迭代器。考虑一下下面的代码:
    try:
    for l in iter_lines('test.txt'):
    print(l[100])
    except:
    pass

    如果 for 循环里的语句抛出了异常,程序会继续执行,但是因为 iter_lines 生成器没有执行完,里面的文件不会被关闭。问题的根本原因是:没有办法能 自动 将外层的异常传递进入内层。这就是上下文管理器 / with 语句设计要解决的问题之一。用 with open,如果 for 循环里的语句抛出了异常导致跳出了 with 的范围,文件会被自动关闭。
    ipwx
        35
    ipwx  
       2021-03-28 01:23:01 +08:00
    @Contextualist 你的认知是错误的。

    (Python 3.7.3)

    看到输出的那行 finally 了么?
    ----

    事实上一个自定义的 with contextmanager 就得通过这种方法写,譬如:

    @contextlib.contextmanager
    def my_context():
    ....try:
    ........yield
    ....finally:
    ........do some cleanup

    with my_context():
    ....raise Error(...)
    Contextualist
        36
    Contextualist  
       2021-03-28 02:17:50 +08:00
    @ipwx 啊,感谢提出!仔细研究了一下,这个情况其实也是出于对 gc 机制的利用。如果你试着把这个生成器实例赋给一个变量(避免 gc ),然后再调用它,像这样:
    it = iter_lines([1,2,3])
    for l in it:
      if l == 2:
         raise ValueError('xxx')

    finally 就不会被触发。你的例子中,finally 执行的原因是生成器实例在 for 循环后被 gc 了,Python 的实现要求没执行完的生成器被 gc 时必须执行 finally,详见 https://docs.python.org/3/reference/expressions.html#yieldexpr

    至于 contextlib.contextmanager,那是因为 Python 的库将 with 捕获的异常手动传入了生成器,详见 https://github.com/python/cpython/blob/7990072999b7e9b4ef6b1f6bb376d441a5a41d74/Lib/contextlib.py#L135
    ipwx
        37
    ipwx  
       2021-03-28 12:13:58 +08:00
    @Contextualist Oh 多谢提示,我还没注意过 .throw()。主要是我自己写自己的库一般用 .close() 来处理 for iter ....,所以也不会受引用计数的 gc 限制。



    看了看 .close() 八成等于 .throw(StopIteration)
    geelaw
        38
    geelaw  
       2021-03-29 20:38:01 +08:00
    @Contextualist #36
    @ipwx #37

    我想象中的

    for A in B:
    ..body

    的展开是

    TMP = B.__iter__() # 编译器生成的迭代器的 __exit__ 会执行剩余的 finally
    with TMP:
    ..while True:
    ....A = TMP.__next__()
    ....body

    即和 C# 里的 foreach 展开包含 finally IDisposable.Dispose 一样。
    Contextualist
        39
    Contextualist  
       2021-03-30 05:17:48 +08:00
    @geelaw 虽然现在的 Python 里并没有这样的语法,但是其实 with 上下文里用 for 循环(即显式指定任何情况下跳出循环都需要 clean up )还的确是个常见的模式(例如 Trio 里的 channel: https://trio.readthedocs.io/en/stable/reference-core.html#clean-shutdown-with-channels )。如果真的是常见模式并且不考虑将语法变得冗杂,不妨提议 Python 加个 for ... in with ...: 的语法 😄
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5792 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 02:26 · PVG 10:26 · LAX 18:26 · JFK 21:26
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.