zope安全代理对象上的Unicode错误

时间:2014-08-29 19:26:32

标签: python zope

好的,所以这个问题的一般背景是我试图创建一个自定义字典类,它将创建字典的字符串表示,它只是查找其中一个值(都是unicode)值)。在实际代码中,根据某些内部逻辑,其中一个键被选为查找的当前默认值,因此unicode(dict_obj)将在字典中返回单个值,例如u'Some value'或者如果当前默认密钥的值不存在:u'None'

此功能没有问题。真正的问题在于在zope页面模板的应用程序中使用它时,该模板将对象包装在安全代理中。代理对象的行为与原始对象的行为不同。

以下是自定义词典类的简化代码:

class IDefaultKeyDict(Interface):

    def __unicode__():
        """Create a unicode representation of the dictionary."""

    def __str__():
        """Create a string representation of the dictionary."""


class DefaultKeyDict(dict):
    """A custom dictionary for handling default values"""
    implements(IDefaultKeyDict)

    def __init__(self, default, *args, **kwargs):
        super(DefaultKeyDict, self).__init__(*args, **kwargs)
        self._default = default

    def __unicode__(self):
        print "In DefaultKeyDict.__unicode__"
        key = self.get_current_default()
        result = self.get(key)
        return unicode(result)

    def __str__(self):
        print "In DefaultKeyDict.__str__"
        return unicode(self).encode('utf-8')

    def get_current_default(self):
        return self._default 

此类的关联zcml权限:

<class class=".utils.DefaultKeyDict">
  <require
    interface=".utils.IDefaultKeyDict" 
    permission="zope.View" />
</class>

我已经在__unicode____str__方法中留下了print语句,以显示代理对象的不同行为。因此,使用预定义的默认密钥创建一个虚拟字典类:

>>> dummy = DefaultKeyDict(u'key2', {u'key1': u'Normal ascii text', u'key2': u'Espa\xf1ol'})
>>> dummy
{u'key2': u'Espa\xf1ol', u'key1': u'Normal ascii text'}
>>> str(dummy)
In DefaultKeyDict.__str__
In DefaultKeyDict.__unicode__
'Espa\xc3\xb1ol'
>>> unicode(dummy)
In DefaultKeyDict.__unicode__
u'Espa\xf1ol'
>>> print dummy
In DefaultKeyDict.__str__
In DefaultKeyDict.__unicode__
Español

一切都按预期工作。现在,我可以将对象包装在zope.security包中的安全代理中,并执行相同的测试以显示错误:

>>> from zope.security.checker import ProxyFactory
>>> prox = ProxyFactory(dummy)
>>> prox
{u'key2': u'Espa\xf1ol', u'key1': u'Normal ascii text'}
>>> type(prox)
<type 'zope.security._proxy._Proxy'>
>>> str(prox)
In DefaultKeyDict.__str__
In DefaultKeyDict.__unicode__
'Espa\xc3\xb1ol'
>>> unicode(prox)
In DefaultKeyDict.__str__
In DefaultKeyDict.__unicode__
*** UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 4: ordinal not in range(128)

正如您所看到的,如果代理对象包含任何特殊字符,则无法再调用代理对象unicode。我可以看到来自zope.security的代理对象主要是使用C代码定义的,我对C Python API并不熟悉,但似乎__str____repr__方法在C代码中定义,但不是__unicode__。所以对我来说,似乎正在发生的事情是,当它试图创建这个代理对象的unicode表示时,不是直接调用__unicode__方法,而是调用__str__方法(就像你能做到的那样)从上面的最后几个打印语句中看到,它返回一个utf-8编码的字节字符串,但然后将其转换为unicode(使用默认的ascii编码)。所以发生的事情似乎相当于:

>>> unicode(prox.__str__())
In DefaultKeyDict.__str__
In DefaultKeyDict.__unicode__
*** UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 4: ordinal not in range(128)

当然,在这种情况下会导致UnicodeDecodeError,尝试使用ascii解码utf-8字符串。正如预期的那样,如果我可以指定utf-8的编码,那么就不会有问题。

>>> unicode(prox.__str__(), encoding='utf-8')
In DefaultKeyDict.__str__
In DefaultKeyDict.__unicode__
u'Espa\xf1ol'

但我无法改变,因为我们正在谈论从所有类型的对象中创建unicode表示的zope.pagetemplatezope.tales包,它们似乎总是在起作用使用安全代理对象(来自zope.security)。另外值得注意的是,直接在对象上调用__unicode__方法没有问题。

>>> prox.__unicode__()
In DefaultKeyDict.__unicode__
u'Espa\xf1ol'

所以真正的问题是unicode(prox)调用了__str__方法。我已经在这上面旋转了一段时间,并且不知道现在还有什么路要走。任何见解都会非常感激。

2 个答案:

答案 0 :(得分:0)

根据您对定义__str____repr__方法而非__unicode__方法的C API所说的话,我怀疑您使用的C库已被编写为兼容python 3.我不熟悉zope,但我相信这应该是这样的。

  

在Python 2中,对象模型指定 str ()和 unicode ()   方法。如果存在这些方法,则必须返回str(bytes)和   unicode(文本)。

     

在Python 3中,只有 str (),它必须返回str(文本)。

我可能会略微忽略您的程序,但您真的需要定义__unicode__方法吗?正如你所说,dict中的所有内容都属于unicode字符集。因此,调用__str__方法会将其解码为utf-8,如果您想查看字符串的二进制文件,为什么不只是encode呢?

请注意decode()返回一个字符串对象,而encode()返回一个字节对象。

如果可以的话,请发布编辑/评论,这样我就可以了解你想要做的更多。

答案 1 :(得分:0)

如果有人正在寻找这个问题的临时解决方案,我可以分享我们实施的monkeypatch修复程序。从zope.talzope.tales修补这两个方法似乎可以解决问题。只要您知道编码始终为utf-8

,这将很有效
from zope.tal import talinterpreter

def do_insertStructure_tal(self, (expr, repldict, block)):
    """Patch for zope.security proxied I18NDicts.

    The Proxy wrapper doesn't support a unicode hook for now. The only way to
    fix this is to monkey patch this method which calls 'unicode'.
    """
    structure = self.engine.evaluateStructure(expr)
    if structure is None:
        return
    if structure is self.Default:
        self.interpret(block)
        return
    if isinstance(structure, talinterpreter.I18nMessageTypes):
        text = self.translate(structure)
    else:
        try:
            text = unicode(structure)
        except UnicodeDecodeError:
            text = unicode(str(structure), encoding='utf-8')
    if not (repldict or self.strictinsert):
        # Take a shortcut, no error checking
        self.stream_write(text)
        return
    if self.html:
        self.insertHTMLStructure(text, repldict)
    else:
        self.insertXMLStructure(text, repldict)
talinterpreter.TALInterpreter.do_insertStructure_tal = do_insertStructure_tal
talinterpreter.TALInterpreter.bytecode_handlers_tal["insertStructure"] = \
    do_insertStructure_tal

和这一个:

from zope.tales import tales


def evaluateText(self, expr):
    """Patch for zope.security proxied I18NDicts.

    The Proxy wrapper doesn't support a unicode hook for now. The only way to
    fix this is to monkey patch this method which calls 'unicode'.
    """
    text = self.evaluate(expr)
    if text is self.getDefault() or text is None:
        return text
    if isinstance(text, basestring):
        # text could already be something text-ish, e.g. a Message object
        return text
    try:
        return unicode(text)
    except UnicodeDecodeError:
        return unicode(str(text), encoding='utf-8')
tales.Context.evaluateText = evaluateText