Problemas al trabajar con generadores de Python y OpenStack Swift Client.
openstack-swift (1)
En su método get_object
, está asignando el valor de retorno de _object_body()
a la variable de contents
. Sin embargo, esa variable también es la que contiene sus datos reales, y se usa desde el principio en _object_body
.
El problema es que _object_body
es una función generadora (usa yield
). Por lo tanto, cuando lo llama, produce un objeto generador, pero el código de la función no comienza a ejecutarse hasta que usted itera sobre ese generador . Lo que significa que cuando el código de la función realmente comienza a ejecutarse (el bucle for
en _test_stream
), pasa mucho tiempo después de que hayas reasignado el contents = _object_body()
.
Su stream = StringIO(contents)
por lo tanto, crea un objeto StringIO
que contiene el objeto generador (de ahí su mensaje de error), no los datos.
Aquí hay un caso de reproducción mínima que ilustra el problema:
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
Estoy teniendo un problema con los generadores de Python al trabajar con la biblioteca del cliente de Openstack Swift.
El problema en cuestión es que estoy tratando de recuperar una gran cadena de datos de una URL específica (aproximadamente 7 MB), dividir la cadena en bits más pequeños y enviar una clase de generador de vuelta, con cada iteración conteniendo un fragmento fragmentado de la cadena. en el conjunto de pruebas, esto es solo una cadena que se envía a una clase de cliente swift para el procesamiento.
El código en la clase de Monkeypatched se ve así:
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
Y en la suite de pruebas:
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
Después de devolver el objeto generador, fue llamado por una función de flujo en la clase de almacenamiento:
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))
Y finalmente, en mi suite de pruebas:
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))
La salida termina con esto:
======================================================================
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>
Intenté aislar esto con un simple script de Python que sigue el mismo flujo que el código anterior, que pasó sin problemas:
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()
Cualquier ayuda con este problema es muy apreciada :)