2016-09-06 110 views
4

如果不采用''.join,PyyAML的yaml.load_allfileinput.input()是否有Pythonic的方式来轻松地从多个来源流式传输多个文档?如何将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,但那是作弊。我正在寻找一种不会将整个流一次读入内存的方式。

我们可能改写这个问题作为是否有某种方式来模拟一个字符串,字节,或类似文件的对象,代理为一个字符串底层迭代器?但是,我怀疑yaml.load_all实际上需要整个类似文件的界面,因此,措辞会要求超过严格需要。

理想我正在寻找最小的适配器,将支持这样的事情:

for doc in yaml.load_all(minimal_adapter(fileinput.input())): 
    print(doc) 

回答

3

您的minimal_adapter应以fileinput.FileInput作为参数并返回load_all可以使用的对象。 load_all或者以一个字符串作为参数,但这需要连接输入,或者期望该参数具有read()方法。

由于您minimal_adapter需要保持一定的状态,我觉得最明显的/最容易实现其作为具有__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) 

有了这个,运行你的例子调用正好给你想要的输出。

对于较大的文件,这可能只有更高的内存效率。load_all尝试一次读取1024个字节块(通过在MinimalAdapter.read()中放置打印语句很容易找到),并且fileinput也会执行一些缓冲(如果您有兴趣了解其行为方式,请使用strace)。


这是使用ruamel.yaml一个YAML 1.2分析器,其中我的作者来完成。这应该适用于PyYAML,其中ruamel.yaml也是派生的超集。

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

这是一个很好的答案,我希望我可以标记多个答案接受(“✅Acceptable”?);然而,另一个解决方案重新使用'fileinput'并不需要重新实现或替换它,我认为这更接近这个问题的最小意图。我可以看到这个答案如何满足不同类型的最小尽管,所以谢谢你的贡献! –

相关问题