Python字符串插值

13

以下是可能导致此行为的原因:

>>> print str(msg)
my message
>>> print unicode(msg)
my message

但是:

>>> print '%s' % msg
another message

更多信息:

  • 我的msg对象是继承自unicode
  • 方法__str__/__unicode__/__repr__被重写以返回字符串'my message'
  • msg对象被初始化为字符串'another message'
  • 这是在Python 2.5上运行的。
  • 变量msg在测试之间没有改变。
  • 这实际上是一个真正的doctest,它确实给出了这些结果。

我希望有一个与此doctest匹配的解决方案,尽可能少地涉及继承(特别是实际继承层次):

>>> print '%s' % msg
my message

谢谢所有的建议。

我感觉这不会有更多帮助,但对于好奇的读者(和冒险的Python程序员),这是对象的实现:

class Message(zope.i18nmessageid.Message):

    def __repr__(self):
        return repr(zope.i18n.interpolate(self.default, self.mapping))

    def __str__(self):
        return zope.i18n.interpolate(self.default, self.mapping)

    def __unicode__(self):
        return zope.i18n.interpolate(self.default, self.mapping)

这是我们创建msg对象的方式:

>>> msg = Message('another message', 'mydomain', default='my message')

Zope所使用的版本和代码包括:

编辑信息:

  • 添加/更新了被覆盖的方法名称
  • 添加了一些更多的信息(Python版本和一些细节信息)
  • 修正了一些错误的信息(`msg`的类基于`unicode`而不是`basestring`)
  • 添加了实际使用的类的实现

3
@extraneon:这是Python 2.x版本:print是一个语句,basestring和unicode! - SilentGhost
1
print语句之间更改msg变量的值将解释它。 - van
1
你有那个对象的实际代码吗?(或者它的类。)如果你能把它粘贴在这里就很有用了... - Michał Marczyk
3
@vaab,msg的类实现是机密吗?如果不是的话,我想看一下。 - Anurag Uniyal
2
@vaab,对于我删除的错误答案表示抱歉。这对我来说似乎是Python内部的一个bug,所以我会在Python的跟踪器中打开一个bug(并使用像Michal下面的简化代码)。 - Alex Martelli
2
作为一个缺陷的解决方法,同时等待修复的Python版本,我认为Paul有正确的想法。 - Alex Martelli
3个回答

8

更新2: 请在下方水平线下找到原始答案,其中包括一个展示OP所描述的行为的类的简单示例。至于我在调查Python源代码(v. 2.6.4)时能够推断出的内容:

Include/unicodeobject.h文件中包含以下两行(在我的(有点旧的)检查中是第436-7行):

#define PyUnicode_AS_UNICODE(op) \                                              
        (((PyUnicodeObject *)(op))->str)

这在格式化代码中随处可见,据我所知,在字符串格式化期间,任何继承自unicode的对象都将被访问,以便直接使用其Unicode字符串缓冲区,而无需调用任何Python方法。就性能而言,我相信这很好(非常符合Juergen在此答案下的评论的猜测)。

对于OP的问题,这可能意味着如果类似于Anurag Uniyal的包装器类想法对于特定用例是可接受的,则只有这样才能使事情按照OP想要的方式工作。如果不是这样,现在我想到的唯一一件事就是在将这些类的对象插入到字符串中时将它们包装在str/unicode中... 哎呀。(我真诚地希望我只是错过了某个更干净的解决方案,希望有人会在一分钟内指出!)


(更新:这是在OP包含他的类代码之前约一分钟发布的,但我仍然将其保留在这里(1)为了下面的猜测/初始尝试解释,(2)为了提供一个产生此行为的简单示例(Anurag Uniyal后来提供了另一个直接调用unicode构造函数而不是通过super的示例),(3)希望以后能够编辑一些内容,以帮助OP获得所需的行为。)

这是一个实际上像OP描述的那样工作的类的示例(Python 2.6.4,它确实会产生一个弃用警告--/usr/bin/ipython:3: DeprecationWarning: object.__init__() takes no parameters):

class Foo(unicode):
    def __init__(self, msg):
        super(unicode, self).__init__(msg)
    def __str__(self): return 'str msg'
    def __repr__(self): return 'repr msg'
    def __unicode__(self): return u'unicode msg'

在IPython中有几种交互方式:
In [12]: print(Foo("asdf"))
asdf

In [13]: str(Foo("asdf"))
Out[13]: 'str msg'

In [14]: print str(Foo("asdf"))
-------> print(str(Foo("asdf")))
str msg

In [15]: print(str(Foo("asdf")))
str msg

In [16]: print('%s' % Foo("asdf"))
asdf

显然,字符串内插将此对象视为unicode实例(直接调用__str__unicode实现),而其他函数将其视为Foo实例。这是如何在内部发生的以及为什么会像这样工作,它是一个错误还是一个特征,我真的不知道。
关于如何修复OP的对象...好吧,没有看到它的代码,我怎么知道?给我代码,我保证思考!好了,我正在考虑...目前没有什么想法。

在我看来,print函数可能采用了一些快捷方式以提高速度。Python具有(相对较快的)内部接口和(相对较慢的)外部接口。我猜想,有人试图避免额外的开销... - Juergen
@Juergen:现在在答案中包含了一些关于源代码的信息...看起来你是对的。 - Michał Marczyk
@Michal:感谢您提供的信息!Python 系统相当清洁,但就我理解和稍稍了解的情况而言,有时会在内部进行一些快捷方式,从而可以获得巨大的速度优势。在我看来这是可以接受的,因为这些快捷方式在99%的情况下都不可见...在其他1%的情况下,必须像在这种情况下一样进行解决。当然,当遇到这样的情况时,可能会令人惊讶甚至恼火... - Juergen
@Juergen:同意。也许对于这种情况,可以在对象上设置一些标志(从C中可见),以指示它覆盖了内置方法,因此需要以缓慢的方式处理...不过我几乎没有资格评判。我猜如果vaab继续提交错误报告,那么我们就会知道Python团队的意见是什么。 - Michał Marczyk

6
所以问题是像下面这样的类的行为表现奇怪:
class Msg(unicode):
    def __init__(self, s):
        unicode.__init__(self, s)

    __unicode__ = __repr__ = __str__ = lambda self: "my message"

msg = Msg("another message")
print str(msg)
print unicode(msg)
print "%s"%msg

这将打印

my message
my message
another message

我不确定为什么会出现这种情况,也不知道如何解决它,但是通过简单地包装 Msg 进行粗略尝试,但不确定它是否有助于 OP 的问题。

class MsgX(object):
    def __init__(self, s):
        self._msg = Msg(s)

    __unicode__ = __repr__ = __str__ = lambda self: repr(self._msg)

msg = MsgX("another message")
print str(msg)
print unicode(msg)
print "%s"%msg

输出:

my message
my message
my message

我无法承担更改继承到Unicode的成本。不过,感谢您提供的简化示例。 - vaab
@vaab:如果你看一下我给出的扩展答案,添加__getattr__将会把所有本应该通过继承解析的访问器转发到包含的.msg属性。这是Python中非常强大的习惯用法,使得包装和委托与继承不相上下,而且耦合度更低。 - PaulMcG

3

我认为你的问题在于你试图扩展一个内置函数。魔术方法__不会被内置函数调用。我认为你需要进行一些包装和委托,像这样(未经测试)(也许Anurag已经完成了):

class Message(object): 

    def __init__(self, strvalue, domain, default='my message'):
        self.msg = zope.i18nmessageid.Message(strvalue,domain,default)

    def __getattr__(self,attr):
        return getattr(self.msg,attr)

    def __repr__(self): 
        return repr(zope.i18n.interpolate(self.msg.default, self.msg.mapping)) 

    def __str__(self): 
        return zope.i18n.interpolate(self.msg.default, self.msg.mapping) 

    def __unicode__(self): 
        return zope.i18n.interpolate(self.msg.default, self.msg.mapping) 

更新1 - 看起来__方法确实会被调用内置类的子类。

>>> class Z(int):
...   def __add__(self,other): return self*other
...   def __str__(self): return "***"
...
>>> a = Z(100)
>>> a + 2
200
>>> a
100
>>> str(a)
'***'
>>> "%s" % a
'***'

所以肯定存在某些不一致的情况...


(注:该段内容已经是中文,无需翻译,本次翻译仅作为示例)

好主意,但这并不起作用!;) 好吧,它对于给定的doctest有效,但是这个类不再是string的实例,这会破坏我使用和需要使用的Python常用库中的其他C检查。我明天会更清楚。 - vaab
啊,你(或那些库)正在使用isinstance,是吗?现在这个类不再继承自basestring了?嗯,那些isinstance检查难道不是在进行参数验证吗?这是一个很好的例子,说明为什么在Python中使用isinstance进行参数检查并不总是最好的选择。 - PaulMcG

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接