Scrips et modules Python

Documentation:

Utilisation de modules

import os

help(os)
help(os.getlogin)
os.getlogin()
os.getcwd()
os.environ
os.environ['PATH'].split(':')
  • Quelle commande shell correspond à os.environ?
  • Quelle structure de données implémente os.environ?
  • Que fait la méthode split(':')?
from os import getlogin
getlogin()
  • Comment importer toutes les définitions du modules os d’un coup?

Premiers scripts

Autant que possible, essayez d’écrire les scripts Python suivants de la manière la plus simple et la plus conforme possible au style Python (définition en compréhension, itérations, utilisation de la bibliothèque standard, etc).

Script moyenne.py

Compléter le script suivant:

notes = [10, 8, 12.5, 18, 8.75]
# calculer la moyenne des notes et la stocker dans une variable `moyenne`
# ...
print(moyenne)

Résultat attendu:

$ python moyenne.py
11.45
Solution 1
notes = [10, 8, 12.5, 18, 8.75]
s = 0
for n in notes:
    s += n
moyenne = s / len(notes)
print(moyenne)
Solution 2
notes = [10, 8, 12.5, 18, 8.75]
moyenne = sum(notes) / len(notes)
print(moyenne)

Script syracuse.py

Écrire un script qui, pour une variale n, fait le calcul suivant:

afficher n
tant que n =/= 1:
    si n est pair:
        diviser n par 2
    sinon
        multiplier n par 3 et lui ajouter 1
    afficher n

Essayer avec plusieurs valeurs de n.

Solution 1
n = 28
while n != 1:
    if n % 2 == 0:
        n /= 2
    else:
        n = n * 3 + 1
    print(n)
Solution 2
n = 28
while n != 1:
    n = n * 3 + 1 if n % 2 else n/2
    print(n)

Script palindrome.py

Compléter le script ci-dessous. Un palindrome est un mot qui est identique lu de gauche-à-droite ou de droite-à-gauche.

mot = 'kayak'
# tester si c'est un palindrome
# ..
if is_palindrome:
    print(mot, 'est un palindrome')
else:
    print(mot, "n'est pas un palindrome")

Tester avec différents mots.

Solution 1
mot = 'kayak'

is_palindrome = True

for i in range(len(mot)//2):
    if mot[i] != mot[-i-1]:
        is_palindrome = False
        break

if is_palindrome:
    print(mot, 'est un palindrome')
else:
    print(mot, "n'est pas un palindrome")
Solution 2
mot = 'kayak'

is_palindrome = mot == mot[::-1]

if is_palindrome:
    print(mot, 'est un palindrome')
else:
    print(mot, "n'est pas un palindrome")

Script filtre_paire.py

Écrire un script qui filtre une liste de chaînes de caractères en ne gardant que les chaînes dont la longueur est paire.

l1 = ['arbre', 'vers', 'ville', 'vélo']
# construire une liste l2 qui ne contient que les mots de l1 de longueur paire
# ...
print(l2)

Résultat attendu:

$ ./filtre_paire.py
['vers', 'vélo']
Solution 1
l1 = ['arbre', 'vers', 'ville', 'vélo']
l2 = []
for mot in l1:
    if len(mot) % 2 == 0:
        l2.append(mot)
print(l2)
Solution 2
l1 = ['arbre', 'vers', 'ville', 'vélo']
l2 = [mot for mot in l1 if len(mot) % 2 == 0]
print(l2)

Script triangle.py

Écrire un script triangle.py qui commence par:

n = 5
# faire une boucle pour afficher le triangle
# *
# **
# ***
# ... (jusqu'à n)

Il doit donner le résultat suivant:

$ python triangle.py
*
**
***
****
*****

Tester pour différentes valeurs de n.

Solution
n = 5
# faire une boucle pour afficher le triangle
for i in range(1, n+1):
    print("*"*i)

Version avancée: modifier l’affichage pour produire:

$ ./triangle.py 5
    *
   ***
  *****
 *******
*********
Solution
n = 5
# faire une boucle pour afficher le triangle
for i in range(1, n+1):
    print(" "*(n-i) + "*"*(i+i-1))

Script organize_strings.py

Étape 1

Étant donné une liste de mots, les ranger dans un dictionnaire avec pour clé la première lettre du mot.

liste = ['barbe', 'arbre', 'ville', 'vers', 'veille']
# construire dictionnaire `index`
index = {}
# ...
print(index)

Résultat attendu (l’ordre des clés et des mots est sans importance):

$ python organize_strings.py
{"a": ["arbre"], "v": ["vers", "ville", "veille"], "b": ["barbe"]}

Solution
liste = ["barbe", "arbre", "ville", "vers", "veille"]
# construire dictionnaire `index`
index = {}
for mot in liste:
    key = mot[0]
    if key not in index:
        index[key] = [mot]
    else:
        index[key].append(mot)
print(index)

Étape 2

Modifier votre script pour qu’il affiche les clés et les mots dans l’ordre lexicographique:

$ python organize_strings.py
Index a:
  - arbre
Index b:
  - barbe
Index v:
  - veille
  - vers
  - ville
Solution
liste = ["barbe", "arbre", "ville", "vers", "veille"]
# construire dictionnaire `index`
index = {}
for mot in liste:
    key = mot[0]
    if key not in index:
        index[key] = [mot]
    else:
        index[key].append(mot)

for key in sorted(index):
    print("Index", key)
    for mot in sorted(index[key]):
        print("  -", mot)

Étape 3

Modifier le script pour que les clés soient la longueur des mots

Solution
liste = ["barbe", "arbre", "ville", "vers", "veille"]
# construire dictionnaire `index` avec clé = longueur du mot
index = {}
for mot in liste:
    key = len(mot)
    if key not in index:
        index[key] = [mot]
    else:
        index[key].append(mot)

# afficher l'index en triant les clés/mots
for key in sorted(index):
    print("Index", key)
    for mot in sorted(index[key]):
        print("  -", mot)

Étape 4

Modifier le script pour que les clés soient les lettres composant le mot (sans répétition, par ordre alphabétique):

$ python organize_strings.py
Index aber
- arbre
- barbe
Index eilv
- veille
- ville
Index ersv
- vers
Solution
liste = ["barbe", "arbre", "ville", "vers", "veille"]
# construire dictionnaire `index` avec clé = lettres du mots triées, sans répétition
index = {}
for mot in liste:
    key = "".join(sorted(set(mot)))
    if key not in index:
        index[key] = [mot]
    else:
        index[key].append(mot)

# afficher l'index en triant les clés/mots
for key in sorted(index):
    print("Index", key)
    for mot in sorted(index[key]):
        print("  -", mot)

Arguments de ligne de commande

Les arguments de ligne de commande

Créer un fichier test_args.py avec le contenu suivant:

import sys
print(sys.argv)

Appeler le script avec différents arguments:

$ ./test_args.py
$ ./test_args.py a 2 b
$ ./test_args.py --size 3

Définition d’une interface de ligne de commande

Le module argparse (cf. documentation) permet de définir facilement les options de ligne de commande attendues par un script.

Par exemple, le script ci-dessous permet de calculer la factorielle de n:

  • n étant lu sur la ligne de commande
  • en choisissant un algorithme de calcul avec l’option --algorithme

Par exemple: $ ./fact.py 5 --algorithme recursif calcule 5! en utilisant un algorithme récursif.

#!/usr/bin/env python

import argparse
import sys

def factorial_rec(n):
    """Retourne la factorielle de n, avec n >= 0, calculée par un algorithme récursif."""
    return 1 if n == 0 else n * factorial_rec(n - 1)

def factorial_iter(n):
    """Retourne la factorielle de n, avec n >= 0, calculée par un algorithme itératif."""
    f = 1
    for i in range(1,n+1):
        f *= i
    return f

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('n', nargs=1, help=""" Entier n, avec n >= 0""")
    parser.add_argument('--algorithme', type=str, default='recursif', help=""" Algorithme: iteratif ou recursif (par défaut: recursif)""")
    args = parser.parse_args()
    
    n = int(args.n[0])
    if args.algorithme == 'recursif':
        print(f'{n}! est égale à {factorial_rec(n)}')
    elif args.algorithme == 'iteratif':
        print(f'{n}! est égale à {factorial_iter(n)}')
    else:
        print("choix d'algorithme invalide")

Que fait la commande ./fact.py -h? Essayer d’exécuter le script ci-dessus avec différentes entrées.

Étape 1

Modifier le script moyenne.py afin qu’il lise la liste des nombres depuis la ligne de commande

Solution
#!/usr/bin/env python
import argparse

def moyenne(l):
    return sum(l) / len(l)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('n', nargs='+', help=""" liste de nombres dont on calcule la moyenne """)
    args = parser.parse_args()

    numbers = [int(x) for x in args.n]
    m = moyenne(numbers)
    print(m)

Étape 2 (avancé)

Modifier le script organize_strings.py pour qu’il lise la liste de mots sur la ligne de commande, et qu’il choisisse le type de clé grace à l’option --cle qui peut valoir: premiere_lettre, longueur, lettres.

Solution
#!/usr/bin/env python
import argparse
import sys

def cle_premiere_lettre(mot):
    """Retourne la premiète lettre de mot comme clé"""
    return mot[0]

def cle_longueur(mot):
    """Retourne la longueur de mot comme clé"""
    return len(mot)

def cle_lettres(mot):
    """Retourne l'ensemble trié des lettres de mot comme clé"""
    return "".join(sorted(set(mot)))

def construit_index(liste, cle):
    """Retourne le dictionnaire des mots de liste en utilisant cle pour déterminer la clé de chaque mot"""
    index = {}
    for mot in liste:
        key = cle(mot)
        if key not in index:
            index[key] = [mot]
        else:
            index[key].append(mot)
    return index

def affiche_index(index):
    """Affiche index par ordre croissant de mots et de clés"""
    for key in sorted(index):
        print("Index", key)
        for mot in sorted(index[key]):
            print("  -", mot)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--cle', action='store', help='type de clé', default='premiere_lettre')
    parser.add_argument('mots', nargs='+', help='liste de mots à indexer')
    args = parser.parse_args()

    cles = {'premiere_lettre': cle_premiere_lettre, 
            'longueur': cle_longueur,
            'lettres': cle_lettres}

    if args.cle not in cles:
        print('Choix de clé invalide')
        sys.exit()

    index = construit_index(args.mots, cles[args.cle])
    affiche_index(index)

Manipulation de fichiers

Rappels sur les fichiers

La fonction open permet d’ouvrir un fichier en mode lecture, écriture ou ajout:

f = open('file.txt')            # mode lecture seul
f = open('file.txt', mode='w')  # mode écriture, efface le contenu du fichier
f = open('file.txt', mode='a')  # mode écrtiure, depuis la fin du fichier

L’objet retourné dispose de méthodes pour lire (read, readline, readlines), écrire (write, writelines), et le fermer (close).

Exemples:

f = open('data.txt')   # mode lecture par défaut
content = f.read()     # lit la totalité
f.close()

with open('data.txt') as f:
   content = f.readlines() # liste avec un élément par ligne (fin de ligne inclus)
# le fichier est automatiquement fermé à la fin du bloc

out = open('log.txt', mode='a')  # mode ajout
out.write('Hello world!\n')

# on peut aussi utiliser `print`:
print("Bonjour le monde!", file=out)

out.close()

Le module sys donne accès à la sortie standard (sys.stdout), la sortie d’erreur (sys.stderr) et l’entrée standard (sys.stdin) présentés comme des fichiers (mêmes méthodes).

Script moyennes.py

Soit le fichier notes.txt avec le contenu suivant:

Bob,8
Alice,19
Charlie,14
Bob,14
Charlie,20
Alice,13
Bob,16
Alice,18
Charlie,9

Étape 1

Écrire un script moyennes.py qui prend en argument de ligne de commande le nom du fichier, qui charge les notes, puis affiche pour chaque nom la moyenne de ses notes.

Solution
#!/usr/bin/env python
import argparse

def moyenne(l):
    """Calcule la moyenne d'une liste de nombres."""
    return sum(l) / len(l)

def lire_notes(f):
    """Retourne un dictionnaire qui associe aux noms les listes de notes contenues dans le fichier f"""
    notes = {}
    for ligne in f.readlines():
            nom, note = ligne.strip().split(',')
            note = int(note)
            if nom not in notes:
                 notes[nom] = [note]
            else:
                 notes[nom].append(note)
    return notes

def afficher_moyennes(notes):
     """Affiche les moyennes des notes, où notes est un dictionnaire associant à chaque nom sa liste de notes"""
     for nom in notes:
          print(f'{nom}: {moyenne(notes[nom])}')

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('filename', nargs=1, help=""" nom du fichier de notes """)
    args = parser.parse_args()

    with open(args.filename[0]) as f:
        notes = lire_notes(f)
        afficher_moyennes(notes)

Étape 2

Modifier votre script pour qu’il lise sur son entrée standard si aucun nom de fichier est fourni en ligne de commande. Testez votre script en utilisant le pipe du shell: cat data.txt | ./moyennes.py.

Solution
#!/usr/bin/env python
import argparse
import sys

def moyenne(l):
    """Calcule la moyenne d'une liste de nombres."""
    return sum(l) / len(l)

def lire_notes(f):
    """Retourne un dictionnaire qui associe aux noms les listes de notes contenues dans le fichier f"""
    notes = {}
    for ligne in f.readlines():
            nom, note = ligne.strip().split(',')
            note = int(note)
            if nom not in notes:
                 notes[nom] = [note]
            else:
                 notes[nom].append(note)
    return notes

def afficher_moyennes(notes):
     """Affiche les moyennes des notes, où notes est un dictionnaire associant à chaque nom sa liste de notes"""
     for nom in notes:
          print(f'{nom}: {moyenne(notes[nom])}')

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('filename', nargs='?', help=""" nom du fichier de notes """)
    args = parser.parse_args()

    f = sys.stdin if not args.filename else open(args.filename)
    
    notes = lire_notes(f)
    afficher_moyennes(notes)
    
    if f != sys.stdin:
        f.close()

Interfaçage C-Python (avancé)

Cette partie concerne la comparaison des temps de calcul en langages C et Python, ainsi que la mise en oeuvre en langage C, d’un module Python. L’exercice s’appuie sur le calcul de la suite de Fibonacci:

\[\begin{align*} F_0 & = 1\\ F_1 & = 1\\ F_n & = F_{n-1} + F_{n-2} \text{ si } n\ge 2\\ \end{align*}\]
  1. Écrire un script Python fibonacci.py qui prend un entier n en ligne de commande et affiche $F_n$.
  2. Écrire un programme C fibonacci.c qui prend également un entier n en ligne de commande et affiche $F_n$.
  3. Comparer les temps d’exécution des deux programmes pour des valeurs croissantes de n (débuter avec n=10, puis des valeurs de 5 en 5).
  4. En suivant ce tutoriel, programmer:
    • dans le fichier fibmodule.c, la fonction C définie dans le fichier fibonacci.c, ainsi que les fonctions et structures de données nécessaires à la définition du module fib
    • dans le fichier setup.py, les définitions nécessaires à la compilation du module avec distutils.
  5. Importer le module fib dans l’interface REPL python, et vérifier son bon fonctionnement. Comparer les temps de calcul du module fib avec votre programme C et votre script python.