2010-10-12 3 views
3

У меня есть менеджер контекста, который фиксирует вывод строки для блока кода с отступом в соответствии с оператором with. Этот менеджер контекста предоставляет настраиваемый объект результата, который, когда блок завершит выполнение, содержит захваченный вывод.Как создать класс, который действует как строка?

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" 

Я не могу просто возвращает строку, конечно, потому что строки являются неизменными и, таким образом, один пользователь получает обратно от with заявление не может быть изменен после того, как их блок кода выполняется. Тем не менее, это что-то вроде перетаскивания, чтобы явно преобразовать объект результата в строку после факта с str (я также играл с тем, чтобы объект был вызван как немного синтаксического сахара).

Итак, можно ли заставить экземпляр результата действовать как строка, поскольку он действительно возвращает строку при ее названии? Я попробовал реализовать __get__, но, похоже, работает только с атрибутами. Или это то, чего я хочу сделать не по-настоящему?

+0

Независимо от того, есть ли ответ на этот вопрос, я знаю, что предпочел бы, чтобы вы вернули класс, который реализовал' __str__' Я не уверен, как это перетаскивание, которое вы должны в какой-то момент, прямо сказать, «прямо здесь, где я блокирую это как строку, никаких дальнейших изменений», вызывая 'str()'. gain? –

+0

Что не так? stringIO? –

+0

@Mike: В основном, что пользователь хочет строку, а не объект, который должен быть преобразован в строку. – kindall

ответ

2

На первый взгляд, это выглядело как UserString (ну, на самом деле MutableString, но это происходит далеко в Python 3.0) был в основном то, что я хотел. К сожалению, UserString не работает достаточно как строка; Я получал некоторое нечетное форматирование в операциях print, заканчивающихся запятыми, которые отлично работали с строками str. (Кажется, вы получаете дополнительное пространство, если оно не является «реальной» строкой или чем-то еще.) У меня была такая же проблема с классом игрушек, который я создал, чтобы сыграть с оберткой строки. Я не нашел времени, чтобы отследить причину, но кажется, что UserString является наиболее полезным в качестве примера.

Я действительно закончил использование bytearray, потому что он работает достаточно, как строка для большинства целей, но изменен. Я также написал отдельную версию, что splitlines() текст в список. Это отлично работает и на самом деле лучше для моего непосредственного использования, который удаляет лишние пустые строки в конкатенированном выпуске различных функций. Вот эта версия:

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

Использование:

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

Я не верю, что есть чистый способ сделать то, что вы хотите. text определяется в модулях globals() dict. Вы должны изменить эти глобал() Dict внутри capturing объекта:

ниже код сломается, если вы пытались использовать with из функции, с тех пор text бы в рамках работы функции, а не Глобал.

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

Это симпатичный взлом, и я по этой причине поддержал его, но «нельзя использовать в функции» несколько ограничивает его полезность. :-) – kindall

1

Я думаю, вы могли бы быть в состоянии создать что-то вроде этого.

import StringIO 

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

Now 'Foo бар БАЗ \ N' == capturing.getvalue()

Это самый простой. Он отлично работает без дополнительной работы, за исключением того, чтобы исправить ваши функции print, чтобы использовать аргумент file=.

+0

Переменная 'as' всегда ** ** доступна после оператора with. :-) – kindall

+0

@kindall: Действительно? Для него никогда не будет никакого использования со стандартными встроенными менеджерами контекста, такими как «файл». Я думаю, возможно, я должен удалить этот ответ. –

+0

Действительно, он работает. Python имеет только область обзора, поэтому, когда вы определяете переменную (даже в цикле или в блоке 'with'), она доступна через конец функции. Вот почему я получил эту сумасшедшую идею в первую очередь. – kindall

4

Как создать класс, который действует как строка? Подкласс ул

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:\\' 

Выход:

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

Как сделать класс, который действует как струна?

Если вы не хотите, чтобы подклассы ул по какой-либо причине:

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.' 

Я знаю, что вы не хотите делать ул (MyClass), но MyClass.str() вид подразумевает, мне кажется, что этот класс должен выставлять себя как str для функций, которые ожидают str как часть объекта. Вместо какого-то неожиданного результата «кто знает, что будет возвращено str (SomeObject).