Sauvegarder des variables python dans des fichiers et les récupérer
MALGRÉ la simplification de manipulation des fichiers en python, ce module désire apporter des facilités à l'écriture de fichiers un peu plus complexes, comme des fichiers CSV (Comma Separated Values), ou plutôt TSV, utilisant des tabulations comme séparateur.
2022.08.12
1. Argument • 2. Emploi du module • 3. Script auto-commenté • 4. Versions
Argument ↑
Pour sauvegarder ou charger des données, python n'est guère plus pratique que les bons vieux BASIC, avec un bloc ressemblant à :
fd =open("myfile, "r") OPEN "I", 13, "myfile" texte =fd.read() INPUT #13, Var$ fd.close() CLOSE 13
Le code a par la suite été quelque peu simplifié en enfermant la lecture ou l'écriture dans une structure semble-t-il inventée pour l'occasion :
with open("nomdefichier", "r") as fd : texte =fd.read()
…la fermeture du fichier ayant lieu lors de la sortie de la structure with / as.
La seule ligne
open("essai.txt", "w").write("Une chaîne")
fonctionne, mais il n'est pas dit qu'elle soit légale : '__warningregistry__': {'version': 0}} apparaît dans le dictionnaire des variables vars().
Le module v2f réduit le code à une seule fonction sans paramètre (pas de "r" ou "w") et sans descripteur de fichier qu'il faut ensuite fermer :
v2f.saveS("mon_fichier", "Eggs and Spam!") sauve un simple texte dans un fichier
texte =v2f.loadS("mon_fichier") lit un fichier et transmet son contenu dans une chaine
De la même manière, une liste est très simplement sauvegardée dans un fichier :
v2f.saveL("petitdej", ["Eggs", 2, "Spam", 3, "!"]) sauve une liste dans un fichier
liste =v2f.loadL("petitdej") lit un fichier et transmet son contenu dans une chaine, les éléments séparés par un retour à la ligne :
'Eggs' 2 'Spam' 3 '!'
Une liste de liste est sauvegardée dans un fichier TSV (tabulations entre les cellules) :
v2f.saveT("mon.csv", [["Eggs", 2], ["Spam", 3]]) sauve une liste dans un fichier
liste =v2f.loadL("petitdej") lit un fichier et transmet son contenu dans une liste ('→' symbolise la tabulation)
'Eggs' → 2 'Spam' → 3
Les dictionnaires sont sauvegardés comme une liste de lignes de deux cellules :
v2f.saveD("moby.dic", { 2:"", "2": {"a":1}, "a":[1,2,3]}) sauve un dictionnaire
dico =v2f.loadD("moby.dic") charge un fichier et place les données dans une variable dictionnaire
2 → '' '2' → { 'a': 1 } 'a' → [ 1, 2, 3 ]
Les fichiers binaires sont également traités (par les objets bytes) :
v2f.saveB("my.bin", bytes([43, 10, 245, 55])) sauve un objet bytes
octets =v2f.loadB("my.bin") lit un binaire et l'insère dans un objet bytes
Ce module fait double ou triple emploi avec d'autres modules de sauvegarde de données (voir modules) mais certains sont abandonnés, n'ont jamais été très pratiques et les fichiers produits ne sont pas forcément lisibles par un être humain.
Voir les versions pour les corrections, améliorations et nouveautés.
Emploi du module ↑
Dans un script python, il est possible d'importer ce module de deux façons :
- import v2f l'appel à chacune des fonctions devra être préfixé de v2f : my_var =v2f.loadB("nom_de_fichier")
- from v2f import * les fonctions sont utilisées sans préfixe (les noms de fonctions sont suffisamment spécifiques pour ne pas se confondre avec d'autres)
Pour que le module v2f.py (clic droit pour le télécharger) puisse être importé par le script, il faut par exemple qu'il soit situé dans le même répertoire, ce qui peut être gênant si vos scripts ne figurent pas tous dans le même répertoire.
Si vous êtes administrateur d'un système GNU+Linux, vous pouvez placer le module, avec les droits du super-utilisateur, parmi les librairies de python :
#cp v2f.py /usr/lib/python3.9En mode super-utilisateur: su - [Enter]
Si vous êtes simple utilisateur d'un système GNU+Linux, vous devriez pouvoir le copier dans le répertoire /home/toto/.local/lib/python3.9 si votre compte est toto et que la version de python est la 3.9.
Le code est commenté avec des DocStrings. Pour faire apparaître les fonctions et leur commentaire dans une console :
>>> import v2f >>> help(v2f)
Le script (ver0.6) commenté en bilingue ↑
À copier-coller dans un fichier texte, ou à télécharger (clic droit) :
""" Saving and loading in Python3 binary objects, strings, lists, tab-CSV and dictionaries Sauvegarder et charger en Python3 des objets binaires, chaînes, listes, tab-CSV et dictionnaires Jean-Christophe Beumier - 2022.12.01 - Ver0.6 - new : coding() http://jchr.be/python/v2f.htm for informations (french) This program is free software. It comes without any warranty. You can modify it, and redistribute it, under GPLv3 conditions. https://www.gnu.org/licenses/gpl-3.0.en.html Ce programme est libre, utilisez-le sous votre propre responsabilité. Vous pouvez le modifier et le distribuer, aux conditions GPLv3. """ def coding() : """ These codes are used to force a specific text-coding Ces codes sont utilisés pour forcer l'encodage d'un texte """ print(""" utf-8 (default) : universal / universel (Unicode set) iso-8859-1 – latin1 – latin-1 : Western Europa / Europe occidentale iso-8859-15 – latin9 : slightly modified latin-1 légèrement modifié cp1252 : Windows enhanced latin-1 (utilis° des car. 160-191 chars are used) iso-8859-2 – latin-2 : Eastern Europa / Europe de l'est iso-8859-3 – latin-3 : Southern Europa / Europe du sud iso-8859-4 – latin-4 : Northern Europa / Europe du nord iso-8859-5 : Cyrillic / cyrillique iso-8859-6 : Arabic / arabe iso-8859-7 : Monotonic Greek / grec monotonique iso-8859-8 : Hebrew / hébreu iso-8859-9 – latin-5 : Turkish, Kurdish / turc, kurde iso-8859-10 – latin-6 : Northern Europa / Europe du nord iso-8859-11 : thai iso-8859-13 – latin-7 : Baltic / baltique iso-8859-14 – latin-8 : Celtic / celtique iso-8859-16 – latin-10 : Southern-Eastern Europa / Europe du sud-est """) ### Blobs section (binary objects) def saveB(fichier, blob) : """ saveB(my_file, my_blob) Save a Blob (binary object, bytes from 0 to 255) in a file Sauvegarde un Blob (valeurs de 0 à 255) dans un fichier """ with open(fichier, "wb") as fd : fd.write(blob) def loadB(fichier) : """ my_blob =loadB(filename) Load a Blob (binary file, bytes values from 0 to 255) from a file Charge un Blob (fichier binaire, octets de valeurs de 0 à 255) d'un fichier """ with open(fichier, "rb") as fd : return fd.read() ### Section Strings def saveS(fichier, chaine, mode="utf-8") : """ saveS(my_file, my_string) Saves a String into a human readable file (utf-8 or your prefered encoding) Sauvegarde une chaîne dans un fichier lisible par un être humain, UTF-8 par défaut ou selon l'encodage désiré """ with open(fichier, "w", encoding=mode) as fd : return fd.write(chaine) def loadS(fichier, mode="") : """ my_string =loadS(filename) Loads a String from a human readable file. You can mention an encoding, otherwise it's utf-8 or cp1252 (an improved iso-8859-1 / latin-1 ) Charge une chaîne d'un fichier texte. Il est possible de préciser un encodage, sinon c'est UTF-8 qui est tenté, et puis CP1252 (extension du iso-8859-1 / latin-1) """ if mode : with open(fichier, "r", encoding=mode) as fd : return fd.read() else : try : with open(fichier, "r", encoding="utf-8") as fd : return fd.read() except : try : with open(fichier, "r", encoding="cp1252") as fd : return fd.read() except : return "Pas d'encodage rencontré. Désolé." def addS(fichier, chaine) : """ addS(filename, my_string) Adds a String to a file ; creation if inexistant Ajoute une chaîne à un fichier, le crée au besoin """ with open(fichier, "a") as fd : fd.write(chaine) ### Section Listes def saveL(fichier, liste) : """ saveL(my_file, my_list) Saves a chain-list in a string form, with line separators ; strings are 'quoted' Sauve une liste sous forme de chaîne avec sauts de ligne ; chaînes avec guillemets """ for i in range(len(liste)) : liste[i] =repr(liste[i]) saveS(fichier, "\n".join(liste)) def loadL(fichier) : """ my_list =loadL(filename) Loads a String file into a list with end-of-line separator Charge un fichier chaîne en liste (séparateur: fin de ligne) """ liste =loadS(fichier).split("\n") for i in range(len(liste)-1, -1, -1) : # permitting pop() item =liste[i].strip() if item =="" : liste.pop(i) ; continue liste[i] =eval(item) return liste ### Section Tables def saveT(fichier, liste, mode="") : """ saveT(my_file, my_list) Saves a CSV table, TSV (Tab Separated Values) Sauvegarde une table CSV, TSV (valeurs séparées par des tabulations) saveT(my_file, my_list, "-") Doesn't quote the strings N'entoure pas les chaînes de guillemets saveT(my_file, my_list, ",") Transforms the number dots to comma (french decimal notation) Transforme les point décimaux en virgule (export pour LibreOffice Calc) """ for i in range(len(liste)) : rangee =liste[i] for j in range(len(rangee)) : if "-" not in mode or type(rangee[j]) !=type("") : rangee[j] =repr(rangee[j]) # string transformation if "," in mode : rangee[j] =rangee[j].replace(".", ",") liste[i] ="\t".join(rangee) saveS(fichier, "\n".join(liste)) def loadT(fichier) : """ my_table =loadT(filename) Loads a TSV (Tab-Separated-Value) file into a list (rows) of lists (cells) Charge un fichier TSV Valeurs séparées par des Tab (liste de listes) """ chaine =loadS(fichier) liste =chaine.split("\n") for i in range(len(liste)-1, -1, -1) : ligne =liste[i].strip() if ligne =="" : liste.pop(i) ; continue cells =liste[i].split("\t") for j in range(len(cells)) : cells[j] =eval(cells[j]) liste[i] =cells return liste ### Section Dictionnaires def saveD(fichier, dico) : """ saveL(my_file, my_dic) Saves a dictionary into lines 'key + tab + value' Sauve un dictionnaire sous forme de lignes "clé + tab + valeur" """ liste =[] for i in dico : liste +=["%s\t%s" %(repr(i), repr(dico[i]))] chaine ="\n".join(liste) saveS(fichier, chaine) def loadD(fichier) : """ my_dic =loadD(filename) Reads a lines-file 'keys + tab + values' to fill a dictionary Charge un fichier de lignes "clé + tab + valeur" pour constituer un dictionnaire """ chaine =loadS(fichier) liste =chaine.split("\n") dico ={} for i in range(len(liste)) : morceaux =liste[i].split("\t") if morceaux ==[''] : continue if len(morceaux) ==1 : print("!!! %s : Clé sans valeur" %morceaux[0]) continue if len(morceaux) > 2 : print("!!! Trop de valeurs sur la ligne :", morceaux) continue cle, val =morceaux dico[eval(cle)] =eval(val) # return dico ### Imports with différents cell-separators def guess(chaine) : """ v2f.guess(my_string) Tries to guess the character used to separate the fields of a CSV Tente de déterminer le caractère utilisé pour séparer les champs d'un CSV """ cs =[] cs +=[[chaine.count("\t"), "\t"]] cs +=[[chaine.count(";"), ";"]] cs +=[[chaine.count(","), ","]] cs =sorted(cs, reverse =True) return cs[0][1] def impT(fichier) : """ v2f.impT(filename) Loads a CSV (Comma/Semi-comma/Tabulation-Separated File) into a list, guessing cellseps Charge un fichier CSV dans une table, en devinant les séparateurs de cellules """ chaine =loadS(fichier) cellsep =guess(chaine) liste =chaine.split("\n") for i in range(len(liste)) : cells =liste[i].split(cellsep) for j in range(len(cells)) : cell =cells[j] try : cell =eval(cell) except : pass cells[j] =cell liste[i] =cells return liste def impD(fichier) : """ v2f.impD(filename) Loads a file into a python dictionnary, guessing cellseps Charge un fichier dans un dictionnaire, en devinant les séparateurs de cellules """ chaine =loadS(fichier) cellsep =guess(chaine) liste =chaine.split("\n") dico ={} for i in range(len(liste)) : chaine =liste[i] if chaine.strip() =="" : continue cells =chaine.split(cellsep) lon =len(cells) if lon !=2 : if lon ==1 : print("Ligne à un seul elément:", chaine) if lon > 2 : print("Ligne à plusieurs valeurs:", chaine) continue cle =cells[0] ; val =cells[1] try : cle =eval(cle) except : pass try : val =eval(val) except : pass if dico.get(cle) : print("Clé ~%s~ déjà employée, l'ancienne valeur ~%s~ écrasée par ~%s~" %(cle, dico[cle], val)) dico[cle] =val return dico def what(fichier) : """ what("myfile") Attempts to determine the kind of file (binary/text, separators...) Tente de déterminer le type de fichier (binaire/texte, séparateurs...) """ octets =bytes(loadB(fichier)) lsr =octets.count(13) ; lsn =octets.count(10) coct =[0] *256 lon =len(octets) ; lon0 =min(lon, 100000) for i in range(lon0) : coct[octets[i]] +=1 if sum(coct[0:9]) > 0 and sum(coct[14:26]) > 0 : print("\n%s is a binary file; %s 'null bytes' on %s (%s%%)" %(fichier, coct[0], lon0, round(coct[0] *100 /lon0, 2))) else : print("\n«%s» is probably a text file" %fichier) if lsr ==0 and lsn ==0 : print("No line separator") else : if lsr ==lsn : print("%s line separator '\\r\\n' (bytes 13 + 10) Atari or Microsoft" %lsn) ; lsep ="\\r\\n" elif lsr ==0 : print("%s linesep '\\n' (octet 10) UNIX, Linux or McOSX" %lsn) ; lsep ="\\n" else : print("%s linesep '\\r' (octet 13) Amiga or Mc Classic" %lsr) ; lsep ="\\r" nspc =coct[32]; ntab =coct[9] ; npvi =coct[59] ; nvrg =coct[44] lignes =str(octets).split(lsep) if nspc ==0 : print("No space found") else : print("%s spaces on %s bytes (%s%%)" %(nspc, lon0, round(nspc *100 /lon0, 2))) if ntab ==0 : print("No tab") else : ltab =[] for i in lignes : ltab += [i.count("\\t")] mintab =min(ltab) ; maxtab =max(ltab) if mintab ==maxtab : print("%s tab(s) ; %s tab(s) a line" %(ntab, mintab)) else : print("%s tab(s) ; de %s à %s tab(s) a line" %(ntab, mintab, maxtab)) if npvi ==0 : print("No semicol") else : lpvi =[] for i in lignes : lpvi += [i.count(";")] minpvi =min(lpvi) ; maxpvi =max(lpvi) if minpvi ==maxpvi : print("%s semicol(s) ; %s semicol(s) a line" %(npvi, minpvi)) else : print("%s semicol(s) ; from %s to %s semicol(s) a line" %(npvi, minpvi, maxpvi)) if nvrg ==0 : print("No comma") else : lvrg =[] for i in lignes : lvrg += [i.count(",")] minvrg =min(lvrg) ; maxvrg =max(lvrg) if minvrg ==maxvrg : print("%s comma(s) ; %s comma(s) a line" %(nvrg, minvrg)) else : print("%s comma(s) ; from %s to %s comma(s) a line" %(nvrg, minvrg, maxvrg)) print("\nBytes:", end ="") for i in range(16) : print(" _%X " %i, end ="") print() for i in range(16) : print("%2X_ " %i, end ="") for j in range(16) : print("%5d" %coct[i *16 +j], end ="") print()
Versions ↑
v0.1 - 2022.08.12
Écriture des premières fonctions d'un module appelé v2f (variables to files) sous GPL v2.
- saveS(), loadS() et addS() pour sauvegarder, charger un fichier ou ajouter une variable chaîne (String) dans un fichier
- saveL() et loadL() pour sauvegarder et charger des Listes
- saveT() et loadT() pour sauvegarder et charger des Tables
- saveD() et loadD() pour sauvegarder et charger des Dictionnaires
- saveB() et loadB() pour sauvegarder et charger des objets Binaires (objets 'bytes' en python).
v0.2 - 2022.08.14
Ajout des fonctions qui tient compte du séparateur de cellules (tab, virgule, point-virgule) :
- impT() pour l'importation de tables
- impD() pour l'importation de dictionnaires
Élimination des routines pour déterminer les séquences de fin de paragraphe :
- au chargement d'une fichier texte, python3 transforme les fins de paragraphe de tous les systèmes d'exploitation en "\n" (octet 10)
- à la sauvegarde d'une chaîne, les fins de paragraphes "\n" sont transformées en \r\n (octets 13+10) pour les OS Microsoft et Atari et en \r (octet 13) pour les Mac Classic et Amiga si python3 y existe.
v0.3 - 2022.08.16
Afin de faciliter l'export, un mode (facultatif) permet d'éviter que les chaînes soient entourées de guillemets (les nombres entre guillemets sauvegardés avec "-" seront récupérés sous forme de nombre) et le point décimal de python peut être remplacé par une virgule. Ces deux modes sont compatibles.
saveT(filename, listoflists, "-") n'entoure pas les chaînes de guillemets
saveT(filename, listoflists, ",") transforme les point décimaux en virgules
saveT(filename, listoflists, "-,") ou mode=",-" fait les deux, ce qui est intéressant pour un export vers LibreOffice Calc français
v0.4 - 2022.08.18
Ajout d'un mode d'encodage des chaînes (facultatif) :
- saveS(filename, "données", mode="latin-1") permet un encodage (latin-1 ou -15, iso-8859-5 pour le cyrillique, cp1252 pour Windows), voir cette page (mais UTF-8 est peut-être préférable).
- loadS(filename, mode="") permet un encodage particulier (voir ligne précédente). Par défaut, tente de lire selon utf-8 puis cp1252.
Expérimental, une fonction de détermination de fichier :
- what("filename") renvoie des informations sur le type de fichier (binaire ou texte), les séparateurs possibles de paragraphe ("\n", "\r", "\r\n") ou de cellule (tab, point-virgule, virgule), ainsi qu'un tableau des occurrences de valeurs trouvées dans le fichier, limitées aux 100 000 premiers octets.
v0.5 - 2022.10.29
Corrections de bugs critiques pour saveD() et loadD() ; amélioration de what() . Le projet m"était complètement sorti de la tête.
v0.6 - 2022.12.01
Passage en licence GPL-v3 ; suppression de la fonction 'utilisateur' strip() pour celle native ; ajout de la fonction coding() qui détaille les encodages iso-8859, cp1252 et utf-8.
Quoi d'autre ?
- Généraliser le choix de l'encodage des chaînes (utf-8, iso-8859-1…) ?
- Permettre de forcer la fin de paragraphe ("\n", "\r" ou "\r\n") ?
- Probablement pas de possibilité de sauvegarder les tableaux avec ";" et "," : la tabulation est recommandée, le point-virgule et surtout la virgule risquent d'interférer avec la ponctuation du texte.