Подтвердить что ты не робот

Вопросы, связанные с генераторами python и быстрым клиентом openstack

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

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

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

def monkeypatch_class(name, bases, namespace):
    '''Guido 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 script, который следует за тем же потоком, что и код выше, который прошел без проблем:

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()

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

4b9b3361

Ответ 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