site sans réclame PythonCont@ctS'abonner

Recettes pour Python 2.x

V

OICI 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.

Utilisation d'une liste comme tableau à plusieurs dimensions
Filtrer une liste
Compter les éléments d'une liste
Supprimer les doublons d'une liste
Surcharge d'un opérateur
Fichier "Random Access"
Travail sur un répertoire
Description et génération des images .PNM et .PAM

Utilisation d'une liste comme tableau à plusieurs dimensions

Listes

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/python
# -*- coding: latin-1 -*-
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
t[2][1]=9            # 9 est assigné à un endroit précis
print t              # ...et rien que là

Une autre possiblilité est de passer par les tuples:

#! /usr/bin/python
# -*- coding: latin-1 -*-
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/python
# -*- coding: latin-1 -*-
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/python
# -*- coding: latin-1 -*-
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/python
# 2008.02.20 - www.jchr.be - GPL2
# 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)

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 a remplacé numeric et numarray.

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/python

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/python
# python 2.6!
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)

raw_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 d'une liste

Il est possible de compter les é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 li.count(i), i
...
1 a
3 c
3 b

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.

#! /usr/bin/python

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/python

liste=["ah","eheheh","ahah","eheh","ahahah","eh"]
liste.sort() # tri de la liste
print liste
for i in range(len(liste)-1,0,-1): # du dernier element au second
  if liste[i-1] in liste[i]: # regarde si le precedent est inclu dans le suivant
    liste.pop(i) # supprime l'element dans ce cas
print liste

Surcharger un opérateur

Surcharge

Il est possible de redéfinir une fonction interne de python, c'est ce qu'on appelle la surcharge d'un opérateur. L'exemple donné ici concerne la fonction cmp() que l'on voudrait un peu plus souple, c'est-à-dire que deux éléments sont considérés comme égaux s'ils ne sont pas trop différents, la limite étant fixée ici à 5%.

#! /usr/bin/python -Qnew
# Qnew pour la division reelle sous python 2.x

print cmp(7,8)
print cmp(8,7)
print cmp(97,100)
print # separation entre les deux resultats

def cmp(x,y):  # redefinition de la fonction cmp()
  if abs(x-y) / min(x,y) < .05: return 0
  if x < y: return -1
  if x > y: return 1

print cmp(7,8)
print cmp(8,7)
print cmp(97,100)

raw_input("...")

Fichier "Random Access"

Fichiers

Fichiers pour lesquels on utilise un pointeur pour accéder à un endroit particulier du fichier.

#! /usr/bin/python
# -*- coding: latin-1 -*-
"""Exemple d'utilisation de fichier random access - 2007-01-18 - www.jchr.be - GPL-2"""

fd=file("fichier.txt",'w')
fd.seek(10)      # se positionne le pointeur sur le 11ème octet à partir du début
fd.write("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("Eggs")
fd.close()

fd=file("fichier.txt")
fd.seek(0,2)       # longueur du fichier: décalage 0 par rapport à la fin du fichier ('2')
eof=fd.tell()
fd.seek(0)         # début du fichier: décalage 0 par rapport au début du fichier
for i in range(eof):
  print fd.read(1) # imprime la lecture d'un caractère, et déplace le pointeur
fd.close()

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:

#! /usr/bin/python
# -*- coding: latin-1 -*-      # permet les accents dans le script
import 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/python
# -*- coding: latin-1 -*-     # permet les accents dans le script
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/python
# -*- coding: utf-8 -*-       # permet les accents dans le script
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 "  "*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 "  "*n+": "+occurrence  # pas un répertoire: un fichier

entrer(chemin,0)

raw_input("Une touche pour quitter...")