如何创建一个像字符串一样的类?

发布于 2024-09-27 01:18:21 字数 1044 浏览 0 评论 0原文

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

I have a context manager that captures output to a string for a block of code indented under a with statement. This context manager yields a custom result object which will, when the block has finished executing, contain the captured output.

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"

I can't just return a string, of course, because strings are immutable and thus the one the user gets back from the with statement can't be changed after their block of code runs. However, it is something of a drag to have to explicitly convert the result object to a string after the fact with str (I also played with making the object callable as a bit of syntactic sugar).

So is it possible to make the result instance act like a string, in that it does in fact return a string when named? I tried implementing __get__, but that appears to only work on attributes. Or is what I want to do not really possible?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

若有似无的小暗淡 2024-10-04 01:18:21

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

How to make a class that acts like a string?
Subclass 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:\\'

Output:

1 D:\Projects\Python\
2 C:\
3 D:\Projects\Python\
4 True
时间海 2024-10-04 01:18:21

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

如果您尝试在函数中使用 with ,下面的代码将会中断,因为 < code>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'

I don't believe there is a clean way to do what you want.
text is defined in the modules' globals() dict.
You would have to modify this globals() dict from within the capturing object:

The code below would break if you tried to use the with from within a function, since then text would be in the function's scope, not the 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'
﹉夏雨初晴づ 2024-10-04 01:18:21

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

At first glance, it looked like UserString (well, actually MutableString, but that's going away in Python 3.0) was basically what I wanted. Unfortunately, UserString doesn't work quite enough like a string; I was getting some odd formatting in print statements ending in commas that worked fine with str strings. (It appears you get an extra space printed if it's not a "real" string, or something.) I had the same issue with a toy class I created to play with wrapping a string. I didn't take the time to track down the cause, but it appears UserString is most useful as an example.

I actually ended up using a bytearray because it works enough like a string for most purposes, but is mutable. I also wrote a separate version that splitlines() the text into a list. This works great and is actually better for my immediate use case, which is removing "extra" blank lines in the concatenated output of various functions. Here's that 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()

Usage:

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']
绳情 2024-10-04 01:18:21

我认为你也许能​​够构建这样的东西。

import StringIO

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

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

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

I think you might be able to build something like this.

import StringIO

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

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

That's the easiest. It works perfectly with no extra work, except to fix your print functions to use the file= argument.

り繁华旳梦境 2024-10-04 01:18:21

如何创建一个像字符串一样的类?

如果您出于某种原因不想子类化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 ) 会返回什么”这样的意外结果。

How to make a class that acts like a string?

If you don't want to subclass str for whatever reason:

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

I know you didn't want to do str(MyClass) but MyClass.str() kind of implies, to me, that this class is expected to expose itself as a str to functions which expect a str as part of the object. Instead of some unexpected result of "who know's what would be returned by str( SomeObject ).

人间不值得 2024-10-04 01:18:21

这是一个老问题,但却是一个有趣的问题。
使用 @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

This is an old question but is an interesting one.
Using the idea from @S.Lott you can use contextmanagers to create a more robust and reusable tool:

@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

sample use with file-like objects:

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

sample use with StringIO objects

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
无戏配角 2024-10-04 01:18:21

类中的 __new__ 方法用于初始化正在构造的类,它可用于使类以字符串形式返回自身。

class instance_of_str(str):
    def __init__(self, string: str):
        self._string = string

    def __new__(cls, string):
        return str.__new__(cls, string)

    def __enter__(self):
        return self

    def __exit__(self, ext_typ, exc_value, traceback):
        pass  # do stuff

    # def __str__(self):
    #     return self._string

    # def __repr__(self):
    #     return repr(self._string)

    def __eq__(self, value):
        return value == self._string
with instance_of_str('string') as instance:
    if isinstance(instance, str):
        print('Is an instance of object type str.')
        print(instance, repr(instance))
    else:
        print('Is not an instance of object type str.')
        print(instance, repr(instance))

输出:

>>> Is an instance of object type str.
>>> string 'string'

The __new__ method in a class is what initializes the class being constructed and it can be used to get the class to return itself as a string.

class instance_of_str(str):
    def __init__(self, string: str):
        self._string = string

    def __new__(cls, string):
        return str.__new__(cls, string)

    def __enter__(self):
        return self

    def __exit__(self, ext_typ, exc_value, traceback):
        pass  # do stuff

    # def __str__(self):
    #     return self._string

    # def __repr__(self):
    #     return repr(self._string)

    def __eq__(self, value):
        return value == self._string
with instance_of_str('string') as instance:
    if isinstance(instance, str):
        print('Is an instance of object type str.')
        print(instance, repr(instance))
    else:
        print('Is not an instance of object type str.')
        print(instance, repr(instance))

Output:

>>> Is an instance of object type str.
>>> string 'string'
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文