簡體   English   中英

調整迭代器,使其行為類似於 Python 中的文件 object

[英]Adapt an iterator to behave like a file-like object in Python

我有一個生成器生成字符串列表。 Python 中是否有一個實用程序/適配器可以使它看起來像一個文件?

例如,

>>> def str_fn():
...     for c in 'a', 'b', 'c':
...         yield c * 3
... 
>>> for s in str_fn():
...     print s
... 
aaa
bbb
ccc
>>> stream = some_magic_adaptor(str_fn())
>>> while True:
...    data = stream.read(4)
...    if not data:
...        break
...    print data
aaab
bbcc
c

因為數據可能很大並且需要流式傳輸(每個片段是幾千字節,整個 stream 是幾十兆字節),我不想在將整個生成器傳遞給 stream 適配器之前急切地評估整個生成器。

執行此操作的“正確”方法是從標准Python io抽象基類繼承。 但是,似乎Python不允許您提供原始文本類,並使用任何類型的緩沖讀取器包裝它。

繼承的最佳類是TextIOBase 這是一個實現,處理readlineread同時注意性能。 要點

import io

class StringIteratorIO(io.TextIOBase):

    def __init__(self, iter):
        self._iter = iter
        self._left = ''

    def readable(self):
        return True

    def _read1(self, n=None):
        while not self._left:
            try:
                self._left = next(self._iter)
            except StopIteration:
                break
        ret = self._left[:n]
        self._left = self._left[len(ret):]
        return ret

    def read(self, n=None):
        l = []
        if n is None or n < 0:
            while True:
                m = self._read1()
                if not m:
                    break
                l.append(m)
        else:
            while n > 0:
                m = self._read1(n)
                if not m:
                    break
                n -= len(m)
                l.append(m)
        return ''.join(l)

    def readline(self):
        l = []
        while True:
            i = self._left.find('\n')
            if i == -1:
                l.append(self._left)
                try:
                    self._left = next(self._iter)
                except StopIteration:
                    self._left = ''
                    break
            else:
                l.append(self._left[:i+1])
                self._left = self._left[i+1:]
                break
        return ''.join(l)

這是一個應該從塊中讀取迭代器的解決方案。

class some_magic_adaptor:
  def __init__( self, it ):
    self.it = it
    self.next_chunk = ""
  def growChunk( self ):
    self.next_chunk = self.next_chunk + self.it.next()
  def read( self, n ):
    if self.next_chunk == None:
      return None
    try:
      while len(self.next_chunk)<n:
        self.growChunk()
      rv = self.next_chunk[:n]
      self.next_chunk = self.next_chunk[n:]
      return rv
    except StopIteration:
      rv = self.next_chunk
      self.next_chunk = None
      return rv


def str_fn():
  for c in 'a', 'b', 'c':
    yield c * 3

ff = some_magic_adaptor( str_fn() )

while True:
  data = ff.read(4)
  if not data:
    break
  print data

StringIO的問題是您必須預先將所有內容加載到緩沖區中。 如果發電機是無限的,這可能是一個問題:)

from itertools import chain, islice
class some_magic_adaptor(object):
    def __init__(self, src):
        self.src = chain.from_iterable(src)
    def read(self, n):
        return "".join(islice(self.src, None, n))

有一個名為werkzeug.contrib.iterio.IterIO但請注意它將整個迭代器存儲在其內存中(直到您將其作為文件讀取),因此它可能不合適。

http://werkzeug.pocoo.org/docs/contrib/iterio/

資料來源: https//github.com/mitsuhiko/werkzeug/blob/master/werkzeug/contrib/iterio.py

readline / iter上的一個漏洞: https//github.com/mitsuhiko/werkzeug/pull/500

這是John和Matt的答案的修改版本,可以讀取字符串的列表/生成器並輸出字節數組

import itertools as it
from io import TextIOBase

class IterStringIO(TextIOBase):
    def __init__(self, iterable=None):
        iterable = iterable or []
        self.iter = it.chain.from_iterable(iterable)

    def not_newline(self, s):
        return s not in {'\n', '\r', '\r\n'}

    def write(self, iterable):
        to_chain = it.chain.from_iterable(iterable)
        self.iter = it.chain.from_iterable([self.iter, to_chain])

    def read(self, n=None):
        return bytearray(it.islice(self.iter, None, n))

    def readline(self, n=None):
        to_read = it.takewhile(self.not_newline, self.iter)
        return bytearray(it.islice(to_read, None, n))

用法:

ff = IterStringIO(c * 3 for c in ['a', 'b', 'c'])

while True:
    data = ff.read(4)

    if not data:
        break

    print data

aaab
bbcc
c

替代用法:

ff = IterStringIO()
ff.write('ddd')
ff.write(c * 3 for c in ['a', 'b', 'c'])

while True:
    data = ff.read(4)

    if not data:
        break

    print data

ddda
aabb
bccc

看看馬特的答案,我可以看到並不總是需要實現所有的讀取方法。 read1可能就足夠了,其描述如下:

讀取並返回大小字節,最多一次調用底層原始流的read()...

然后它可以用io.TextIOWrapper包裝,例如,它具有readline實現。 作為一個例子,這里是從S3(亞馬遜簡單存儲服務) boto.s3.key.Key流式傳輸CSV文件,它實現了讀取的迭代器。

import io
import csv

from boto import s3


class StringIteratorIO(io.TextIOBase):

    def __init__(self, iter):
        self._iterator = iter
        self._buffer = ''

    def readable(self):
        return True

    def read1(self, n=None):
        while not self._buffer:
            try:
                self._buffer = next(self._iterator)
            except StopIteration:
                break
        result = self._buffer[:n]
        self._buffer = self._buffer[len(result):]
        return result


conn = s3.connect_to_region('some_aws_region')
bucket = conn.get_bucket('some_bucket')
key = bucket.get_key('some.csv')    

fp = io.TextIOWrapper(StringIteratorIO(key))
reader = csv.DictReader(fp, delimiter = ';')
for row in reader:
    print(row)

更新

這是相關問題的答案 ,看起來好一點。 它繼承了io.RawIOBase並覆蓋了readinto 在Python 3中它已經足夠了,所以不是在io.BufferedReader中包裝IterStreamio.BufferedReader可以將它包裝在io.TextIOWrapper 在Python 2中需要read1 ,但它可以通過readinto簡單地表達。

這正是stringIO的用途。

>>> import StringIO
>>> some_var = StringIO.StringIO("Hello World!")
>>> some_var.read(4)
'Hell'
>>> some_var.read(4)
'o Wo'
>>> some_var.read(4)
'rld!'
>>>

或者,如果你想做它聽起來像

Class MyString(StringIO.StringIO):
     def __init__(self,*args):
         StringIO.StringIO.__init__(self,"".join(args))

那么你可以簡單

xx = MyString(*list_of_strings)

如果你只需要一個read方法,那么這就足夠了

def to_file_like_obj(iterable, base):
    chunk = base()
    offset = 0
    it = iter(iterable)

    def up_to_iter(size):
        nonlocal chunk, offset

        while size:
            if offset == len(chunk):
                try:
                    chunk = next(it)
                except StopIteration:
                    break
                else:
                    offset = 0
            to_yield = min(size, len(chunk) - offset)
            offset = offset + to_yield
            size -= to_yield
            yield chunk[offset - to_yield:offset]

    class FileLikeObj:
        def read(self, size=-1):
            return base().join(up_to_iter(float('inf') if size is None or size < 0 else size))

    return FileLikeObj()

可用於可迭代的產生str

my_file = to_file_like_object(str_fn, str)

或者,如果您有一個可迭代的產生bytes而不是str ,並且您想要一個類似文件的對象,其 read 方法返回bytes

my_file = to_file_like_object(bytes_fn, bytes)

我認為這種模式有一些不錯的特性:

  • 代碼不多, strbytes都可以用
  • 在可迭代生成小塊和大塊的兩種情況下(在可迭代的末尾除外),准確返回長度要求的內容
  • append str / bytes - 所以避免復制
  • 利用切片——因此也避免了復制,因為應該是整個實例的str / bytes切片將返回完全相同的實例

首先,您的生成器必須生成字節對象。 雖然沒有內置任何內容,但您可以使用http://docs.python.org/library/stringio.html和itertools.chain的組合。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM