Как сделать класс, который действует как строка? - PullRequest
3 голосов
/ 13 октября 2010

У меня есть менеджер контекста, который записывает вывод в строку для блока кода с отступом под оператором 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__, но, похоже, он работает только с атрибутами.Или то, что я хочу сделать, на самом деле не возможно?

Ответы [ 5 ]

4 голосов
/ 02 января 2013

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

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
2 голосов
/ 13 октября 2010

На первый взгляд, это выглядело как 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 голосов
/ 13 октября 2010

Я не верю, что есть чистый способ сделать то, что вы хотите. text определяется в модулях globals() dict. Вам нужно изменить этот параметр globals () из объекта 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'
1 голос
/ 02 января 2013

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

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

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

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

1 голос
/ 14 октября 2010

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

import StringIO

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

Теперь 'foo bar baz \ n' == capturing.getvalue()

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...