Recettes pour Python 2.x, traduit en python3
VOICI quelques exemples disparates de bidouilles (non orientées objet) écrites en python 2.x pour illustrer la page de présentation de python ou concernant quelques modules.
Affichage
→ Alignement à droite de chaînes et de nombres
Listes
→ Utilisation d'une liste comme tableau à plusieurs dimensions
→ Filtrer une liste
→ Compter les éléments communs d'une liste
→ Supprimer les doublons 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
↑ Alignement à droite de chaîne et de nombre
Pour un alignement à droite de mots de longueurs différentes, il faut calculer une marge variable :
dico = { "un":"één", "deux":"twee", "trois":"drie", "quatre":"vier", "cinq":"vijf" } # passage en revue de toutes les clés du dico [liste en compréhension] # et détermination du plus grand nombre de lettres : mx = max([len(i) for i in dico.keys()]) for i in dico : sp =mx -len(i) # nombre d'espaces à prévoir avant le mot de gauche print(" "*sp +i, ":", dico[i]) # affichage de la marge, de la clé et de la valeur
donne :
un = één deux = twee trois = drie quatre = vier cinq = vijf
On pourrait procéder de la même manière pour les nombres, mais la solution retenue est un formatage avec %d en deux étapes :
nombres =[12, 187, 4562, 1] mx =len(str(max(nombres))) # longueur en caractères du plus grand nombre à afficher # préparation de la chaîne de formatage, qui contient le nombre de caractères à réserver frmt ="%%%dd" %mx # (= "%4d") "%%" pour "%", %d pour '4' (valeur de mx) et 'd' for i in nombres : print(frmt %i) # sortie formatée de quatre caractères pour chaque nombre
donne :
12 187 4562 1
↑ 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[2]=7 >>> b [1,2,7] >>> b[2]=9 >>> a [1,2,9]
On peut néanmoins construire une liste de quatre sous-listes de trois éléments initialisés à 0 où chaque élément est individualisé :
#! /usr/bin/python3 t =[] for i in range(4) : t +=[[]] # ajoute quatre fois une sous-liste vide: [[],[],[],[]] for j in range(3) : t[i] +=[0] # ajoute trois éléments '0' à chacune des quatre sous-listes vides print(t) # rien que des 0 t[2][1] =9 # 9 est assigné à la 2e valeur de la 3e sous-liste print(t) # une seule modification n'a eu lieu
Une autre possiblilité est de passer par les tuples :
#! /usr/bin/python3 t=[(0,)*3]*4 # (virgule!) donne une liste de quatre tuples: [(0,0,0),(0,0,0),(0,0,0),(0,0,0)] for i in range(4) : t[i]=list(t[i]) # chaque tuple est transformé en liste t[2][1]=9 # 9 est assigné au deuxième élément de la troisième sous-liste print(t) # …et rien que là. On a gagné deux lignes de code
Mais pour un tableau à 3, 4, n… dimensions? Il faut construire un tuple de tuples de tuples… en tenant compte du fait que ((0,)*3)*2 donne (0,0,0,0,0,0) et non ((0,0,0),(0,0,0)) : il faut passer par des listes. Pour trois dimensions :
#! /usr/bin/python3 t=(0,)*3 t=tuple([t]*4) # [(0,0,0),(0,0,0),…] transformé en ((0,0,0),(0,0,0),…) t=[t]*5 for i in range(len(t)) : t[i]=list(t[i]) # [((0,0,0),(0,0,0)… transformé en [[(0,0,0),(0,0,0)… for j in range(len(t[i])) : t[i][j]=list(t[i][j]) # [[(0,0,0),(0,0,0)… transformé en [[[0,0,0],[0,0,0]… print(t,"\n") t[0][2][1]=9 print(t)
Le premier exemple est alors plus simple :
! /usr/bin/python3 t=[] for i in range(5) : t+=[[]] # ajoute 5 fois une sous-liste vide: [[],[],[],[],[]] for j in range(4) : t[i]+=[[]] # ajoute 4 fois une sous-sous-listes vides [] à chacune des 5 sous-listes vides for k in range(3) : t[i][j]+=[0] # ajoute 3 éléments 0 à chacune des sous-sous-listes vides t[0][2][1]=9 # 9 est assigné à un endroit précis print(t) # …et rien que là
Si on veut initialiser un tableau dont on ne sait pas a priori combien il aura de "dimensions", on peut utiliser la récursivité. Le principe est de passer un tuple qui reprend le nombre d'éléments de chaque dimension (on passe et on récupère un tuple en paramètre en le préfixant d'un astérisque).
#! /usr/bin/python3 # initialise une liste de x listes de y listes… de n éléments def tableau(*dim) : tab=[] if len(dim) : for i in range(dim[0]) : tab+=[0] if len(dim) >1 : # si le tableau a plus d'une dimension… tab[i] =tableau(*dim[1:]) # … on traite la dimension suivante return tab tup=input("x,y… ,n: ") # "n," pour une seule dimension print(tableau(*tup))
Mais la solution la plus pythonesque est certainement [[0 for x in range(10)] for y in range(10)] . Exemple en trois dimensions :
>>> m = [[[0 for x in range(2)] for y in range(3)] for z in range(4)] >>> m[1][2][0] ="*" m
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…
↑ 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
Il est possible de compter le nombre de fois que revient chaque éléments d'une liste 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 les listes, plus souples. Le schéma est de trier la liste puis de voir si chaque élément est le même que le précédent. On génère une nouvelle liste avec les doublets [n,element]. Si l'élément est nouveau, on crée le doublet [1, element], sinon on ajoute 1 au premier terme du dernier doublet créé coll[-1][0]. La variable prec contient la valeur précédente, qui ne peut se trouver dans la liste initiale. Attention! Ce n'est valable qu'en python2 : il n'est plus possible de comparer chaînes, liste, tuples et dictionnaires en python3.
#! /usr/bin/python2 li =["a","b",["a","b"],"a","a","b"] li =sorted(li) coll =[] prec =2 **100 -43 # valeur hautement improbable for i in li : if i !=ant : coll +=[[1,i]] prec =i else : coll[-1][0] +=1 coll =sorted(coll) print(coll)
Il est possible de toujours considérer le premier élément comme étant nouveau, et de ne commencer la boucle qu'au second élément, ce qui occasionne un petit détour.
↑ 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)
↑ Modification d'une fonction interne à python
Il est possible de 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")
Note : 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, comme blen(), beaucoup plus simple :
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 valent respectivement 2 et 3.
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
↑ (Re)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)
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 semblables (par exemple entiers et «réels») :
Il suffirait pourtant de préciser :
à copier-coller dans une console python3def cmp(x, y) : if type(x) == type(0) : x =float(x) if type(y) == type(0) : y =float(y) return (x > y) - (x < y)
Cela ne peut pas fonctionner pour des dictionnaires, qui ne sont plus comparables en python3.
En python2, cela avait pourtant du sens :
>>> 0 < {} < [] < "" < () True
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)
On peut déjà penser à une recette de secours si abs() est supprimé dans python4 (par exemple parce que cela n'a aucun sens pour les collections) :
à copier-coller dans une console python3def abs(x) : if x < 0 : return -x else : return x
↑ 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 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 simple.
#! /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'\x00' ; 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 def entrer(dans, n) : # définition de la fonction 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 est la profondeur de récursion, convertie en espaces entrer(occurrence, n +1) # recherche les éléments du répertoire (récursion) else : print(f"{' ' *n}{occurrence}") # pas un répertoire: un fichier entrer(chemin, 0) input("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 ("ABC", "XYZ", "FLAG") : # permitted extensions liste.pop(i) liste.sort() # sorting the list lon =len(liste) for i in range(lon): # in a row ; ùuch ore 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 TNY / TN3 files print(f"{num:3} {dic[num]}")