Comment faire une classe qui agit comme une chaîne?
-
29-09-2019 - |
Question
J'ai un gestionnaire de contexte qui capture la sortie d'une chaîne pour un bloc de code en retrait sous une instruction with
. Ce gestionnaire de contexte donne un objet résultat personnalisé qui, lorsque le bloc a terminé son exécution, contenir la sortie capturé.
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 l'instruction with
ne peut être modifiée après leur bloc de code se exécute. Cependant, il est quelque chose d'un glisser d'avoir à convertir explicitement l'objet de résultat à une chaîne après le fait avec str
(je joue aussi avec le fait callable objet comme un peu de sucre syntaxique).
Ainsi est-il possible de faire l'instance fait agir comme une chaîne, en ce qu'elle fait en retour de fait une chaîne quand le nom? J'ai essayé de mettre en œuvre __get__
, mais qui semble ne fonctionne que sur les attributs. Ou est-ce que je veux faire pas vraiment possible?
La solution 3
À première vue, il semblait que UserString
(bien, en fait MutableString
, mais qui va loin dans Python 3.0) est essentiellement ce que je voulais. Malheureusement, UserString ne fonctionne pas tout à fait assez comme une chaîne; Je recevais une mise en forme bizarre dans les états de print
se terminant par des virgules qui a bien fonctionné avec des chaînes de str
. (Il semble que vous obtenez un espace supplémentaire imprimé si ce n'est pas une chaîne « réelle », ou quelque chose.) J'ai eu le même problème avec une classe de jouet j'ai créé pour jouer avec enroulant une chaîne. Je ne l'ai pas pris le temps de traquer la cause, mais il semble UserString
est le plus utile à titre d'exemple.
En fait, je fini par utiliser un bytearray
parce que cela fonctionne assez comme une chaîne pour la plupart des cas, mais il est mutable. J'ai également écrit une version distincte qui splitlines()
le texte dans une liste. Cela fonctionne très bien et est en fait mieux pour mon cas d'utilisation immédiate, qui enlève « extra » lignes vides 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']
Autres conseils
Comment faire une classe qui agit comme une chaîne? Sous-classe 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
Je ne crois pas qu'il y ait un moyen de faire propre ce que vous voulez.
text
est définie dans les modules de l'dict globals()
.
Vous devez modifier cette globals () dict à partir de l'objet capturing
:
Le code ci-dessous romprait si vous avez essayé d'utiliser le with
à partir d'une fonction, car text
serait alors dans le champ de la fonction, et non 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'
Je pense que vous pourriez être en mesure de quelque chose de construction comme celui-ci.
import StringIO
capturing = StringIO.StringIO()
print( "foo bar baz", file= capturing )
'foo bar baz \ n' == capturing.getvalue()
C'est le plus facile. Il fonctionne parfaitement sans travail supplémentaire, sauf pour corriger vos fonctions print
d'utiliser l'argument file=
.
Comment faire une classe qui agit comme une chaîne?
Si vous ne voulez pas la sous-classe str pour quelque raison que ce:
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 () genre de Implique, pour moi, que cette classe devrait s'exposer comme str à des fonctions qui attendent une str dans le cadre du objet. Au lieu d'un résultat inattendu de « qui savent de quoi seraient retournés par str (SomeObject).