3 votes

Comment créer une classe qui se comporte comme une chaîne de caractères ?

J'ai un gestionnaire de contexte qui capture la sortie vers une chaîne pour un bloc de code indenté sous un with déclaration. Ce gestionnaire de contexte produit un objet résultat personnalisé qui, une fois l'exécution du bloc terminée, contiendra la sortie capturée.

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 simplement renvoyer une chaîne, bien sûr, car les chaînes sont immuables et donc celle que l'utilisateur reçoit de la fonction with ne peuvent pas être modifiées après l'exécution de leur bloc de code. Cependant, il est quelque peu ennuyeux de devoir convertir explicitement l'objet résultat en une chaîne de caractères après coup avec str (J'ai également joué avec le fait de rendre l'objet appelable comme un peu de sucre syntaxique).

Est-il possible de faire en sorte que l'instance de résultat se comporte comme une chaîne de caractères, c'est-à-dire qu'elle renvoie effectivement une chaîne de caractères lorsqu'elle est nommée ? J'ai essayé d'implémenter __get__ mais cela ne semble fonctionner que sur les attributs. Ou bien ce que je veux faire n'est-il pas vraiment possible ?

5voto

DevPlayer Points 996

Comment créer une classe qui se comporte comme une chaîne de caractères ? 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

2voto

unutbu Points 222216

Je ne crois pas qu'il y ait nettoyer pour faire ce que vous voulez. text est défini dans les modules globals() dict. Vous devrez modifier ce dict de globals() à partir de l'application capturing objet :

Le code ci-dessous serait cassé si vous essayiez d'utiliser l'option with à l'intérieur d'une fonction, car alors text serait dans la portée de la fonction, pas dans les globales.

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'

2voto

kindall Points 60645

A première vue, ça ressemblait à UserString (enfin, en fait MutableString mais cela va disparaître dans Python 3.0) était essentiellement ce que je voulais. Malheureusement, UserString ne fonctionne pas tout à fait assez de comme une chaîne de caractères ; j'obtenais un formatage bizarre dans le fichier print se terminant par des virgules qui fonctionnaient bien avec str chaînes. (Il semble que vous obteniez un espace supplémentaire imprimé si ce n'est pas une "vraie" chaîne, ou quelque chose comme ça). J'ai eu le même problème avec une classe jouet que j'ai créée pour m'amuser à envelopper une chaîne. Je n'ai pas pris le temps d'en rechercher la cause, mais il semble que UserString est le plus utile à titre d'exemple.

En fait, j'ai fini par utiliser un bytearray car elle fonctionne suffisamment comme une chaîne de caractères pour la plupart des besoins, mais elle est mutable. J'ai aussi écrit une version séparée qui splitlines() le texte dans une liste. Cela fonctionne très bien et c'est même mieux pour mon cas d'utilisation immédiat, qui consiste à supprimer les 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']

1voto

S.Lott Points 207588

Je pense que vous pourriez être capable 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. Il fonctionne parfaitement sans aucun travail supplémentaire, sauf pour fixer votre print pour utiliser les fonctions file= argument.

1voto

DevPlayer Points 996

Comment créer une classe qui se comporte comme une chaîne de caractères ?

Si vous ne voulez pas sous-classer 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, pour moi, que cette classe est censée s'exposer comme une str aux fonctions qui attendent une str comme partie de l'objet. Au lieu d'un résultat inattendu de "qui sait ce qui serait retourné par str( SomeObject ).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X