Table des matières

Un affichage paramétrable des nombres réels et des chaines dans des listes, tuples, dictionnaires et nombres complexes

Problématique

En python, on sait afficher un nombre réel (à virgule flottante) avec le nombre de décimale qu'il faut:

print round(0.57338244358883583,5)
0.57338
 
print "%.5f" % 0.57338244358883583
0.57338

Mais quand on a une liste, ou même un arbre (une liste de liste de liste…), ça ne marche plus!

Par exemple:

[12.321456987,5.789654123,123.789654123]

Comment je fais pour l'afficher avec 4 chiffres seulement après la virgule pour tous les nombres réels qui se trouvent dedans? On peut avoir recours à des opérations sur les listes, mais ce n'est pas simple, cela ne donne pas toujours des résultats corrects, et cela ne marche plus pour les arbres (liste de liste de listes…):

print map(lambda x: round(x,4), [12.321456987,5.789654123,123.789654123])
[12.3215, 5.7896999999999998, 123.7897]

On a souvent aussi à “échapper” aux affichages bizarres des nombres réels dans une liste, du genre:

print 0.1
0.1
print [0.1]
[0.10000000000000001]

Le problème se pose aussi pour les tuples, les dictionnaires, et les nombres complexes, ainsi, bien sûr, pour les données imbriquées les unes dans les autres (liste de tuples de listes…).

Enfin, lorsque des caractères accentués sont dans une liste, les instructions “print” donnent des résultats curieux, parce qu'ils dépendent de l'encodage de la console dans laquelle on affiche.

print "éàç"
"éàç"
print ["éàç"]
['\xe9\xe0\xe7']

Pour avoir un code portable, il faut connaitre l'encodage des chaines qu'on manipule, et les convertir automatiquement avec l'encodage voulu (de la console par exemple).

Voilà une solution.

Solution proposée

Le code proposé est récursif.

Il traite les structures de base suivantes de Python:

Le résultat est une chaîne de caractères qui sera utilisée telle quelle.

On a le choix entre les 2 types d'affichage suivants pour les nombres réels (à virgule flottante):

Le code est suffisamment simple pour être adapté à des cas différents, non traités ici, comme par exemple pour affichage des nombres avec exposants.

La fonction est conçue pour l'affichage dans une console, mais peut aussi servir dans n'importe quelle autre utilisation, y compris pour l'enregistrement sur disque.

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import sys
 
def formate(x, nbc=-15, decod="utf-8", encod=None):
    """transforme en chaine un objet imprimable avec le bon formatage des flottants et le bon encodage des chaines
  - nbc: nombre de chiffres pour les flottants:
    - Si nbc<0: abs(nbc) chiffres significatifs.
    - Si nbc>=0: nbc chiffres après la virgule.
  - decod: encodage des chaines non-unicode (pour les chaines du code source: mettre l'encodage du code source)
  - encod: encodage voulu pour la chaine de sortie (pour l'affichage: trouve encodage de la console)
"""
 
    if encod==None:
        encod = sys.stdout.encoding
        if encod==None:
            encod = 'utf-8'  # cas particulier de la console d'eclipse
 
    if isinstance(x,int) or isinstance(x,long):
        return "%d" % (x)
 
    elif isinstance(x,float):
        f = "%d" % (abs(nbc))
        if nbc<0:
            f = "%." + f + "g"
        else:
            f = "%." + f + "f"
        return f % (x)
 
    elif isinstance(x,complex):
        L = "("
        y = x.real
        if isinstance(y,float) and float(int(y))==y:
            y=int(y)
        L += formate(y,nbc,encod,decod)
        y = x.imag
        if isinstance(y,float) and float(int(y))==y:
            y=int(y)
        if y>=0:
            L += "+"
        L += formate(y,nbc,encod,decod) + "j)"
        return L
 
    elif isinstance(x,str):
        return '"%s"' % (x.decode(decod).encode(encod,'replace'))
 
    elif isinstance(x,unicode):
        return '"%s"' % (x.encode(encod,'replace'))
 
    elif isinstance(x,list):
        L = "["
        if x!=[]:
            for elem in x:
                L += "%s, " % formate(elem,nbc,decod,encod)
            L=L[:-2]
        L += "]"
        return L
 
    elif isinstance(x,tuple):
        L = "("
        if x!=():
            for elem in x:
                L += "%s, " % formate(elem,nbc,decod,encod)
            L = L[:-2]
        L += ")"
        return L
 
    elif isinstance(x,dict):
        L = "{"
        if x!={}:
            for k, v in x.iteritems():
                L += '%s:%s, ' % (formate(k,nbc,decod,encod), formate(v,nbc,decod,encod))
            L = L[:-2]
        L += "}"
        return L
 
    else:
        return "%s" % (x)

Quelques exemples d'utilisation (les résultats attendus sont en commentaires):

x = [0.92498894346812754, [6127.6807831692537, 7.6312137788054568, [95.047644748356008, 0.21995139819419918, 0.31819577813609723], 0.035713503484146036], 0.89938839028430906]
print formate(x,-5)
# [0.92499, [6127.7, 7.6312, [95.048, 0.21995, 0.3182], 0.035714], 0.89939]
print formate(x,5)
# [0.92499, [6127.68078, 7.63121, [95.04764, 0.21995, 0.31820], 0.03571], 0.89939]
 
x=[0.1]
print formate(x)
# [0.1]
 
x=complex(0.271165409484, 0.278306542098)
print formate(x,5)
# (0.27117+0.27831j)
 
x=complex(0.271165409484, -0.278306542098)
print formate(x,5)
# (0.27117-0.27831j)
 
x=complex(0.271165409484, 5)
print formate(x,5)
# (0.27117+5j)
 
x={"a":5, "toto":0.626229260038258, 4:10, 1.32456:0.587782755128, "c":"abcéèàçïô"}
print formate(x,5)
# {"a":5, 4:10, "c":"abcéèàçïô", "toto":0.62623, 1.32456:0.58778}
 
x = [12.321456987,5.789654123,123,"abcéèàçïô",(78.197586424365,56.576284659321,u"abcéèàçïô")]
print formate(x,4)
# [12.3215, 5.7897, 123, "abcéèàçïô", (78.1976, 56.5763, "abcéèàçïô")]
 
x = "abcéèàçïô"
print x
# abcéèàçïô
print formate(x)
# "abcéèàçïô"
 
x = u"abcéèàçïô"
print x
# abcéèàçïô
print formate(x)
# "abcéèàçïô"
 
x = ["abcéèàçïô", u"abcéèàçïô"]
print x
# ['abc\xc3\xa9\xc3\xa8\xc3\xa0\xc3\xa7\xc3\xaf\xc3\xb4', u'abc\xe9\xe8\xe0\xe7\xef\xf4']
print formate(x)
# [abcéèàçïô, abcéèàçïô]

Amusez-vous bien (et dieu sait qu'il n'est pas facile de s'amuser avec les encodages :-D )