如何在fileinput.input中使用yaml.load_all?

时间:2016-09-06 23:52:22

标签: python pyyaml

不使用''.join,是否有Pythonic方式使用PyYAML的yaml.load_allfileinput.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

2 个答案:

答案 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也是派生超集。