如果我在python 3中运行以下代码
from io import BytesIO
import csv
from io import TextIOWrapper
def fill_into_stringio(input_io):
writer = csv.DictWriter(TextIOWrapper(input_io, encoding='utf-8'),fieldnames=['ids'])
for i in range(100):
writer.writerow({'ids': str(i)})
with BytesIO() as input_i:
fill_into_stringio(input_i)
input_i.seek(0)
我收到一个错误:
ValueError: I/O operation on closed file.
如果我不使用TextIOWrapper,io流将保持打开状态。例如,如果我将我的函数修改为
def fill_into_stringio(input_io):
for i in range(100):
input_io.write(b'erwfewfwef')
我不再收到任何错误,因此由于某种原因,TestIOWrapper 正在关闭我想从中阅读的流。这是打算这样吗,是否有办法在不自己编写 csv 作家的情况下实现我正在尝试的事情?
csv
模块在这里很奇怪;大多数包装其他对象的类文件对象都拥有相关对象的所有权,在它们自己关闭(或以其他方式清理)时关闭它。
避免此问题的一种方法是在允许清理TextIOWrapper
之前显式detach
:
def fill_into_stringio(input_io):
# write_through=True prevents TextIOWrapper from buffering internally;
# you could replace it with explicit flushes, but you want something
# to ensure nothing is left in the TextIOWrapper when you detach
text_input = TextIOWrapper(input_io, encoding='utf-8', write_through=True)
try:
writer = csv.DictWriter(text_input, fieldnames=['ids'])
for i in range(100):
writer.writerow({'ids': str(i)})
finally:
text_input.detach() # Detaches input_io so it won't be closed when text_input cleaned up
避免这种情况的唯一其他内置方法是对于实际文件对象,您可以在其中向它们传递文件描述符并closefd=False
,并且在close
-ed 或以其他方式清理时它们不会关闭基础文件描述符。
当然,在你的特定情况下,有更简单的方法:只需让你的函数期待基于文本的类似文件的对象,并在不重新包装的情况下使用它们;你的函数真的不应该负责对调用者的输出文件强加编码(如果调用方想要 UTF-16 输出怎么办?)。
然后你可以做:
from io import StringIO
def fill_into_stringio(input_io):
writer = csv.DictWriter(input_io, fieldnames=['ids'])
for i in range(100):
writer.writerow({'ids': str(i)})
# newline='' is the Python 3 way to prevent line-ending translation
# while continuing to operate as text, and it's recommended for any file
# used with the csv module
with StringIO(newline='') as input_i:
fill_into_stringio(input_i)
input_i.seek(0)
# If you really need UTF-8 bytes as output, you can make a BytesIO at this point with:
# BytesIO(input_i.getvalue().encode('utf-8'))
我在尝试从子进程和线程将标准输出重定向到 PyQT5 中的 GUI 文本框时得到了相同的ValueError: I/O operation on closed file.
。应用程序略有不同,但存在相同的潜在错误。基本上,垃圾回收器正在删除 TextIOWrapper() 对象,当不再有对它的引用时,它会关闭基础流作为此过程的一部分。
函数执行后,代码中不再有对 TextIOWrapper 对象的引用,因此垃圾回收器会在执行input_i.seek(0)
之前将其删除。作为删除 TextIOWrapper 对象的一部分,垃圾回收器将关闭包装的缓冲区。当您再次访问包装的流时,流将被关闭并引发错误。
鉴于这种行为,我认为用 TextIOWrapper 包装 stdout 缓冲区通常是一个坏主意,因为一旦垃圾回收删除了 TextIOWrapper 对象,它就会关闭您的原始标准输出流。
就我而言,我子类化了 StringIO 以使写入方法触发附加到我的其他文本框的 pyqt 信号(用于允许线程安全数据传输的信号),因为 StringIO 具有自己的内存缓冲区,不会影响底层缓冲区(例如 stdout)并在垃圾回收期间意外关闭它。我可能会在进一步考虑后在 io 中子类化一个抽象基类,也许下次。
class StdOutRedirector(io.StringIO):
def __init__(self, update_ui: pyqtSignal):
super().__init__()
self.update_ui = update_ui
def write(self, string):
self.update_ui.emit(string)
try:
sys.stdout = StdOutRedirector(self.send_text)
doSomeStuffWithRedirectedStdout()
except Exception as error:
tell_user("bug happened")
finally:
sys.stdout = sys.__stdout__