Recettes, trucs et astuces pour Python3
VOICI quelques exemples disparates de bidouilles (non orientées objet) pour illustrer la page de présentation de python ou concernant quelques modules. Certaines datent de python2 mais ont été réécrites pour python3.
Affichage
→ Alignement à droite de chaînes et de nombres
→ Ajout d’un pluriel selon le nombre
Gros fichiers de texte
→ Découper un texte en plusieurs morceaux
→ Chercher remplacer sur un très gros fihier
Listes
→ Utilisation d’une liste de listes comme tableau à plusieurs dimensions
→ Simulation d’un tableau à plusieurs dimensions avec une liste plate
→ Filtrer une liste
→ Compter les éléments communs d’une liste
→ Supprimer les doublons d’une liste
→ Produire un range() de «réels»
→ Ventiler (alphabétiquement) l’affichage des éléments d’une liste
Fonctions
→ Modification d’une fonction interne à python
→ (Re)créer les fonctions cmp() et sign()
→ Créer une fonction «à peu près égal»
Fichiers et répertoires
→ Fichier "Random Access"
→ Travail sur un répertoire
Simplification des structures
→ Chasse aux for
→ Chasse aux if
→ Raccourcir les conditions
Optimisations
→ Optimisation de la boucle for
→ Optimisation des concaténations
Autre page
→ Description et génération des images .PNM et .PAM
↑ Alignement à droite de nombres et de chaînes
print(), f"", collections définies en compréhension
Pour un alignement à droite d’entiers positifs, le plus grand de ces nombres aura la largeur la plus grande :
nombres =(12, 187, 4562, 1) mx =len(str(max(nombres))) # largeur du plus grand nombre à afficher dans le tuple for i in nombres : print(f"{i:{mx}d}") # sortie formatée de mx caractères pour chaque nombre
donne
12 187 4562 1
Pour des entiers positifs ou négatifs, il faut également tenir compte du nombre négatif le plus «large» :
nombres =[12, 187, -4562, 1] mx =len(str(max(nombres))) # longueur en caractères du plus grand nombre à afficher mn =len(str(min(nombres))) # longueur en caractères du plus petit nombre à afficher pl =max(mx, mn) # détermination du nombre le plus large (signe négatif compris) for i in nombres : print(f"{i:{pl}d}")
Pour aligner des nombres, «réels» ou non :
nombres = (25, 22/7, -9999) mx = max(len(f"{i:.2f}") for i in nombres) # valeur maximale dans un tuple de largeurs des nombres for i in nombres : print(f"{i:{mx}.2f}")
25.00 3.14 -9999.00
Pour un alignement à droite de chaînes :
chaines = ("un", "deux", "trois", "quatre", "cinq") mx = max(len(i) for i in chaines) for i in chaines : print(f"{i:>{mx}}")
donne :
un deux trois quatre cinq
Pour une liste ou tuple d’éléments divers :
divers = ("un", 25, 22/7, 1.414, -999999) rempl =[] for i in divers : if type(i) ==type(.0) : # en cas de nombre avec virgule, lon =len(str(round(i))) # ne tenir compte que de la partie entière else : lon =len(str(i)) # sinon prendre l’expression entière (chaîne ou entier) rempl +=[lon] # rentrer la longueur mx =max(rempl) # longueur maximale de l’expression entière for i in range(len(rempl)) : # remplissage à gauche de l’expression rempl[i] = mx -rempl[i] # calcul du nombre d’espaces par occurrence for i in range(len(divers)) : print(" " *rempl[i] +str(divers[i]))
ce qui donne :
25 3.142857142857143 1.414 -999999
↑ Ajout d’un pluriel selon le nombre
Pour l’inclusion de variables dans une chaîne, voir ici
Lorsqu’il n’est pas possible de savoir à priori si un mot devra être affiché au pluriel (le résultat d’une recherche), on peut utiliser un pluriel optionnel, transcrit sous la forme mot(s) ou mot·s. Il est très facile d’associer un s à mot (ici : fichier) uniquement si le nombre est supérieur à 1 :
s ="s" # le caractère 's' sera passé par la variable s for n in range(0, 4) : print(f"Il y a {n} fichier{s*(n > 1)} dans le répertoire")
Explication : (n > 1) vaut selon les cas True ou False, soit 1 ou 0 en tant qu’opérateur (True + True =2). "s"*True vaudra donc "s" et "s"*False vaudra "".
Cela se complique un peu pour considérer aussi les entiers négatifs :
s ="s" for n in range(-3, 4) : print(f"Solde: {n} euro{s*(not(-2 < n < 2))}")
…l’expression logique équivalant à : «faux quand n est strictement entre -2 et 2»
↑ Couper un très gros fichier de texte
Pour découper un gros fichier de texte en morceaux, plutôt qu’utiliser la souris pour définir une grande plage de plusieurs Mo, il est possible d’insérer une chaîne courte improbable, tel que ******* ou §§§§§§§ et demander à un script de sauvegarder les différents morceaux du texte en tant que fichiers séparé. C’est ce que fait ce script :
#! /usr/bin/python3 fichier =input("Nom du fichier à couper: ") texte =open(fichier).read() morceaux =texte.split("*******") for i in range(len(morceaux)) : svg =f"{fichier}-{i:0{len(str(i))}d}" open(svg, "w").write(morceaux[i]) print(svg)
{i:0{len(str(i))}d} permet d’ajouter les éventuels 0 nécessaires à une numérotation sur plusieurs chiffres : 00, 01, 02... s’il y a entre 9 et 99 morceaux.
↑ Chercher / remplacer sur un très gros fichier de texte
Avec un éditeur de texte comme gedit ou pluma, une recherche-remplacement sur un simple texte de plusieurs millions d’octets peut prendre du temps et consommer beaucoup de ressources. Ce script économise les deux :
#! /usr/bin/python3 fichier =input("Texte à modifier: ") print(f"Le résultat se trouvera dans le fichier {fichier}+\n") print("Attention: distinction majuscules / minuscules !") chercher =input(" Chaîne à rechercher: ") remplacer =input("Chaîne de remplacement: ") texte =open(fichier).read() texte =texte.replace(chercher, remplacer) open(fichier +"+", "w").write(texte)
à sauvegarder sous un nom de type remplacer.py.
Il semblerait qu’il n’y a pas de différence significative pour un fichier de 30Mo avec le script suivant, qui découpe le texte selon la chaîne à remplacer, suivie d’un collage des morceaux à l’aide de la chaîne de remplacement (c’est peut-être ce que fait la fonction interne replace() de python) :
#! /usr/bin/python3 fichier =input("Texte à modifier: ") print(f"Le résultat se trouvera dans le fichier {fichier}+\n") print("Attention: distinction majuscules / minuscules !") chercher =input(" Chaîne à rechercher: ") remplacer =input("Chaîne de remplacement: ") texte =open(fichier).read() liste =texte.split(chercher) # on peut remplacer ces deux lignes texte =remplacer.join(liste) # par la ligne suivante : # texte =remplacer.join(texte.split(chercher)) open(fichier +"+", "w").write(texte) # sauvegarde avec '+' en fin de nom de fichier
↑ Utilisation d’une liste comme tableau à plusieurs dimensions
Les listes ne sont pas des tableaux comme il en existe en C, mais il est possible de les initialiser comme tels. t=[[0]*3]*4 donne
[[0,0,0], [0,0,0], [0,0,0], [0,0,0]]
soit une liste de quatre sous-listes de trois éléments. t[2][1]=7 devrait assigner la valeur 7 au 2e élément de la 3e liste, mais print(t) donne
[[0,7,0], [0,7,0], [0,7,0], [0,7,0]]
ce qui montre que python se souvient qu’il a cloné la sous-liste [0,0,0], devenue [0,7,0], problème similaire à l’alias lors d’une affectation de liste à une autre variable :
>>> a =[1,2,3] >>> b =a >>> a[1] =5 >>> b [1,5,3] >>> b[1] =7 >>> a [1,8,7]
Pour contruire d’une liste de liste dont les éléments sont intitialisés à 0 :
liste =[] for x in range(3) : li =[] for y in range(2) : li +=[0] liste +=[li] print(liste)
La solution la plus pythonesque est certainement l’équivalent de «liste en comprhension»
[[0 for y in range(2)] for x in range(3)]
Exemple en trois dimensions :
>>> tabl = [[[0 for x in range(2)] for y in range(3)] for z in range(4)] >>> tabl[1][2][0] =7 >>> tabl
donne
[[[0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [7, 0]], [[0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [0, 0]]]
Distraction originelle ou effet pervers d’un postulat de base, cela n’a pas l’air d’inquiéter les développeur et de corriger cette bizarrerie lors du passage à python3. Cela peut provoquer des erreurs difficilement détectables, par exemple lorsqu’on appelle une fonction avec une liste en paramètre :
def moycarr(li) : for i in range(len(li)) : li[i] =li[i] **2 return sum(li) /len(li) data =[1, 2, 3, 4, 5] print(moycarr(data), data)
retournera 11.0 et [1, 4, 9, 16, 25]. Même initialisée dans une fonction, la liste li reste un alias de data, sa modification dans la fonction affectant les valeurs des éléments de data.
Pour éviter cet effet pervers, il suffit de réaffecter les valeurs de la liste de réception avec la fonction list(), coupant de cette façon le lien d’identité entre les deux noms de variable :
def moycarr(li) : li =list(li) for i in range(len(li)) : li[i] =li[i] **2 return sum(li) /len(li)
Le concept de liste est finalement assez différent de celui du tableau. Il est plus souple parce qu’une liste peut contenir toute sorte d’éléments, et par exemple elle-même :
>>> li=["a", "b"] >>> li+=[li] >>> li ['a', 'b', […]] >>> li[2] ['a', 'b', […]] >>> li[2][2] ['a', 'b', […]] >>> li[2][2][2][2][2][2][2][2][2][2][2][2][2] ['a', 'b', […]]
Il reste à voir à quoi cela peut servir… Pour les tableaux et matrices, utilisez plutôt le module externe numpy, qui traite des tableaux, de la trigonométrie, des fonctions pseudo-aléatoires…
↑ Simulation d’un tableau à plusieurs dimensions avec une liste plate
Admettons qu’il faille rechercher une valeur dans une liste représentant un tableau à plusieurs dimensions : il faudra plusieurs boucles imbriquées pour tester chaque position, ce qui n’est pas idéal. Afin de pouvoir utiliser la méthode maliste.index(val), il faut que la liste soit unidimensionnelle (plate). Il est toujours possible de simuler une liste en calculant soi-même la rangée et la colonne à partir de l’index de la valeur dans la liste. Pour un échiquier de 9 *9 :
maliste =[0] *81 ; liste[64] =3 rangee, colonne =divmod(maliste.index(3), 9) print(rangee, colonne)
Il est bien sûr possible d’écrire une fonction pour ce faire :
maliste =[0] *81 ; maliste[64] =3 def cherch(liste, val) : return divmod(liste.index(val), 9) print(cherch(maliste, 3))
Cela ne fonctionne que si la valeur existe bien dans une liste 9 *9. Pour traiter des listes aux dimensions variées, il faut les passer en paramètres de fonction et le calcul devient plus compliqué avec le nombre de dimensions.
↑ Filtrer une liste déjà constituée
Pour filtrer une liste déjà existante, on pourrait copier chaque élément sous condition dans une autre. Pour éviter la création d’une variable supplémentaire, on peut la parcourir à l’envers et enlever tout ce qui doit l’être.
#! /usr/bin/python3 liste=range(100) # liste des entiers 0 à 99 for i in liste[::-1] : # parcours inverse if liste[i] %2 ==0 : # ce nombre est-il pair? liste.pop(i) # supprimé print(liste)
Malheureusement, le temps pris par l’élimination d’un élément est plus long que l’ajout à une liste (cas d’une seconde variable) et augmente encore avec la taille de la liste :
#! /usr/bin/python3 import time for j in range(6,15) : liste=range(10000*j) liste2=[] t=time.clock() for i in liste : if liste[i]%2==0 : liste2+=[liste[i]] t1=time.clock()-t liste=range(10000*j) t=time.clock() for i in liste[::-1] : if liste[i]%2==0 : liste.pop(i) t2=time.clock()-t print("%d\n %5.3f - %5.3f (%5.3f X plus long)" %(10000*j, t1, t2, t2/t1)) input("Une touche pour terminer")
Il est simple de constater que plus la liste est longue, plus la méthode utilisant pop() prend plus de temps que celle où on recopie les éléments filtrés dans une seconde liste.
↑ Compter les éléments communs d’une liste ou d’un tuple
Il est possible de compter le nombre de fois que revient chaque élément d’une liste (ou d’un tuple) en passant par un ensemble, qui ne prend pas les doublons en considération, et en comptant chacun de ses éléments dans la liste de départ.
>>> li =["b","a","c","b","c","b","c"] >>> ens =set(li) >>> for i in ens : … print(f"{li.count(i)} {i}") … 3 b 3 c 1 a
Si la liste contient autre chose que des nombres et des chaînes, il n’est pas possible d’en générer un ensemble ou de passer par un dictionnaire, il faut alors utiliser deux listes qui évolueront ensemble, l’une qui accumulera les éléments uniques, et l’autre le nombre d’occurrence de chacun de ces éléments. Ces deux nouvelles listes seront assemblées avec zip()
liste =["a", 1, [3], "a", 1, "a", "d", {2:5, "a":9}] elem =[] ; nbr =[] for i in liste : if i in elem : continue elem +=[i] ; nbr +=[liste.count(i)] print(list(zip(elem, nbr)))
Le tri n’est possible qu’en cas de liste homogène de chaînes ou de nombres (sorted() transforme les «objets zip» en liste)
- sorted(zip(elem, nbr)) permet le tri selon l’ordre des éléments
- sorted(zip(nbr, elem), reverse=True) par nombre d’occurrence, en commençant par le plus haut.
↑ Supprimer les doublons d’une liste
La façon la plus simple de supprimer les doublons d’une liste est de la convertir en un ensemble set(), qui élimine les doubles :
>>> liste=[1,1,3,4,5,5,5] >>> ensemble=set(liste) >>> ensemble set(1,3,4,5) >>> liste=list(ensemble) >>> liste >>> [1,3,4,5]
soit en une fois :
>>> print(list(set([1,1,3,4,5,5,5]))) [1,3,4,5]
Le mieux serait, pour ne pas en passer par là, d’utiliser directement les ensembles. Dans une boucle, par exemple, il suffit de prévoir la ligne ensemble.add(element), où il ne faut pas tester if element not in liste : liste+=[element] :
>>> a=set() >>> a.add(5) >>> a.add(9) >>> a.add(5) >>> a set([9,5])
Mais il se peut que pour une raison ou une autre, vous disposiez d’une liste : les ensembles ne pouvant contenir que des nombres et des chaînes, pas des collections. La première manière est de créer une seconde liste, qui ne recevra une valeur qu’une fois :
liste1=[1,4,6,[7,3],3,5,4,[7,3],6,2] liste2=[] for i in liste1 : if i not in liste2 : liste2+=[i] print(liste2)
…manière qui garde l’ordre des premiers éléments de la liste de départ.
Une autre manière, qui permet une comparaison plus fine des éléments à éliminer. Si l’on décide qu’un élément de chaîne vaut pour une chaîne plus complète, il faut procéder de la sorte :
#! /usr/bin/python3 liste =["ah", "éhéhéh", "ahah", "éhéh", "ahahah", "éh"] liste.sort() # tri de la liste print(liste) for i in range(len(liste)-1,0,-1) : # du dernier élément au second if liste[i-1] in liste[i] : # regarde si le précédent est inclu dans le suivant liste.pop(i) # supprime l'élément dans ce cas print(liste)
↑ Produire un range() de «réels»
Même si range() ne produit plus de liste en python3, il s’agit d’un objet itérable comme une liste. Pour obtenir une liste de nombres réels basée le même principe (progression arithmétique limitée) :
#! /usr/bin/python3 def frange(deb, fin, pas) : # trois variables nécessaires if pas ==0 : return None # un pas nul rendrait la liste infinie liste =[] ; acc =deb if pas > 0 : while acc < fin : # tant que le nombre intermédiaire reste inférieur à la limite liste +=[acc] # on continue à engranger acc +=pas # et on passe à la valeur suivante else : while acc > fin : # tant que le nombre intermédiaire reste supérieure à la limite liste +=[acc] acc +=pas return liste print(frange(22 /7, 60, 10 **.5)) print(frange(22 /7, -60, -10 **.5))
Il y a certainement moyen de faire plus compact.
↑ Ventiler (alphabétiquement) l’affichage des éléments d’une liste
Lors de l’affichage d’une liste de chaînes triée, il peut être intéressant de marquer l’arrêt à chaque changement d’initiale. Comme l’exemple initial de la chaîne (préalablement triée) mélange majuscules et miniuscule, il faut comparer init0 à l’initiale majuscule (ou minuscule) de chaque chaîne.
init0 ="" liste =["Ah","aïe","bain","Bon","Brute","Truand","utile","UV"] for i in liste : init =i[0].upper() if init !=init0 : print (f"\n= {init} =") init0 =init print(i)
Cela se complique en cas de tri «naturel» (voir module ICU), c’est-à-dire ne discriminant pas les lettres et leurs accentuées. Sans trop réfléchir ni chercher un module qui propose cela, écrivons une fonction qui enlève les diacritiques :
def dediacr(ch) : """ Enlève les diacritiques usuels des alphabets latins Certaines langues séparent une lettre simple et son accentuée (N != Ñ en espagnol) Ajoutez les caractères comme bon vous semble """ if ch in "ÀÁÂÄÃÅÆ" : ch ="A" if ch =="Ç" : ch ="C" if ch in "EÈÊË" : ch ="E" if ch =="Ð" : ch ="D" # si listés ensemble dans votre dictionnaire if ch =="Ĝ" : ch ="G" # si listés ensemble dans votre dictionnaire if ch in "ÍÌÎÏIJ" : ch ="I" # IJ pourrait être en Y if ch in "Ł" : ch ="L" # si listés ensemble dans votre dictionnaire if ch =="Ñ" : ch ="N" # si listés ensemble dans votre dictionnaire if ch in "ÓÒÔÖÕØŒ" : ch ="O" if ch in "ÚÙÛÜ" : ch ="U" if ch in "ŸÝ" : ch ="Y" return ch init0 ="" liste =["Ah","Bon","Brute","ça","Truand","utile","UV"] for i in liste : init =dediacr(i[0].upper()) if init !=init0 : print (f"\n= {init} =") # la nouvelle initiale est affichée init0 =init # et devient l'initiale «en cours» print(i)
↑ Modification d’une fonction interne à python
Il est possible de «surcharger» (redéfinir) des fonctions internes à Python. Celle-ci précise la longueur en bytes des chaînes :
14à copier-coller dans une console python3def len(x) : if type(x) ==type("") : # si chaîne x= bytes(x, "utf-8") lon =0 for i in x : # impossible d’utiliser len() (récursivité) lon +=1 return lon len("10€ à payer")
Mais ceci n’est qu’un exemple, il vaut mieux ne pas toucher à la fonction len() qui concerne une multitude d’objets, et nommer autrement la fonction à créer, par exemple blen(), dédiée aux chaînes et de ce fait plus simple à programmer :
14à copier-coller dans une console python3def blen(ch) : return len(bytes(ch, "utf-8")) blen("10€ à payer")
Cette chaîne de 11 caractères équivaut à 14 octets utf-8 parce que "à" et "€" en utilisent respectivement 2 et 3 octets.
En python, seuls les mots réservés ne peuvent pas servir de variable, mais toute fonction peut être redéfinie ou être utilisée comme variable :
>>> print =123 >>> print("Ah! mais…") Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'int' object is not callable
…parce que print() n’est alors plus une fonction, mais une variable.
↑ Chasse aux for
Il est possible d’encapsuler une ou plusieurs boucles for dans une expression en compréhension.
li =[] for x in range(2) : for y in range(3) : li.append([x,y]) print(li)
est avantageusement remplacé par une liste en compréhension :
li =[[x,y] for x in range(2) for y in range(3)] print(li)
↑ Chasse aux if emboîtés
Il est parfois utile d’éviter de multiplier les structures conditionnelles. Ce script parcourt le répertoire courant, accepte les noms de plus de 4 caractères des fichiers qui s’y trouvent et les affiche s’ils sont terminés par .txt ou .TXT, tout en indiquant les noms de fichiers trop courts.
import os for i in os.listdir() : if len(i) > 4 : if i[-4:] ==".txt" : print(i) else : print(f" {i} a un nom de fichier trop court")
Il est possible de rendre de réduire la cascade d’indentations en utilisant continue qui permet d’éviter le reste de la boucle :
import os for i in os.listdir() : if len(i) < 5 : print(f" {i} a un nom de fichier trop court") ; continue if i[-4:] ==".txt" : print(i)
- continue revient en début de boucle, i passe à l’élément suivant
- le else éloigné du premier if est supprimé
- si le nom de fichier est trop court, le second if n’est pas évalué, le script revient en début de boucle
- le second if sort du premier, on élimine une indentation
↑ Raccourcir les conditions
Dans l’exemple précédents, on ne cherchait que les fichiers ayant l’extention .txt, mais qui pourrait tout aussi bien être .TXT. De plus, si l’on cherche plusieurs types de fichiers, il faudrait un enchaînement de conditions du type :
if i[-4:] ==".txt" or i[-4:] ==".TXT" or i[-4:] ==".odt" or i[-4:] ==".ODT" or i[-4:] ==".pdf" or i[-4:] ==".PDF" :
Il est possible de simplifier cette expression :
- en basant les tests sur la chaîne i[-4:] passée par lower() pour ne plus devoir s’occuper des extensions en majuscules ;
- en testant la présence de ladite chaîne dans un tuple :
if i[-4].lower() in (".txt", ".odt", ".pdf") :
...ce qui prend également en compte les fichiers terminant en .Odf, .tXt, etc.
↑ Créer la fonction cmp()
Pour une raison peu compréhensible, python3 a abandonné la fonction cmp(), mais elle est facile à recréer. Sachant que dans un calcul, True vaut 1 et False, 0 :
def cmp(x, y) : return (x > y) - (x > y)
Cette fonction recréée ne peut pas fonctionner pour des dictionnaires, qui ne sont plus comparables en python3. Pour les listes, il ne faut pas qu’elles mélangent les types de données (int et float sont comparables).
Il se dit que c’est parce que les comparaisons de valeurs de types différents n’ont pas de sens, mais cela peut en avoir entre types entiers et «réels» :
>>> 3 < 10 **.5 True
Et cela avait un sens (mais tout à fait arbitraire) en python2 :
>>> 0 < {} < [] < "" < () True
Créer la fonction sign()
De la même manière, il est possible de créer la fonction sign() qui retourne 1 ou -1 selon que le nombre est positif ou négatif (et 0 s’il est nul), qui existe dans certains langages :
def sign(var) : return (0 < var) - (var < 0)
Note : le module math possède la fonction copysign(), qui applique le signe de la deuxième valeur à la première :
>>> import math >>> a =-3 ; b =5 ; math.copysign(a,b) ; a 3.0 -3
Nous voyons avec l’exemple que
- la valeur de retour est de type «réel»
- la variable porteuse de la valeur d’origine n’est pas modifiée
Prévoir de recréer la fonction abs()?
On peut déjà penser à une recette de secours si abs() est supprimé dans python4 (sous prétexte par exemple que cela n’a aucun sens pour les collections ou que c’est trop trivial). Plus sérieusement, ceci est un exemple d’utilisation de raise pour signaler une erreur :
à copier-coller dans une console python3def abs(x) : if type(x) in (type(0), type(0.)) : if x < 0 : x =-x return x elif type(x) ==type(0j) : return (x.real **2 +x.imag **2) **.5 raise TypeError(f"No number nor complex, but {type(x)}, not allowed") # cas non prévu
Essayez ensuite : abs(-43), abs(3 -4j), abs("Bonjour, Monde"), etc...
↑ Créer une fonction «à peu près égal»
S’il est facile de recréer la fonction cmp() disparue en python3, il est également possible de créer une plus souple, par exemple où les deux éléments sont considérés comme égaux s’ils ne sont pas trop différents («plus ou moins, poum en abrégé»), la limite étant ici fixée à 5% de la plus petite valeur.
à copier-coller dans une console python3def poum(x, y) : # définition de la fonction poum() if abs(x -y) / min(x, y) < .05 : return 0 if x < y : return -1 if x > y : return 1 print(poum(7, 8)) print(poum(97, 100))
↑ Fichier «Random Access» Rev. 2023.03.29
Fichiers pour lesquels on utilise un pointeur pour accéder à un endroit particulier du fichier. Attention : comme ce fichier contient des octets vide (=0), il ne sera peut-être pas éditable avec un éditeur de fichier-texte.
#! /usr/bin/python3 """Exemple d'utilisation de fichier random access - 2007-01-18 - www.jchr.be - GPL-2""" with open("fichier.txt","wb") as fd : fd.seek(10) # se positionne le pointeur sur le 11ème octet à partir du début (seek(10) =seek(10, 0)) fd.write(b"Spam") # y écrit le texte (attention : la position du pointeur devient 14) fd.seek(-10, 1) # remonte de 10 octets ("-10") à partir de la position actuelle ("1") fd.write(b"Eggs") with open("fichier.txt", "rb") as fd : fd.seek(0, 2) # position le curseur après le dernier octet du fichier (, 2) fin =fd.tell() # eof contient le décalage de la fin du fichier = longueur du fichier en caractères fd.seek(0) # début du fichier: décalage 0 par rapport au début du fichier seek(0) =seek(0, 0) for i in range(fin) : print(fd.read(1)) # imprime la lecture d'un octet, et déplace le pointeur
Remarque : en python3, l’ouverture avec le mode "rb" exprime les octets comme des objets bytes : les octets nuls par exemple sont affichés b'\x00b' ; l’ouverture avec le mode "r" exprime les octets sous forme de chaîne, mais seulement jusqu’à 127 (codage ASCII).
↑ Travail sur un répertoire
Sous-module os.path
Le travail sur les répertoires et fichiers est possible à partir du module interne os.
Pour déterminer le chemin complet du script appelé, il faut en plus utiliser le module sys :
à copier-coller dans une console python3import sys, os script= sys.argv[0] # le premier element de argv est le nom du script, les suivants : parametres chemin= os.path.abspath(script) # chemin + nom du script repertoire= os.path.dirname(chemin) # retourne la partie 'chemin' print(repertoire)
Le script suivant détermine le répertoire par défaut (le plus souvent /home/votre_ID) et place tout ce qui s’y trouve dans la liste dossier. Une boucle permet ensuite de passer en revue tout ce qu’il y a dans la liste et reconstitue le chemin entier de l’objet (os.sep permet la compatibilité avec les différents systèmes : '/' pour Unix, '\' pour Microsoft, ':' pour Mac). Pour autant que le test détermine avec os.path.isfile() que l’objet est un fichier, celui-ci est affiché.
#! /usr/bin/python3 import os chemin =os.getcwd() # ! il ne s'agit pas nécessairement du répertoire du script dossier =os.listdir(chemin) for nom in dossier : # pour chaque chose rencontrée dans un répertoire element =chemin +os.sep +nom # le chemin complet est composé if os.path.isfile(element) : # teste si element est un fichier print(element)
De la même manière, os.path.isdir() permet de vérifier qu’on est en présence d’un répertoire. Il est exploité ci-dessous dans une fonction récursive : chaque répertoire recense et traite ses sous-répertoires.
#! /usr/bin/python3 import sys, os # importe les modules system et operating system script =os.path.abspath(sys.argv[0]) # nom complet : chemin/script chemin =os.path.dirname(script) # chemin complet du répertoire du script cpt =0 def entrer(dans, n) : # définition de la fonction global cpt repertoires =os.listdir(dans) for nom in repertoires : occurrence =dans +os.sep +nom # addition du chemin complet if os.path.isdir(occurrence) : # teste s'il s'agit d'un répertoire print(f"{' ' *n}{occurrence}") # n id loop-depth, converted into space-string entrer(occurrence, n +1) # recherche les éléments du répertoire (récursion) else : print(f"{' ' *n}{occurrence}") # pas un répertoire: un fichier cpt +=1 entrer(chemin, 0) input(f"{cpt} fichiers. Une touche pour quitter…")
↑ Sélectionner des fichiers d’un répertoire sans boîte
Voici une méthode de sélection de fichiers. Un premier tri se fait selon une ou plusieurs extensions, ensuite un affichage par numéro d’ordre permet une sélection individuelle (ou tous si * est saisi. La boucle qui suit est limitée ici à l’affichage des noms de fichier).
Pour utiliser une boîte avec TKinter, voir cette page.
#! /usr/bin/python3 import os liste =os.listdir(".") # list of all file in current directory lon =len(liste) for i in range(lon -1, -1, -1) : # filtering by extension if liste[i].rsplit(".")[-1].upper() not in ("ODT", "TXT") : # permitted extensions liste.pop(i) liste.sort() # sorting the list lon =len(liste) for i in range(lon): # in a row ; more dfficult by column print(f"{i:3d} {liste[i]:&14}", end="") if (i +1) % 4 ==0: print() print() nr =input("\n Number(s), space-separated if several (* for all): ") li =nr.split() ; dic ={} # choix des numéros if "*" not in li : li =[int(x) for x in li] if nr !="*" : dic ={x:liste[x] for x in li} else : dic =dict(zip(range(len(liste)), liste)) for num in dic : # loop for all choosen files odt, ODT, txt, TXT print(f"{num:3} {dic[num]}")
↑ Optimisation de la boucle for
Quelle est la boucle for à insérer dans l’autre pour optimiser ? Il semblerait que la boucle la moins parcourue doit être insérée dans la plus parcourue.
import time t =time.time() for i in range(100) : for j in range(1000000) : pass print(time.time() -t) t =time.time() for i in range(1000000) : for j in range(100) : pass print(time.time() -t)
Ce qui donne respectivement ±4.3s et ±3.3s sur un dualcore à 2300Mhz.
↑ Optimisation des concaténations
La concaténation est toujours assez coûteuse en temps, ce qui devient parfois épineux en cas de nombreuses «additions». Il est très avantageux de stocker les bouts de chaînes dans une liste et puis de les joindre avec "".join(liste).
import time t =time.time() ch ="" for i in range(100000) : ch +="bouh! " print(time.time() -t) t =time.time() li =[] for i in range(100000) : li +=["bouh!"] ch =" ".join(li) print(time.time() -t)
Ce qui donne respectivement ±3.9s et ±0.2s sur un dualcore à 2300Mhz. join() évite également l’espace en fin de chaîne.
Attention : cette façon de faire est désavantageuse pour l’addition de nombres :
import time t =time.time() ch =0 for i in range(10000000) : ch +=3 print(time.time() -t) t =time.time() li =[] for i in range(10000000) : li +=[3] ch =sum(li) print(time.time() -t)
…qui donne respectivement ±1.1s et ±1.6s sur un dualcore à 2300Mhz.