2010-10-12 3 views
3

J'ai un gestionnaire de contexte qui capture la sortie d'une chaîne pour un bloc de code indenté sous une instruction with. Ce gestionnaire de contexte génère un objet résultat personnalisé qui, une fois l'exécution du bloc terminée, contiendra la sortie capturée.Comment faire une classe qui agit comme une chaîne?

from contextlib import contextmanager 

@contextmanager 
def capturing(): 
    "Captures output within a 'with' block." 
    from cStringIO import StringIO 

    class result(object): 
     def __init__(self): 
      self._result = None 
     def __str__(self): 
      return self._result 

    try: 
     stringio = StringIO() 
     out, err, sys.stdout, sys.stderr = sys.stdout, sys.stderr, stringio, stringio 
     output = result() 
     yield output 
    finally: 
     output._result, sys.stdout, sys.stderr = stringio.getvalue(), out, err 
     stringio.close() 

with capturing() as text: 
    print "foo bar baz", 

print str(text) # prints "foo bar baz" 

Je ne peux pas retourner une chaîne, bien sûr, parce que les chaînes sont immuables et donc celui que l'utilisateur revient de la déclaration with ne peut être modifiée après leur bloc de court code. Cependant, il est quelque peu pénible d'avoir à convertir explicitement l'objet résultat en une chaîne après le fait avec str (j'ai aussi joué avec le fait de rendre l'objet appelable comme un peu de sucre syntaxique).

Alors, est-il possible de faire en sorte que l'instance de résultat agisse comme une chaîne, en ce sens qu'elle renvoie une chaîne lorsqu'elle est nommée? J'ai essayé d'implémenter __get__, mais cela semble fonctionner uniquement sur les attributs. Ou est ce que je veux faire n'est pas vraiment possible?

+0

Qu'il y ait ou non une réponse à cette question, je sais que je préférerais que vous retourniez une classe qui a implémenté' __str__' Je ne sais pas à quel point c'est un frein que vous devez, à un moment donné, dire explicitement, "ici est là où je verrouille cela en tant que chaîne, pas d'autres changements" en appelant 'str()'. gain? –

+0

Quel est le problème le 'stringIO'? –

+0

@Mike: Principalement qu'un utilisateur veut la chaîne, pas un objet qui doit être converti en chaîne – kindall

Répondre

2

À première vue, il ressemblait à UserString (bien, en fait MutableString, mais qui va loin dans Python 3.0) est essentiellement ce que je voulais. Malheureusement, UserString ne fonctionne pas assez comme une chaîne; Je recevais un formatage impair dans print instructions se terminant par des virgules qui ont bien fonctionné avec str chaînes. (Il semble que vous obtenez un espace supplémentaire imprimé si ce n'est pas une "vraie" chaîne, ou quelque chose.) J'ai eu le même problème avec un cours de jouets que j'ai créé pour jouer avec l'emballage d'une chaîne. Je n'ai pas pris le temps de rechercher la cause, mais il semble que UserString soit le plus utile à titre d'exemple.

En fait, j'ai fini par utiliser un bytearray parce qu'il fonctionne assez comme une chaîne pour la plupart des buts, mais est mutable. J'ai également écrit une version distincte que splitlines() le texte dans une liste. Cela fonctionne très bien et est en fait meilleur pour mon cas d'utilisation immédiate, qui supprime des lignes vides "supplémentaires" dans la sortie concaténée de diverses fonctions. Voici cette version:

import sys 
from contextlib import contextmanager 

@contextmanager 
def capturinglines(output=None): 
    "Captures lines of output to a list." 
    from cStringIO import StringIO 

    try: 
     output = [] if output is None else output 
     stringio = StringIO() 
     out, err = sys.stdout, sys.stderr 
     sys.stdout, sys.stderr = stringio, stringio 
     yield output 
    finally: 
     sys.stdout, sys.stderr = out, err 
     output.extend(stringio.getvalue().splitlines()) 
     stringio.close() 

Utilisation:

with capturinglines() as output: 
    print "foo" 
    print "bar" 

print output 
['foo', 'bar'] 

with capturinglines(output): # append to existing list 
    print "baz" 

print output 
['foo', 'bar', 'baz'] 
2

Je ne crois pas qu'il existe un propre façon de faire ce que vous voulez. text est défini dans les modules globals() dict. Vous devez modifier cette globals() dict à partir de l'objet capturing:

Le code ci-dessous éclaterait si vous avez essayé d'utiliser le with à partir d'une fonction, depuis lors text serait dans le cadre de la fonction, et non la globals.

import sys 
import cStringIO 

class capturing(object): 
    def __init__(self,varname): 
     self.varname=varname 
    def __enter__(self): 
     self.stringio=cStringIO.StringIO() 
     self.out, sys.stdout = sys.stdout, self.stringio 
     self.err, sys.stderr = sys.stderr, self.stringio   
     return self 
    def __exit__(self,ext_type,exc_value,traceback): 
     sys.stdout = self.out 
     sys.stderr = self.err 
     self._result = self.stringio.getvalue() 
     globals()[self.varname]=self._result 
    def __str__(self): 
     return self._result 


with capturing('text') as text: 
    print("foo bar baz") 

print(text) # prints "foo bar baz" 
# foo bar baz 

print(repr(text)) 
# 'foo bar baz\n' 
+0

C'est un hack mignon et je l'ai upvoted pour cette raison, mais "ne peut pas être utilisé dans une fonction" limite un peu son utilité. :-) – kindall

1

Je pense que vous pourriez être en mesure de construire quelque chose comme ça.

import StringIO 

capturing = StringIO.StringIO() 
print("foo bar baz", file= capturing) 

Maintenant 'foo bar baz \ n' == capturing.getvalue()

C'est le plus facile. Cela fonctionne parfaitement sans travail supplémentaire, sauf pour réparer vos fonctions print pour utiliser l'argument file=.

+0

La variable 'as' est ** toujours ** disponible après l'instruction with, en fait. :-) – kindall

+0

@kindall: Vraiment? Il n'y a jamais d'utilisation avec les gestionnaires de contextes intégrés standard comme 'file'. Je pense que je devrais peut-être supprimer cette réponse. –

+0

Vraiment, ça marche. Python a seulement une portée au niveau de la fonction, donc quand vous définissez une variable (même dans une boucle ou un bloc 'with '), elle est disponible jusqu'à la fin de la fonction. C'est pourquoi j'ai eu cette idée folle en premier lieu. – kindall

4

Comment faire une classe qui agit comme une chaîne? Sous str

import os 
class LikeAStr(str): 
    '''Making a class like a str object; or more precisely 
    making a str subclass with added contextmanager functionality.''' 

    def __init__(self, diff_directory): 
     self._iwd = os.getcwd() 
     self._cwd = diff_directory 

    def __enter__(self): 
     return self 

    def __exit__(self, ext_typ, exc_value, traceback): 
     try: os.chdir(self._iwd) # might get deleted within the "with" statement 
     except: pass 

    def __str__(self): 
     return self._cwd 

    def __repr__(self): 
     return repr(self._cwd) 


astr = LikeAStr('C:\\') 

with LikeAStr('C:\\') as astr: 
    print 1, os.getcwd() 
    os.chdir(astr) # expects str() or unicode() not some other class 
    print 2, os.getcwd() 
    # 

# out of with block 
print 3, os.getcwd() 
print 4, astr == 'C:\\' 

Sortie:

1 D:\Projects\Python\ 
2 C:\ 
3 D:\Projects\Python\ 
4 True 
1

Comment faire une classe qui agit comme une chaîne?

Si vous ne voulez pas sous-classe str pour une raison quelconque:

class StrBuiltin(object): 
    def __init__(self, astr=''): 
     self._str = astr 

    def __enter__(self): 
     return self 

    def __exit__(self, ext_typ, exc_value, traceback): 
     pass # do stuff 

    def __str__(self): 
     return self._str 

    def __repr__(self): 
     return repr(self._str) 

    def __eq__(self, lvalue): 
     return lvalue == self._str 

    def str(self): 
     '''pretend to "convert to a str"''' 
     return self._str 

astr = StrBuiltin('Eggs&spam') 

if isinstance(astr.str(), str): 
    print 'Is like a str.' 
else: 
    print 'Is not like a str.' 

Je sais que vous ne vouliez pas faire str (MyClass) mais MyClass.str() implique en quelque sorte, pour moi, cette classe est censée s'exposer comme un str à des fonctions qui attendent un str dans le cadre de l'objet. Au lieu d'un résultat inattendu de "qui sait ce qui serait retourné par str (SomeObject)

Questions connexes