使用io.TextIOWrapper包装一个开放流

发布于 2021-01-29 18:10:43

如何将打开的二进制流(Python 2 file,Python
3io.BufferedReader和an)包装io.BytesIO在中io.TextIOWrapper

我正在尝试编写将保持不变的代码:

  • 在Python 2上运行。
  • 在Python 3上运行。
  • 使用从标准库生成的二进制流(即,我无法控制它们是什么类型)
  • 将二进制流设为测试双倍(即没有文件句柄,无法重新打开)。
  • 产生一个io.TextIOWrapper包装指定流的。

io.TextIOWrapper之所以需要,是因为标准库的其他部分需要使用它的API。存在其他类似文件的类型,但是没有提供正确的API。

包装作为subprocess.Popen.stdout属性显示的二进制流:

import subprocess
import io

gnupg_subprocess = subprocess.Popen(
        ["gpg", "--version"], stdout=subprocess.PIPE)
gnupg_stdout = io.TextIOWrapper(gnupg_subprocess.stdout, encoding="utf-8")

在单元测试中,用io.BytesIO实例替换流以控制其内容,而不用触摸任何子进程或文件系统。

gnupg_subprocess.stdout = io.BytesIO("Lorem ipsum".encode("utf-8"))

在Python 3的标准库创建的流上,效果很好。但是,相同的代码在Python 2生成的流上失败:

[Python 2]
>>> type(gnupg_subprocess.stdout)
<type 'file'>
>>> gnupg_stdout = io.TextIOWrapper(gnupg_subprocess.stdout, encoding="utf-8")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'file' object has no attribute 'readable'

不是解决方案: file

一个明显的响应是在代码中具有一个分支,以测试流是否实际上是Python 2file对象,并以与io.*对象不同的方式处理该对象。

这不是经过良好测试的代码的选项,因为它会生成一个要进行单元测试的分支-为了尽快运行,该分支不得创建任何 真实的 文件系统对象-不能执行。

单元测试将提供测试加倍,而不是真实的file对象。因此,创建那些测试双打不会使用的分支将破坏测试套件。

没有解决方案: io.open

一些受访者建议重新打开(例如使用io.open)基础文件句柄:

gnupg_stdout = io.open(
        gnupg_subprocess.stdout.fileno(), mode='r', encoding="utf-8")

在Python 3和Python 2上均可使用

[Python 3]
>>> type(gnupg_subprocess.stdout)
<class '_io.BufferedReader'>
>>> gnupg_stdout = io.open(gnupg_subprocess.stdout.fileno(), mode='r', encoding="utf-8")
>>> type(gnupg_stdout)
<class '_io.TextIOWrapper'>



[Python 2]
>>> type(gnupg_subprocess.stdout)
<type 'file'>
>>> gnupg_stdout = io.open(gnupg_subprocess.stdout.fileno(), mode='r', encoding="utf-8")
>>> type(gnupg_stdout)
<type '_io.TextIOWrapper'>

但是,当然,它 依赖于 从文件句柄中 重新打开真实文件
。因此,当测试double是一个io.BytesIO实例时,它在单元测试中将失败:

>>> gnupg_subprocess.stdout = io.BytesIO("Lorem ipsum".encode("utf-8"))
>>> type(gnupg_subprocess.stdout)
<type '_io.BytesIO'>
>>> gnupg_stdout = io.open(gnupg_subprocess.stdout.fileno(), mode='r', encoding="utf-8")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
io.UnsupportedOperation: fileno

没有解决方案: codecs.getreader

标准库还具有该codecs模块,该模块提供包装器功能:

import codecs

gnupg_stdout = codecs.getreader("utf-8")(gnupg_subprocess.stdout)

很好,因为它不会尝试重新打开流。但是它无法提供io.TextIOWrapperAPI。具体来说,它 不继承io.IOBase并且
不具有encoding属性

>>> type(gnupg_subprocess.stdout)
<type 'file'>
>>> gnupg_stdout = codecs.getreader("utf-8")(gnupg_subprocess.stdout)
>>> type(gnupg_stdout)
<type 'instance'>
>>> isinstance(gnupg_stdout, io.IOBase)
False
>>> gnupg_stdout.encoding
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/codecs.py", line 643, in __getattr__
    return getattr(self.stream, name)
AttributeError: '_io.BytesIO' object has no attribute 'encoding'

因此codecs不提供替代的对象io.TextIOWrapper

该怎么办?

那么,如何通过测试倍数和真实对象编写适用于Python 2和Python 3的代码,这些对象 将已经打开的字节流
包裹起来io.TextIOWrapper

关注者
0
被浏览
53
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

    基于各种论坛上的多项建议,并尝试使用标准库来满足标准,我目前的结论是 这无法 像我们目前所拥有的 那样 使用库和类型 来完成



知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看