【问题标题】:issues working with python generators and openstack swift client使用 python 生成器和 openstack swift 客户端的问题
【发布时间】:2013-12-24 03:54:20
【问题描述】:

我在使用 Openstack Swift 客户端库时遇到了 Python 生成器问题。

手头的问题是我正在尝试从特定的 url(大约 7MB)检索一大串数据,将字符串分块成更小的位,然后发回一个生成器类,每次迭代都保存一个分块位字符串。在测试套件中,这只是一个字符串,它被发送到 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()

非常感谢您对此问题的任何帮助:)

【问题讨论】:

  • 你应该在函数 gen_num 中返回 _object_body 吗?没有括号?
  • 有些奇怪。是test_stream 中的最后一行抛出错误消息吗?如果是这样,似乎data&lt;generator object _object_body at 0x2a6bd20&gt;,但我不明白这是怎么回事。它是什么时候从字符串改变的?
  • 在您的代码中添加一些打印,以确保您正在执行您认为正在执行的内容。正如@DSM 所说,data您在此处显示 的代码中以字符串的形式开始生活,而 data += buf 无法改变它 - data 不可能神奇地变成生成器对象代码你展示了。因此我得出结论 ;-) 你实际上并没有执行你展示的代码。
  • @DSM,是的,这是 test_stream 中的最后一行抛出此错误消息。
  • path 视为 S3 存储桶中文件的路径,而 contents 是该文件中包含的数据。我认为问题可能必须处理找到不返回任何内容的路径的代码,然后 contents 只会将 None 返回到 stream_read。但是,我仍然不明白为什么在这种情况下会返回生成器类。也许是一个空文件?

标签: python openstack openstack-swift


【解决方案1】:

在您的get_object 方法中,您将_object_body() 的返回值分配给contents 变量。但是,该变量也是保存您的实际数据的变量,它在 _object_body 的早期使用。

问题在于_object_body 是一个生成器函数(它使用yield)。因此,当您调用它时,它会生成一个生成器对象,但在您迭代该生成器之前,该函数的代码不会开始运行。这意味着当函数的代码真正开始运行时(_test_stream 中的 for 循环),在您重新分配 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

【讨论】:

  • 我认为你成功了!这里有一个错误:“创建了一个包含生成器对象的 StringIO 对象”。它实际上创建了一个包含str(_object_body()) 的StringIO 对象,也就是字符串'&lt;generator object _object_body at 0x2a6bd20&gt;'。真是一团糟;-)
  • 确实,你是对的。所以要从中吸取的教训是,不要为多个不相关的事情重用变量。 (我发现我越来越多地使用单分配风格进行编程,正是为了避免这些问题)
  • 就是这样!非常感谢大家在这个问题上的帮助:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-11-11
  • 1970-01-01
  • 1970-01-01
  • 2012-04-03
  • 1970-01-01
  • 1970-01-01
  • 2020-12-02
相关资源
最近更新 更多