(modifié le 29/10/2010 avec ajout de la classe Formatx)
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…
Ce module, appelé printx.py, comporte une classe Printx() qui affiche une liste d'expressions comportant des objets de base python quelconque, avec:
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:
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:
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!