StringIO y compatibilidad con la statement ‘with’ (administrador de contexto)

Tengo un código heredado con una función heredada que toma un nombre de archivo como argumento y procesa el contenido del archivo. A continuación se muestra un fax de trabajo del código.

Lo que quiero hacer es no tener que escribir en el disco con algo de contenido que genero para usar esta función heredada, así que pensé que podría usar StringIO para crear un objeto en lugar del nombre de archivo físico. Sin embargo, esto no funciona, como se puede ver a continuación.

Pensé que StringIO era el camino a seguir con esto. ¿Alguien puede decirme si hay una manera de usar esta función heredada y pasarle algo en el argumento de que no es un archivo en el disco pero que puede tratarse como tal por la función heredada? La función heredada tiene el administrador de contexto trabajando en el valor del parámetro de filename .

Lo único que encontré en Google fue: http://bugs.python.org/issue1286 , pero eso no me ayudó …

Código

 from pprint import pprint import StringIO # Legacy Function def processFile(filename): with open(filename, 'r') as fh: return fh.readlines() # This works print 'This is the output of FileOnDisk.txt' pprint(processFile('c:/temp/FileOnDisk.txt')) print # This fails plink_data = StringIO.StringIO('StringIO data.') print 'This is the error.' pprint(processFile(plink_data)) 

Salida

Esta es la salida en FileOnDisk.txt :

 ['This file is on disk.\n'] 

Este es el error:

 Traceback (most recent call last): File "C:\temp\test.py", line 20, in  pprint(processFile(plink_data)) File "C:\temp\test.py", line 6, in processFile with open(filename, 'r') as fh: TypeError: coercing to Unicode: need string or buffer, instance found 

Una instancia de StringIO es un archivo abierto ya. El comando de open , por otro lado, solo toma nombres de archivos, para devolver un archivo abierto. Una instancia de StringIO no es adecuada como nombre de archivo.

Además, no es necesario que cierre una instancia de StringIO , por lo que tampoco es necesario utilizarla como administrador de contexto.

Si todo su código heredado puede ser un nombre de archivo, entonces una instancia de StringIO no es el camino a seguir. Utilice el módulo tempfile para generar un nombre de archivo temporal en su lugar.

Aquí hay un ejemplo que usa un administrador de contexto para asegurar que el archivo temporal se limpie después:

 import os import tempfile from contextlib import contextmanager @contextmanager def tempinput(data): temp = tempfile.NamedTemporaryFile(delete=False) temp.write(data) temp.close() try: yield temp.name finally: os.unlink(temp.name) with tempinput('Some data.\nSome more data.') as tempfilename: processFile(tempfilename) 

También puede cambiar a la nueva infraestructura de Python 3 ofrecida por el módulo io (disponible en Python 2 y 3), donde io.BytesIO es el reemplazo más robusto para StringIO.StringIO / cStringIO.StringIO . Este objeto admite que se use como administrador de contexto (pero aún no se puede pasar a open() ).

Podrías definir tu propia función abierta.

 fopen = open def open(fname,mode): if hasattr(fname,"readlines"): return fname else: return fopen(fname,mode) 

sin embargo, con quiere llamar a __exit__ después de que se haga y StringIO no tiene un método de salida …

Podrías definir una clase personalizada para usar con este abierto.

 class MyStringIO: def __init__(self,txt): self.text = txt def readlines(self): return self.text.splitlines() def __exit__(self): pass 

Este está basado en el documento de python de contextmanager.

Simplemente está envolviendo StringIO con un contexto simple, y cuando se llama a exit , regresará al punto de rendimiento y cerrará adecuadamente StringIO. Esto evita la necesidad de crear tempfile, pero con una cadena grande, esto todavía consumirá la memoria, ya que StringIO almacena esa cadena. Funciona bien en la mayoría de los casos en los que sabe que los datos de la cadena no serán largos

 from contextlib import contextmanager @contextmanager def buildStringIO(strData): from cStringIO import StringIO try: fi = StringIO(strData) yield fi finally: fi.close() 

Entonces puedes hacer:

 with buildStringIO('foobar') as f: print(f.read()) # will print 'foobar'