Проблемы работы с генераторами python и openstack swift client

У меня проблема с генераторами Python при работе с клиентской библиотекой Openstack Swift.

Проблема заключается в том, что я пытаюсь извлечь большую строку данных из определенного URL-адреса (около 7 МБ), разделить строку на более мелкие биты и отправить обратно класс генератора, при этом каждая итерация содержит фрагментированный бит строки. в наборе тестов это просто строка, которая отправляется в обезьянчевый класс клиента swift для обработки.

Код в классе monkeypatched выглядит следующим образом:

def monkeypatch_class(name, bases, namespace):
    '''Guido's monkeypatch metaclass.'''
    assert len(bases) == 1, "Exactly one base class required"
    base = bases[0]
    for name, value in namespace.iteritems():
        if name != "__metaclass__":
            setattr(base, name, value)
    return base

И в тестовом наборе:

from swiftclient import client
import StringIO
import utils

class Connection(client.Connection):
    __metaclass__ = monkeypatch_class

    def get_object(self, path, obj, resp_chunk_size=None, ...):
        contents = None
        headers = {}

        # retrieve content from path and store it in 'contents'
        ...

        if resp_chunk_size is not None:
            # stream the string into chunks
            def _object_body():
                stream = StringIO.StringIO(contents)
                buf = stream.read(resp_chunk_size)
                while buf:
                    yield buf
                    buf = stream.read(resp_chunk_size)
            contents = _object_body()
        return headers, contents

После возврата объекта генератора он был вызван потоковой функцией в классе хранения:

class SwiftStorage(Storage):

    def get_content(self, path, chunk_size=None):
        path = self._init_path(path)
        try:
            _, obj = self._connection.get_object(
                self._container,
                path,
                resp_chunk_size=chunk_size)
            return obj
        except Exception:
            raise IOError("Could not get content: {}".format(path))

    def stream_read(self, path):
        try:
            return self.get_content(path, chunk_size=self.buffer_size)
        except Exception:
            raise OSError(
                "Could not read content from stream: {}".format(path))

И, наконец, в моем тестовом наборе:

def test_stream(self):
    filename = self.gen_random_string()
    # test 7MB
    content = self.gen_random_string(7 * 1024 * 1024)
    self._storage.stream_write(filename, io)
    io.close()
    # test read / write
    data = ''
    for buf in self._storage.stream_read(filename):
        data += buf
    self.assertEqual(content,
                     data,
                     "stream read failed. output: {}".format(data))

Выход заканчивается этим:

======================================================================
FAIL: test_stream (test_swift_storage.TestSwiftStorage)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/bacongobbler/git/github.com/bacongobbler/docker-registry/test/test_local_storage.py", line 46, in test_stream
    "stream read failed. output: {}".format(data))
AssertionError: stream read failed. output: <generator object _object_body at 0x2a6bd20>

Я попытался изолировать это с помощью простого скрипта Python, который следует тому же потоку, что и код выше, который прошел без проблем:

def gen_num():
    def _object_body():
        for i in range(10000000):
            yield i
    return _object_body()

def get_num():
    return gen_num()

def stream_read():
    return get_num()

def main():
    num = 0
    for i in stream_read():
        num += i
    print num

if __name__ == '__main__':
    main()

Любая помощь в этом вопросе очень ценится:)

1 ответ

Решение

В вашем get_object метод, вы присваиваете возвращаемое значение _object_body() к contents переменная. Однако эта переменная также содержит ваши фактические данные, и она используется на ранних этапах _object_body,

Проблема в том, что _object_body это функция генератора (она использует yield). Поэтому, когда вы вызываете его, он генерирует объект генератора, но код функции не запускается, пока вы не выполните итерацию по этому генератору. Это означает, что когда код функции фактически начинает работать (for зациклиться _test_stream), это долго после того, как вы переназначены contents = _object_body(),

Ваш stream = StringIO(contents) поэтому создает StringIO объект, содержащий объект генератора (отсюда и ваше сообщение об ошибке), а не данные.

Вот минимальный случай воспроизведения, который иллюстрирует проблему:

def foo():
    contents = "Hello!"

    def bar():
        print contents
        yield 1

    # Only create the generator. This line runs none of the code in bar.
    contents = bar()

    print "About to start running..."
    for i in contents:
        # Now we run the code in bar, but contents is now bound to 
        # the generator object. So this doesn't print "Hello!"
        pass
Другие вопросы по тегам