【问题标题】:How to make a class that acts like a string?如何制作一个像字符串一样的类?
【发布时间】:2011-04-24 14:16:32
【问题描述】:

我有一个上下文管理器,它将输出捕获到在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__,但这似乎只适用于属性。还是我想做的事真的不可能?

【问题讨论】:

  • 不管这个问题是否有答案,我知道我希望你返回一个实现__str__ 的类。我不确定你必须在某个时候通过调用str() 明确地说“这是我将其锁定为字符串的地方,没有进一步的更改”。那么有什么收获呢?
  • @Mike:主要是用户想要字符串,而不是必须转换为字符串的对象。
  • 看起来 UserString (嗯,MutableString,但那会消失)基本上是我想要的,所以我要投票关闭它。编辑:哎呀,我想我真的不能这样做,因为没有一个真正适用的原因。
  • @kindall:然后你把它放在一个答案中,把它标记为正确的,然后继续。链接到 docs.python.org 上的 UserString 和 MutableString 页面的奖励积分。

标签: python string with-statement


【解决方案1】:

如何创建一个像字符串一样的类? 子类 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:\\'

输出:

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

【讨论】:

    【解决方案2】:

    我不相信有一种干净的方式来做你想做的事。 text 在模块的 globals() 字典中定义。 您必须在 capturing 对象中修改这个 globals() 字典:

    如果您尝试在函数内使用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'
    

    【讨论】:

    • 这是一个可爱的 hack,因此我赞成它,但“不能在函数中使用”在一定程度上限制了它的实用性。 :-)
    【解决方案3】:

    乍一看,它看起来像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']
    

    【讨论】:

      【解决方案4】:

      我认为你可能能够构建这样的东西。

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

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

      这是最简单的。它无需额外工作即可完美运行,除了修复您的 print 函数以使用 file= 参数。

      【讨论】:

      • as 变量实际上在 with 语句之后总是可用。 :-)
      • @kindall:真的吗?对于像file 这样的标准内置上下文管理器,它从来没有任何用处。我想也许我应该删除这个答案。
      • 真的,它有效。 Python 只有函数级别的作用域,所以当你定义一个变量时(即使在循环或with 块中),它可以通过函数末尾获得。这就是为什么我一开始就有这个疯狂的想法。
      • “疯狂的想法”。同意。由于替代方案(在此答案中)是微不足道的并且不需要编程。而且由于这个想法很难解释。
      • @kindall:所有对象都需要“消息传递”来做事。为什么这会有所不同?我仍然没有得到“问题”。请用一些特定的用例更新这个问题,这些用例显示了你实际上不能做的事情。您提出的解决方案存在一些“问题”或“错误”。
      【解决方案5】:

      如何制作一个像字符串一样的类?

      如果您出于某种原因不想继承 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) 将返回什么”的一些意外结果。

      【讨论】:

        【解决方案6】:

        这是一个老问题,但很有趣。 使用@S.Lott 的想法,您可以使用 contextmanagers 创建更强大且可重用的工具:

        @contextmanager
        def redefine_print(stream):
            global print
            from functools import partial, wraps
            old_print = print
            try:
                print = wraps(print)(partial(print, file=stream))
                yield print
            finally:
                print = old_print
        

        与类文件对象一起使用的示例:

        with open('file', 'a+') as stream:
            print('a')       # print in the interface
            with redefine_print(stream):
                print('b')   # print in the file 
            print('c')       # print in the interface
            stream.seek(0)
            print(stream.readlines())
        

        StringIO 对象的使用示例

        import io
        stream = io.StringIO()
        with redefine_print(stream) as xprint:
            print('b')   # add to the ioStream
            xprint('x')   # same as print, just to see how the object works
        
        print(stream.getvalue())   # print the intercepted value
        print(xprint.__doc__)      # see how @wraps helps to keep print() signature
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-09-02
          • 2011-07-24
          • 1970-01-01
          • 2015-08-16
          • 2015-09-22
          • 1970-01-01
          相关资源
          最近更新 更多