web-dev-qa-db-fra.com

StringIO et compatibilité avec l'instruction 'with' (gestionnaire de contexte)

J'ai du code hérité avec une fonction héritée qui prend un nom de fichier comme argument et traite le contenu du fichier. Un fac-similé de travail du code est ci-dessous.

Ce que je veux faire n'est pas d'avoir à écrire sur le disque avec du contenu que je génère afin d'utiliser cette fonction héritée, donc j'ai pensé que je pourrais utiliser StringIO pour créer un objet à la place du nom de fichier physique. Cependant, cela ne fonctionne pas, comme vous pouvez le voir ci-dessous.

Je pensais que StringIO était la solution. Quelqu'un peut-il me dire s'il existe un moyen d'utiliser cette fonction héritée et de lui transmettre quelque chose dans l'argument qui n'est pas un fichier sur le disque mais qui peut être traité comme tel par la fonction héritée? La fonction héritée a le gestionnaire de contexte with qui travaille sur la valeur du paramètre filename.

La seule chose que j'ai rencontrée dans google était: http://bugs.python.org/issue1286 , mais cela ne m'a pas aidé ...

Code

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

Sortie

Il s'agit de la sortie dans FileOnDisk.txt:

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

Voici l'erreur:

Traceback (most recent call last):
  File "C:\temp\test.py", line 20, in <module>
    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
42
mpettis

Une instance de StringIO est déjà un fichier ouvert. La commande open, en revanche, ne prend que les noms de fichiers, pour renvoyer un fichier ouvert. Une instance StringIO ne convient pas comme nom de fichier.

De plus, vous n'avez pas besoin de fermer une instance StringIO, il n'est donc pas nécessaire de l'utiliser comme gestionnaire de contexte non plus.

Si tout votre code hérité peut prendre un nom de fichier, alors une instance StringIO n'est pas la solution. Utilisez le module tempfile pour générer un nom de fichier temporaire à la place.

Voici un exemple d'utilisation d'un gestionnaire de contexte pour garantir que le fichier temporaire est nettoyé par la suite:

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)

Vous pouvez également passer à la nouvelle infrastructure Python 3 offerte par le module io (disponible dans Python 2 et 3), où - io.BytesIO est le remplacement le plus robuste de StringIO.StringIO/cStringIO.StringIO. Cet objet ne prend pas en charge l'utilisation en tant que gestionnaire de contexte (mais ne peut toujours pas être transmis à open()).

61
Martijn Pieters

vous pouvez définir votre propre fonction ouverte

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

cependant avec veut appeler __exit__ après son fait et StringIO n'a pas de méthode de sortie ...

vous pouvez définir une classe personnalisée à utiliser avec cette ouverture

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

Celui-ci est basé sur le python doc de contextmanager

Il s'agit simplement d'encapsuler StringIO avec un contexte simple, et lorsque exit est appelé, il reviendra au point de rendement et fermera correctement le StringIO. Cela évite d'avoir à créer un fichier temporaire, mais avec une grande chaîne, cela consommera toujours de la mémoire, car StringIO met cette chaîne en mémoire tampon. Cela fonctionne bien dans la plupart des cas où vous savez que les données de chaîne ne seront pas longues

from contextlib import contextmanager

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

Ensuite, vous pouvez faire:

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