Outils pour utilisateurs

Outils du site


printx

Un module pour afficher correctement n'importe quel objet de base de Python

(modifié le 29/10/2010 avec ajout de la classe Formatx)

Problématique

Le problème posé est d'afficher correctement dans une console quelconque les nombres à virgule flottante ainsi que des chaines de caractères, même lorsqu'ils se trouvent dans des objets python de base: nombre complexe, liste, tuple, dictionnaire, set, frozenset, et même lorsque ces objets sont imbriqués les uns dans les autres avec une “profondeur” quelconque (liste de tuple de liste de dictionnaire etc…).

Exemples de difficultés:


Affichage de nombres en virgule flottante (float)

print 0.1, [0.1]
0.1 [0.10000000000000001]

Avant Python 2.7, on constate que les nombres flottants ne sont pas affichés de la même façon lorsqu'ils se trouve dans un objet python (ici une liste).

Par ailleurs, afficher une liste avec tous les nombres float affichés avec, par exemple, 5 décimales, nécessite un traitement qui doit être recodé à chaque fois.


Affichage d'une chaine encodée en utf-8 dans une console DOS ayant un encodage cp850:

print "abcéèçàù", ["abcéèçàù"] 
abc├®├¿├º├á├╣ ['abc\xc3\xa9\xc3\xa8\xc3\xa7\xc3\xa0\xc3\xb9']

On constate que les chaines de caractères doivent être encodées en fonction de la console d'affichage (qui peut changer d'une exécution à l'autre), mais aussi qu'elles ne sont pas affichées de la même façon lorsqu'elles se trouvent dans un objet python (ici une liste).


Affichage d'une liste de chaine comportant divers encodages (utf-8 et unicode) dans une console eclipse ayant un encodage utf-8:

print "abcéèçàù", ["abcéèçàù", u"abcéèçàù"]
abcéèçàù ['abc\xc3\xa9\xc3\xa8\xc3\xa7\xc3\xa0\xc3\xb9', u'abc\xe9\xe8\xe7\xe0\xf9']

On voit que la 1ère chaine utf-8 a été correctement affichée dans la console eclipse (configurée ici en utf-8), mais pas quand elle se trouve dans une liste.


Etc…

Bref, ce n'est pas un résultat correct. Et on passe trop de temps à faire des corrections dans le code pour que l'affichage soit correct. Pour autant qu'on y arrive…

Module proposé

Ce module, appelé printx.py, comporte une classe Printx() qui affiche une liste d'expressions comportant des objets de base python quelconque, avec:

  • un format d'affichage pour les nombres à virgule flottante
  • les encodages nécessaires pour décoder les chaines non-unicode, et pour encoder toutes les chaines en fonction de la console d'affichage

Pour le décodage par défaut des chaines non-unicode, j'ai mis 'utf-8' parce que tous mes scripts sont en utf-8, mais rien ne vous empêche de mettre un autre encodage (cp1252, …), et même de le changer en cours d'exécution d'un script.

Lors de l'importation du module printx, son exécution déclenche l'instanciation de cette classe avec la variable printx qui est donc immédiatement disponible. Vous voyez dans le code utilisé la méthode spéciale __call__() qui a l'avantage suivant: une fois la classe instanciée par printx, un simple appel de printx() appelle en fait cette méthode.

Pour cette classe, on peut changer à tout moment les paramètres de réglage en appelant la méthode configure(): format des nombres flottants, et encodage/décodage des chaines.

Cette classe utilise les 2 fonctions suivantes:

  • formchaine(ch, decod, encod) qui affiche la chaine ch avec les encodages mentionnés
  • formdatas(x, ffl, decod, encod) qui affiche un objet python quelconque, avec le format choisi pour l'affichage des nombres flottants, et l'encodage pour les chaines.

Ces 2 fonctions, qui retourne une chaine pour affichage, peuvent être utilisées directement, ce qui permet, par exemple, d'utiliser ces fonctions pour écrire dans un fichier sur disque.

En ce qui concerne les 3 paramètres de configuration, voici leur signification:

Formatage des nombres à virgule flottante: ffl=-15. Trois valeurs sont possibles:

  • ffl = nombre entier négatif ⇒ on provoque l'affichage selon “%g” % nb, le nombre donné représentant un affichage “intelligent” de ffl nombres significatif (pas forcément après la virgule), et dans lequel les derniers zéros après la virgule ne sont pas affichés.
  • ffl = nombre entier positif ⇒ on provoque l'affichage selon “%f” % nb, le nombre donné représentant un affichage avec ffl nombres après la virgule. S'il y a moins de chiffres après la virgule, des zéros complètent l'affichage.
  • ffl = chaine de caractère ⇒ il s'agit de la chaine de formatage à utiliser: ffl % nb.

Decodage des chaines de caractères non-unicode: decod='utf-8' signifie qu'on transformera en unicode la chaine initialement codée en utf-8 avec ch.decode(decod)

Encodage des chaines de caractères: encod signifie qu'on encodera les chaines unicode avec ch.encode(encod)

Enfin, il a été ajouté une classe 'Formatx', avec formatx comme variable d'instance par défaut, qui fait la même chose que Printx, mais se contente de créer la chaine sans l'afficher.


#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import division
# Python 2.7
 
"""
Module d'affichage des objets Python de base:
=> int, long, float, complex, str, unicode, list, tuple, dict, set, frozenset
 
- configuration du format d'affichage des nombres float
- configuration de l'encodage en fonction de la console d'affichage
"""
 
import sys
 
#############################################################################
# Variables globales
#
coding = 'utf-8' # doit correspondre a la ligne coding du code source 
 
codout = sys.stdout.encoding  # encodage pour affichage dans console courante
if codout==None:
    codout = coding # car certaines consoles d'affichage renvoient None
 
#############################################################################
def formchaine(ch, decod=coding, encod=codout):
    """retourne la chaine ch avec le bon encodage pour l'affichage
    """
    if isinstance(ch,unicode):
        return ch.encode(encod, 'replace')
    else:
        if decod==encod:
            return ch
        else:    
            return ch.decode(decod).encode(encod, 'replace')
 
#############################################################################
def formdatas(x, ffl=-15, decod=coding, encod=codout):
    """transforme en chaine pour affichage n'importe quel objet 
       python de base
    """
 
    # traitement des entiers
    if isinstance(x,(int, long)):
        return "%d" % (x)
 
    # traitement des flottants
    elif isinstance(x,float):
        if isinstance(ffl, int):
            frmt = "%d" % (abs(ffl))
            if ffl<0:
                frmt = "%." + frmt + "g"
            else:
                frmt = "%." + frmt + "f"
        else:
            frmt = ffl
        return frmt % (x)
 
    # traitement des complexes
    elif isinstance(x, complex):
        L = "("
        y = x.real
        L += "%s" % formdatas(y, ffl, decod, encod)
        y = x.imag
        if y>=0:
            L += "+"
        L += "%s" % formdatas(y, ffl, decod, encod) + "j"
        L += ")"
        return L
 
    # traitement des complexes
    elif isinstance(x, (str, unicode)):
        return '"%s"' % formchaine(x, decod, encod)
 
    elif isinstance(x, list):
        L = "["
        if x!=[]:
            L += "%s" % formdatas(x[0], ffl, decod, encod)
            for elem in x[1:]:
                L += ", %s" % formdatas(elem, ffl, decod, encod)
        L += "]"
        return L
 
    # traitement des tuples
    elif isinstance(x,tuple):
        L = "("
        if x!=():
            L += "%s" % formdatas(x[0], ffl, decod, encod)
            for elem in x[1:]:
                L += ", %s" % formdatas(elem, ffl, decod, encod)
        L += ")"
        return L
 
    # traitement des dictionnaires
    elif isinstance(x,dict):
        L = "{"
        if x!={}:
            for k, v in x.iteritems():
                L += '%s:%s, ' % (formdatas(k, ffl, decod, encod), formdatas(v, ffl, decod, encod))
            L = L[:-2]  # suppression du dernier ", " en trop
        L += "}"
        return L
 
    # traitement des ensembles set
    elif isinstance(x,set):
        L = "set(["
        if x != set([]):
            for y in x:
                L += "%s, " % formdatas(y, ffl, decod, encod)
            L = L[:-2]  # suppression du dernier ", " en trop    
        L += "])"
        return L
 
    # traitement des ensembles frozenset
    elif isinstance(x,frozenset):
        L = "frozenset(["
        if x != set([]):
            for y in x:
                L += "%s, " % formdatas(y, ffl, decod, encod)
            L = L[:-2]  # suppression du dernier ", " en trop    
        L += "])"
        return L
 
    # traitement des données non typées avant: on laisse passer
    else:
        return "%s" % (x)
 
#############################################################################
class Formatx(object):
    """fait la même chose que Printx, mais renvoie une chaine sans l'afficher
    """
 
    #========================================================================
    def __init__(self, ffl=-15, decod=coding, encod=codout):
        """initialisation
        """
        self.config(ffl, decod, encod)
 
    #========================================================================
    def config(self, ffl=-15, decod=coding, encod=codout):
        """configuration à l'initialisation, ou après
        """
        if isinstance(ffl, int):
            frmt = "%d" % (abs(ffl))
            if ffl<0:
                self.ffl = "%." + frmt + "g"
            else:
                self.ffl = "%." + frmt + "f"
        else:
            self.ffl = ffl
        self.decod = decod
        self.encod = encod
 
    #========================================================================
    def __call__(self, *args):
        """création de la chaine à partir de la liste des arguments
        """
        ch = u"".encode(self.encod)
        if len(args)!=0:
            for x in args:
                if isinstance(x, (str, unicode)):
                    # les chaines de 1er rang n'ont pas de guillemets
                    ch += formchaine(x, self.decod, self.encod) + " "
                else:
                    ch += formdatas(x, self.ffl, self.decod, self.encod) + " "
        return ch
 
#############################################################################
class Printx(object):
    """affiche n'importe quel objet Python de base avec le bon encodage
       des chaines et le bon format des nombres flottants
    """
 
    #========================================================================
    def __init__(self, ffl=-15, decod=coding, encod=codout):
        """initialisation
        """
        self.config(ffl, decod, encod)
 
    #========================================================================
    def config(self, ffl=-15, decod=coding, encod=codout):
        """configuration à l'initialisation, ou après
        """
        if isinstance(ffl, int):
            frmt = "%d" % (abs(ffl))
            if ffl<0:
                self.ffl = "%." + frmt + "g"
            else:
                self.ffl = "%." + frmt + "f"
        else:
            self.ffl = ffl
        self.decod = decod
        self.encod = encod
 
    #========================================================================
    def __call__(self, *args):
        """création et affichage de la chaine à partir de la liste des 
           arguments
        """
        if len(args)==0:
            print
        else:
            for x in args:
                if isinstance(x, (str, unicode)):
                    # les chaines de 1er rang n'ont pas de guillemets
                    print formchaine(x, self.decod, self.encod),
                else:
                    print formdatas(x, self.ffl, self.decod, self.encod),
            print
 
#############################################################################
# instanciation par défaut
printx = Printx()
formatx = Formatx()
 
#############################################################################
if __name__ == '__main__': 
 
    # tests de printx()
 
    printx(0.1, [0.1])
    printx([1.23, 4.56, ("a", "b", [7,8,"9", 0.1]), 789.01])
    printx((1.23, 4.56, 789.01, 20.0/3), "abcéèçàù", ["abcéèçà", u"abcéèçà"])
    printx(complex(12.0*2/5,-3.456))
    x = [0.1, 0.2, 0.3, 'abcéèçà', u'abcéèçà2']
    printx(set(x))
    printx(frozenset(x))
    printx.config(ffl=5)
    printx(20.0/3, [20.0/3])
 
    # tests de formatx()
 
    ch = formatx(12,45)
    print ch
    print formatx([1.23, 4.56, ("a", "b", [7,8,"9", 0.1]), 789.01])
    print formatx((1.23, 4.56, 789.01, 20.0/3), "abcéèçàù", ["abcéèçà", u"abcéèçà"])
    print formatx(0.1, [0.1])
    print formatx(complex(12,3.456))
    print formatx(complex(12.0*2/5,-3.456))
    print formatx()
    x = [0.1, 0.2, 0.3, 'abcéèçà', u'abcéèçà2']
    print formatx(set(x))
    print formatx(frozenset(x))
    print formatx("abcéèçàù", ["abcéèçàù"])
    print formatx("abcéèçàù", ["abcéèçàù", u"abcéèçàù"])
    formatx.config(ffl=5)
    print formatx(20.0/3, [20.0/3])

Instruction d'importation suggérée dans le code utilisateur du module:

from printx import printx, formatx

Mais rien ne vous empêche d'importer aussi les classes Printx et Formatx, ce qui vous permettrait d'ajouter d'autres instances de classe avec d'autres paramètres de configuration.


Exemples d'utilisation (après l'importation du module comme ci-dessus):

printx(0.1, [0.1])
0.1 [0.1]
printx([1.23, 4.56, ("a", "b", [7,8,"9", 0.1]), 789.01])
[1.23, 4.56, ("a", "b", [7, 8, "9", 0.1]), 789.01]
printx((1.23, 4.56, 789.01, 20.0/3), "abcéèçàù", ["abcéèçà", u"abcéèçà"])
(1.23, 4.56, 789.01, 6.66666666666667) abcéèçàù ["abcéèçà", "abcéèçà"]
printx(complex(12.0*2/5,-3.456))
(4.8-3.456j)
x = [0.1, 0.2, 0.3, 'abcéèçà', u'abcéèçà2']
 
printx(set(x))
set(["abcéèçà", 0.3, 0.2, 0.1, "abcéèçà2"])
 
printx(frozenset(x))
frozenset(["abcéèçà", 0.3, 0.2, 0.1, "abcéèçà2"])

Exemple de modification du formatage des nombres à virgule flottante:

printx.config(ffl=5)
printx(20.0/3, [20.0/3])
6.66667 [6.66667]

Etc…


Amusez-vous bien!

printx.txt · Dernière modification: 2010/10/29 21:44 par tyrtamos

Outils de la page