2014-08-29 4 views
2

Ok, donc le contexte général de cette question est que j'essaie de créer une classe de dictionnaire personnalisée qui va créer une représentation sous forme de chaîne du dictionnaire qui est juste une recherche de l'une des valeurs (qui sont toutes des valeurs Unicode). Dans le code réel, selon une logique interne, l'une des clés est choisie comme valeur par défaut pour la recherche, de sorte que unicode(dict_obj) renverra une seule valeur dans le dictionnaire comme u'Some value' ou si la valeur n'existe pas pour le courant clé par défaut: u'None'Erreurs Unicode sur les objets proxy de sécurité zope

Cette fonctionnalité fonctionne sans problème. Le vrai problème réside dans l'utilisation dans l'application à partir des modèles de pages zope qui enveloppent l'objet dans un proxy de sécurité. L'objet mandaté ne se comporte pas comme l'objet original.

Voici le code bouilli vers le bas de la classe dictionnaire personnalisé:

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 

Et les autorisations ZCML associées pour cette classe:

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

J'ai laissé les déclarations d'impression dans les deux __unicode__ et __str__ méthodes pour montrer le comportement différent avec les objets mandatés. créant ainsi une classe dictionnaire factice avec une clé par défaut prédéfini:

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

Tout fonctionne comme prévu. Maintenant, je peux envelopper l'objet dans un proxy de sécurité de l'emballage zope.security et faire les mêmes tests pour montrer l'erreur:

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

Comme vous pouvez le voir, appeler unicode sur l'objet approximé ne peut plus si elle contient des caractères spéciaux. Je peux voir l'objet proxy de zope.security est principalement défini avec le code C et je ne suis pas du tout familier avec l'API C Python, mais il semble que les méthodes __str__ et __repr__ sont définies dans le code C mais pas __unicode__. Donc, pour moi, ce qui semble se passer est que quand il essaie de créer une représentation unicode de cet objet proxy, au lieu d'appeler directement la méthode __unicode__, il appelle la méthode __str__ (comme vous pouvez le voir sur les dernières instructions d'impression ci-dessus)), qui renvoie une chaîne de caractères codée utf-8, mais qui est ensuite convertie en unicode (en utilisant le codage ascii par défaut). Donc, ce qui se passe semble être l'équivalent de ceci:

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

cours Alors elle se traduira par une UnicodeDecodeError dans ce cas, en essayant de décoder une chaîne utf-8 avec ascii. Comme prévu, si je pouvais spécifier le codage de utf-8 il n'y aurait pas de problème.

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

Mais je ne peux pas changer cela, puisque nous parlons des zope.pagetemplate et zope.tales paquets qui créent la représentation unicode de tous types d'objets, et ils semblent toujours travailler avec la sécurité des objets approximé (à partir de zope.security). De plus, il n'y a aucun problème à appeler la méthode __unicode__ directement sur l'objet.

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

Donc, le vrai problème est que unicode(prox) appelle la méthode __str__. Cela fait un moment que je tourne autour de ça et je ne sais pas où aller maintenant.Toute idée serait très appréciée.

+0

Votre analyse est correcte et l'emballage ne prend pas en charge un crochet Unicode; en partie parce que le * python 2 C-API n'a jamais défini un emplacement pour cela *. L'encapsuleur doit définir un attribut '__unicode__' sur le type et il devra gérer le cas où l'objet encapsulé n'a pas ce type de hook lui-même. Tricky, qui est probablement pourquoi il n'a pas encore été abordé. –

+0

Ceci est certainement un bug; l'implémentation Python gère __unicode__'; seule l'implémentation C est manquante. N'hésitez pas à le signaler en tant que tel à https://bugs.launchpad.net/zope.security –

+0

@MartijnPieters merci pour l'aperçu de l'API C-Python. Cela rend les choses plus difficiles que je ne le pensais au départ. Au moins pour l'instant, le bug est rapporté sur https://bugs.launchpad.net/zope.security/+bug/1367566 Plus tard, j'ai trouvé un bug lié déjà signalé dans le paquet 'zope.proxy', mais il semble être un implémentation C différente de la classe proxy. https://bugs.launchpad.net/zope.proxy/+bug/1262702 –

Répondre

0

A en juger par ce que vous avez dit à propos de l'API C définissant __str__ et __repr__ méthodes, mais pas __unicode__ méthodes, je soupçonne que quelle que soit la bibliothèque C que vous utilisez a été écrit pour être compatible avec Python 3. Je ne suis pas au courant Zope, mais je suis relativement confiant que cela devrait être le cas.

In Python 2, the object model specifies str() and unicode() methods. If these methods exist, they must return str (bytes) and unicode (text) respectively.

In Python 3, there’s simply str(), which must return str (text).

je manquerai un peu le point avec votre programme, mais avez-vous vraiment besoin de la méthode définie __unicode__? Comme vous l'avez dit, tout dans la dict appartient au jeu de caractères Unicode. Donc, l'appel de la méthode __str__ va décoder cela en utf-8, et si vous voulez voir les binaires de la chaîne, pourquoi ne pas simplement le encode?

Notez que decode() renvoie un objet chaîne, tandis que encode() renvoie un objet octets.

Si vous le pouvez, veuillez poster un commentaire/commentaire afin que je puisse comprendre un peu plus ce que vous essayez de faire.

+0

Eh bien, la bibliothèque C est écrite pour être compatible avec Python 2 et 3. Je vois certaines des définitions dans un bloc de '#if PY_MAJOR_VERSION < 3' avec un commentaire ci-dessus disant pour la compatibilité Python 2. Cependant, il semble que la compatibilité ** unicode() ** pour Python 2 en ait été exclue. –

+0

Le problème principal ici est que le code traite avec un paquet zope tiers qui est utilisé pour le rendu des objets python en code HTML. Il s'attend à être en mesure de récupérer une représentation unicode d'un objet et je ne peux pas changer cela. L'un des problèmes du traitement des chaînes d'octets codées dans la logique d'un programme est que le type d'encodage peut être perdu. Donc maintenant, le comportement (et le problème) est que la chaîne est encodée en utf-8, puis elle est passée à un paquet tiers qui ne sait pas quel est le codage, et tente de le décoder avec le système par défaut qui est ASCII. –

+0

Malheureusement, il semble être un problème très spécifique à l'interaction de ce paquet zope avec mon objet personnalisé. –

0

Si quelqu'un cherche une solution temporaire à ce problème, je peux partager les correctifs monkeypatch que nous avons implémentés. Patcher ces deux méthodes de zope.tal et zope.tales semble faire l'affaire. Cela fonctionnera bien tant que vous savez que l'encodage sera toujours 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 

et celui-ci:

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 
Questions connexes