不使用''.join
,是否有Pythonic方式使用PyYAML的yaml.load_all
和fileinput.input()
来轻松地从多个来源流式传输多个文档?
我正在寻找类似以下内容(非工作示例):
# example.py
import fileinput
import yaml
for doc in yaml.load_all(fileinput.input()):
print(doc)
预期产出:
$ cat >pre.yaml <<<'--- prefix-doc'
$ cat >post.yaml <<<'--- postfix-doc'
$ python example.py pre.yaml - post.yaml <<<'--- hello'
prefix-doc
hello
postfix-doc
当然,yaml.load_all
要求字符串,字节或类似文件的对象和fileinput.input()
都不是这些,所以上面的例子不起作用。
实际输出:
$ python example.py pre.yaml - post.yaml <<<'--- hello'
...
AttributeError: FileInput instance has no attribute 'read'
你可以让这个例子与''.join
一起使用,但这是作弊。我正在寻找一种不会立即将整个流读入内存的方法。
我们可能会将问题改为是否有某种方法可以模拟字符串,字节或类似文件的对象代理字符串的底层迭代器?然而,我怀疑{{1}实际上需要整个类似文件的界面,因此短语要求的不仅仅是严格必要的。
理想情况下,我正在寻找支持这样的最小适配器:
yaml.load_all
答案 0 :(得分:4)
fileinput.input
的问题在于,生成的对象没有read
方法,这是yaml.load_all
正在寻找的方法。如果您愿意放弃fileinput
,您可以编写自己的课程来做您想做的事情:
import sys
import yaml
class BunchOFiles (object):
def __init__(self, *files):
self.files = files
self.fditer = self._fditer()
self.fd = self.fditer.next()
def _fditer(self):
for fn in self.files:
with sys.stdin if fn == '-' else open(fn, 'r') as fd:
yield fd
def read(self, size=-1):
while True:
data = self.fd.read(size)
if data:
break
else:
try:
self.fd = self.fditer.next()
except StopIteration:
self.fd = None
break
return data
bunch = BunchOFiles(*sys.argv[1:])
for doc in yaml.load_all(bunch):
print doc
BunchOFiles
类为您提供一个read
方法的对象,该方法可以很好地迭代文件列表,直到所有内容都用完为止。鉴于上面的代码和您的示例输入,我们得到您正在寻找的输出。
答案 1 :(得分:3)
您的minimal_adapter
应该以{{1}}为参数,并返回fileinput.FileInput
可以使用的对象。 load_all
要么将字符串作为参数,要求连接输入,或者期望参数具有load_all
方法。
由于你的minimal_adapter需要保留一些状态,我发现它最清楚/最容易实现它作为具有read()
方法的类的实例,并让该方法返回实例并存储其参数以备将来使用使用。以这种方式实现,该类还应该有一个__call__
方法,因为在将实例交给read()
后会调用它:
load_all
通过这种方式,运行示例调用可以精确地提供所需的输出。
对于较大的文件,这可能只会提高内存效率。 import fileinput
import ruamel.yaml
class MinimalAdapter:
def __init__(self):
self._fip = None
self._buf = None # storage of read but unused material, maximum one line
def __call__(self, fip):
self._fip = fip # store for future use
self._buf = ""
return self
def read(self, size):
if len(self._buf) >= size:
# enough in buffer from last read, just cut it off and return
tmp, self._buf = self._buf[:size], self._buf[size:]
return tmp
for line in self._fip:
self._buf += line
if len(self._buf) > size:
break
else:
# ran out of lines, return what we have
tmp, self._buf = self._buf, ''
return tmp
tmp, self._buf = self._buf[:size], self._buf[size:]
return tmp
minimal_adapter = MinimalAdapter()
for doc in ruamel.yaml.load_all(minimal_adapter(fileinput.input())):
print(doc)
尝试一次读取1024个字节的块(通过在load_all
中放置一个打印语句很容易找到),而MinimalAdapter.read()
也会尝试一些缓冲(如果fileinput
使用strace
你有兴趣了解它的表现。)
这是使用ruamel.yaml YAML 1.2解析器完成的,我是作者。这适用于PyYAML,其中ruamel.yaml也是派生超集。