Python: implémenter simplement __str__ sur une classe quand __unicode__ est défini

En Python 2.7 et avant, appeler la fonction str(…) sur un objet appelle forcément son implémentation de __str__, même si la méthode __unicode__ est définie (l’inverse fonctionne par contre). Il en va de même pour le statement print, qui appelle en sous-main la fonction str.

Voici comment simplement implémenter une méthode __str__ qui s’appuie sur __unicode__.

Prenons par exemple une classe représentant un fichier. Typiquement, le nom de mon fichier peut tout-à-fait contenir des caractères non ASCII: des caractères accentués.

class MyFile(object):
    def __init__(self, path):
        if isinstance(path, str):
            path = transform_to_unicode(path)
            # Je passe les détails de cette fonction magique
            # qui pourra faire l'objet d'un autre article
        self._fpath = path
    
    def path(self):
        return self._fpath
    
    def __unicode__(self):
        # Ex: [monfichier.txt]
        return u'[{0}]'.format(self.path())

Ce qui se passe, si je fais un print sur un objet, c’est que python appelle sa méthode __repr__:

>>> print MyFile('monfichier.txt')

Ce n’est pas ce que je veux!

Je pourrais donc bêtement faire que __str__ renvoie le résultat de __unicode__, mais je m’exposerai lors d’un affichage dans une console, des problèmes d’encodage, se manifestant par une exception de type UnicodeEncodeError. En plus, ça ne serait évidemment pas correct que cette fonction renvoie autre chose qu’un str. Il faut donc préciser explicitement la conversion d’encodage. Pour cela, je peux utiliser la valeur liée à la console courante.

class MyFile(object):
    # ...
    # ...
    def __str__(self):
        import sys
        return unicode(self).encode(sys.stdout.encoding or 'ascii','replace')

J’utilise la méthode encode du type unicode pour convertir en chaîne de caractères que comprendra ma console. En encodage fallback (si sys.stdout.encoding vaut None), j’utilise ASCII. Et si jamais certains caractères ne peuvent pas être convertis dans l’encodage cible, j’ai passé l’argument 'replace' pour, au pire, remplacer les caractères invalides par un '?'.

Au fait, si vous vous demandez pourquoi « sys.stdout.encoding or 'ascii'« , allez voir cet article.

À partir de Python 3, le problème devient plus simple, car toutes les chaînes de caractères sont des unicode, la fonction encode servant alors à obtenir la chaîne d’octets dans le bon encodage.

Vous aimerez aussi...

Laisser un commentaire