StringIO和与'with'语句的兼容性(上下文管理器)

时间:2012-08-09 22:02:30

标签: python python-2.x with-statement contextmanager

我有一些带遗留函数的遗留代码,它以文件名作为参数并处理文件内容。代码的工作传真如下。

我想要做的是不必使用我生成的某些内容写入磁盘以使用此遗留函数,因此我可以使用StringIO来创建对象来代替物理文件名。但是,这不起作用,如下所示。

我认为StringIO是这样的方式。任何人都可以告诉我是否有一种方法可以使用这个遗留函数并在参数中传递一些不是磁盘上的文件但可以被遗留函数处理的东西?遗留函数确实让with上下文管理器处理filename参数值。

我在谷歌遇到的一件事是:http://bugs.python.org/issue1286,但这对我没有帮助......

代码

from pprint import pprint
import StringIO

    # Legacy Function
def processFile(filename):
    with open(filename, 'r') as fh:
        return fh.readlines()

    # This works
print 'This is the output of FileOnDisk.txt'
pprint(processFile('c:/temp/FileOnDisk.txt'))
print

    # This fails
plink_data = StringIO.StringIO('StringIO data.')
print 'This is the error.'
pprint(processFile(plink_data))

输出

这是FileOnDisk.txt中的输出:

['This file is on disk.\n']

这是错误:

Traceback (most recent call last):
  File "C:\temp\test.py", line 20, in <module>
    pprint(processFile(plink_data))
  File "C:\temp\test.py", line 6, in processFile
    with open(filename, 'r') as fh:
TypeError: coercing to Unicode: need string or buffer, instance found

3 个答案:

答案 0 :(得分:57)

StringIO实例已经打开了一个文件。另一方面,open命令只接受文件名,以返回打开的文件。 StringIO实例不适合作为文件名。

此外,您无需关闭StringIO实例,因此无需将其用作上下文管理器。

如果所有旧代码都可以使用文件名,那么StringIO实例就不是了。使用tempfile module代替生成临时文件名。

以下是使用contextmanager确保之后清除临时文件的示例:

import os
import tempfile
from contextlib import contextmanager

@contextmanager
def tempinput(data):
    temp = tempfile.NamedTemporaryFile(delete=False)
    temp.write(data)
    temp.close()
    try:
        yield temp.name
    finally:
        os.unlink(temp.name)

with tempinput('Some data.\nSome more data.') as tempfilename:
    processFile(tempfilename)

您还可以切换到io模块提供的较新的Python 3基础架构(在Python 2和3中提供),其中io.BytesIOStringIO.StringIO / {的更强大的替代品{1}}。此对象支持用作上下文管理器(但仍无法传递给cStringIO.StringIO)。

答案 1 :(得分:6)

你可以定义自己的开放功能

fopen = open
def open(fname,mode):
    if hasattr(fname,"readlines"): return fname
    else: return fopen(fname,mode)

但是想要在完成后调用__exit__并且StringIO没有退出方法......

您可以定义要与此打开的

一起使用的自定义类
class MyStringIO:
     def __init__(self,txt):
         self.text = txt
     def readlines(self):
          return self.text.splitlines()
     def __exit__(self):
          pass

答案 2 :(得分:3)

这是基于contextmanager的python文档

它只是用简单的上下文包装StringIO,当调用 exit 时,它将返回到屈服点,并正确关闭StringIO。这样就避免了创建tempfile的需要,但是对于大字符串,由于StringIO缓冲了该字符串,因此仍然会占用内存。 在大多数您知道字符串数据不会很长的情况下,它都能很好地工作

from contextlib import contextmanager

@contextmanager
def buildStringIO(strData):
    from cStringIO import StringIO
    try:
        fi = StringIO(strData)
        yield fi
    finally:
        fi.close()

那么您可以做:

with buildStringIO('foobar') as f:
    print(f.read()) # will print 'foobar'