Python :

Python : Séance 1 :

Introduction :

Le langage Python est né à la fin des années 80. En décembre 2008, les concepteurs de Python ont décidé de casser la compatibilité ascendante en proposant la version 3.0, cela implique qu'actuellement 2 versions coexistent : les versions 2.x et 3.x. La conséquence de ce choix est que le code écrit en version 2.x est incompatible avec la version 3.x utilisée comme suppport de cours.

Caractéristiques de Python :

  • Langage open source, portable, bien développé (de nombreuses librairies viennent enrichir le langage) et abondamment documenté.

  • C'est un langage interprété et puissant.

  • Sa syntaxe est simple et son identation significative : elle doit donc être particulièrement rigoureuse. Bonne pratique : 4 espaces par niveau d'indentation, pas de tabs; ne jamais mixer des tabs et des espaces.

  • Il existe, comme en Java, une gestion automatique de la mémoire (garbage collector).

  • Le typage est dynamique (défini à l'exécution) et fort (les types de données employés décrivent correctement les données manipulées).

  • C'est un langage OO.

Les identificateurs (noms de constantes, variables, fonctions, ...) :

Les identificateurs suivent les mêmes règles que dans d'autres langages. Ils doivent commencer par une lettre (ou éventuellement par '_') suivie d'un ou plusieurs cractères (lettre, chiffre, '_'). Ils ne peuvent pas être un mot réservé voir la liste sur le site officiel https://docs.python.org/3/reference/lexical_analysis.html#identifiers).

Il existe des coutumes de nommage qu'il est bon de respecter lorsqu'on veut comuniquer avec d'autres développeurs.

  • une variable commence par une minuscule : maVariable ou ma_Variable

  • de même pour les fonctions : maFonction

  • une constante est écrite en majuscule : MA_CONSTANTE

  • le nom d'une classe commence par une majuscule : MaClasse

Les commentaires :

Les commentaires commencent toujours par le caractère '#' et se terminent à la fin de la ligne; ils ne doivent pas nécessairement démarrer en début de ligne.

Les types de base :

Il existe 2 types entiers : le type int et le type bool.

  • int (entier) : pas de limite de valeur, si ce n'est les limites de la machine, les valeurs peuvent être introduites dans différentes bases (2, 8, 10, 16) ou être converties :

    • en base décimale : 51

    • en base binaire : 0b110011 ou bin(51)

    • en base octale : Oo63 ou oct(51)

    • en base hexadécimale : 0x33 ou hex(51)

    Opérateurs :

    • + : somme

    • - : soustraction

    • * : multiplication

    • / : division en virgule flottante

    • // : division en virgule flottante

    • // : division entière

    • ** : puissance

    • % : modulo

  • bool : Valeurs possibles : True et False

    Opérateurs : ==, !=, and, or, not(...) (évaluation court-circuitée)

Type floattant (float) : Deux notations sont possibles :

  • avec le point décimal par exemple : 3.5 ou .35

  • en notation spécifique par exemple : 3.5e2 (= 3.5 * 102 = 350.0

Nombre complexe (complex) : la notation utilise 2 valeurs flottantes, la seconde introduite par le symbole j représente la partie imaginaire du nombre, par exemple : 2+4j.

La partie réelle est obtenue par l'attribut real : (2+4j).real vaut 2.

La partie imaginaire est obtenue par l'attribut imag : (2+4j).imag vaut 4.

Chaîne de caractères (str) : les littéraux peuvent être encodés de diverses façons :

  • entre simples quotes : 'Voici une chaîne de caractères'

  • entre doubles quotes : "L'autre chaîne de caractères"

  • entre triples quotes :

    '''
    Cette forme permet une
    mise en page sur plusieurs lignes.
    '''

    Remarque : on peut utiliser trois simples quotes (''') ou trois doubles quotes (""") pour former une triple quotes.

Opérations sur les chaînes de caractères :

  • concaténation : +. Par exemple :mot1 + mot2

  • évaluation de la longueur : len(mot1)

Remarque : Contrairement au langage C, une affectation n'a pas d'effet à la compilation et donc l'expression suivante génère une erreur : res = ( c= True) and True.

Structures de contrôle :

  • Une instruction simple ne connaîtpas de caractères indiquant la fin de l'instruction (cf d'autres langages), mais se termine normalement en fin de ligne; outre la continuité implicite des lignes au sein des parenthèses/crochets/accolades, une instruction peut être prolongée sur la ligne suivante par un simple '' en fin de ligne.

  • Un bloc d'instructions n'est pas délimité par une paire d'accolades mais est marqué par une indentation significative (en général de 4 caractères).

  • Une alternative respecte la structure suivante : if cdt : instruction [elif cdt : instruction] else : instruction.

    if a < b :
        print(a, " est inférieur à ", b)
    else :
        print(a, " est supérieur ou égal à ", b)
  • Une répétitve de type while est contrôlée par une condition de continuation.

    while a < b :
        print(a)
        a += 1
  • Un parcours for est répété pour chaque valeur indiquée dans une liste.

    for lettre in "Voici ma phrase" :
        if lettre.isalpha() :
            print(lettre)
  • Les ruptures de séquences :

    • break permet de quitter prématurément une boucle.

    • continue permet de quitter prématurément une itération pour revenir directement à la condition de contrôle.

L'interpréteur :

À la manière des shells Linux, Python peut être considéré comme un interpréteur qui permet de traduire et d'exécuter les instructions introduites au clavier. Il peut donc aussi être utilisé comme calculatrice.

L'appel du programme python3 sur la ligne de commande du shell pour introduit, après quelques lignes de commentaire, dans un mode interpréteur (prompt >>>).

Les scripts en Python :

L'écriture de programmes en Python nécessite de respecter quelques contraintes :

  • La première ligne doitappeler l'interpréteur en respectant le format #!/usr/bin/pythonpython est un alias ou un lien symbolique vers l'exécutable désiré.

  • Par convention, on impose l'extension .py aux fichiers.

  • Le fichier doit être rendu exécutable.

  • Par exemple :

    #!/usr/bin/python
    a, b = 4, 8
    if a == b :
        print ("a égale b")
    else :
        print("a différent de b")

Exercices :

  1. Utilisez l'interpréteur pour calculer la surface d'un rectangle de 5 mètres de long sur 3 de large.

    >>>longueur = 5
    >>>largeur = 3
    >>>print("surface rectangle ", longueur, " x ", largeur, " = ", longueur * largeur)
  2. Utilisez l'interpréteur pour calculer le volume d'un cube de 5 mètres de côté.

    >>>cote = 5
    >>>cote **(3)
  3. Utilisez l'interpréteur pour calculer le quotient et le reste de la division de 123 par 12.

    >>>a = 123
    >>>b = 12
    >>>a // b
    >>>a % b
  4. Testez si les valeurs 0, "", "0" et None sont définies comme des valeurs booléennes False. Testez d'autres valeurs reconnues par Python. Pour chacune de ces valeurs, affichez son évaluation si ça vaut True ou False.

    S'évaluent à False :
    ""
    0
    0.0
    False
    None
    
    >>>a = None
    >>>if not(a) :
    >>>... print("None s'évalue à False")
    >>>...
    >>>None s'évalue à False
  5. Écrivez un script qui affiche la liste des nombres de la suite de Fibonacci jusqu'à 100.

    La suite de Fibonacci est une suite infinie d'entiers naturels tel que le premier terme vaut 0, le second terme vaut 1 et les termes suivants sont égaux à la somme des deux termines qui le précèdent. Ses neuf premiers termes sont : 0, 1, 1, 2, 3, 5, 8, 13, 21.

    On peut également définir cette suite récursivement de la manière suivante. La suite de Fibonacci est une suite d'entiers naturels n0, n1, n2, n3, ... tel que n0 = 0, n1 = 1 et "n(i + 2) = n(i + 1) + ni" (pour tout entier naturel i).

    #!/usr/bin/python3
        
    a, b = 0, 1
    while a < 100 :
        print(a)
        temp = a + b
        a = b
        b = temp
  6. La résolution d'une équation du second degré (ax2 + bx + c) peut donner 0, 1 ou 2 solution(s) réelle(s) en fonction de la valeur du déterminant rho (b2 - 4ac); si rho est négatif, il n'y a pas de racine réelle, si rho est nulle, il y a une seule racine (-b / 2a) tandis que si rho est positif il y a 2 racines (-b ± sqrt(rho) / 2a). Écrivez un programme qui évalue les solutions d'équations du second degré dont les coefficients (a, b et c) sont introduits au clavier (la lecture sur stdin est réalisée grâce à la fonction input(); l'arrêt du programme est requis lorsque le coefficient a est nul.

    #!/usr/bin/python3
    
    import math
    
    a, b, c = int(input('a = ')), int(input('b = ')), int(input('c = '))
    
    if a == 0 :
        print("Le coefficient a est nul.")
    else :
        delta = b **(2) - 4 * a * c
    
        if delta < 0 :
            print("pas de solution")
        elif delta == 0 :
            print("une solution")
            x = -b / (2 * a)
            print("x = ", x)
        else :
            print("deux solutions")
            x1 = (-b - math.sqrt(delta)) / (2 * a)
            x2 = (-b + math.sqrt(delta)) / (2 * a)
            print ("x1 = ", x1, " et x2 = ", x2)

Python : Séance 2 :

Listes :

Une liste est une collection ordonnée et modifiable d'élémets éventuellement hétérogènes. En Python, les tableaux et les listes sont confondus. Nous pouvons dès lors utiliser indifféremment les termes "tableau" ou "liste" pour parler des listes.

  • Les éléments d'une liste sont séparés par des virgulres et entourés de croichets.

    list1 = ['a', 14, 'abcd']
    print(list1) # ['a', 14, 'abcd']
    
    list2 = [4, 2.718, True]
    print(list2) # [4, 2.718, True]
    
    list3 = [list1, list2] # liste de listes
    print(list3) # [['a', 14, 'abcd'], [4, 2.718, True]]
  • L'accès aux éléments individuels d'une liste se fait en indiquant leur indice entre crochets. La numérotation des éléments commence à zéro. L'utilisation d'indices négatifs permet de compter à partir de la fin de la liste. La fonction len donne la longueur d'une liste.

    liste = [3, 7, 1, 9]
    liste[0] # 3
    liste[2] = 'a' # liste = [3, 7, 'a', 9]
    liste[-1] = 'b' # liste = [3, 7, 'a', 'b']
    liste[0], liste[2] = liste[2], liste[0] # liste = ['a', 7, 3, 'b'] (permutation)

    On peut extraire un sous-ensemble des éléments d'une liste en spécifiant un intervalle d'indices à l'aide du caractère "deux points". Les indices conservés sont ceux qui sont supérieurs ou égaux à la borne inférieure (par défaut 0) et strictement inférieurs à la borne supérieure (par défaut len(liste)). Un 3ième indique un pas de parcours.

    liste = [1, 2, 3, 4]
    liste[1:3] = [5, 6, 7] # liste = [1, 3, 6, 7, 4]
    liste[0:2] = [8, 9] # liste = [8, 9, 6, 7, 4]
    liste[0:2] = [] # liste = [6, 7, 4]
    lsite[1:1] = ['a', 'b'] # liste = [6, 'a', 'b', 7, 4]
  • Quelques opérateurs sur les listes : + (concaténation), * (répétition), == et != (comparaison).

    rep = [0.O] * 3 # rep = [0.0, 0.0, 0.0]
    concat = [1, 2] + [3] # concat = [1, 2, 3]
    [1.2, False] == [1.2, False] # True
    [5, 'abc'] != [5, 'f', 1.1] # True

    La fonction range permet de produire un intervalle d'entiers. Notez que les entiers générés seront strictement inférieurs à la borne supérieure spécifiée. La valeur retournée par range n'est pas une liste, mais un objet spécial que l'on peut transformer en liste.

    l1 = list(range(5)) #l1 = [0, 1, 2, 3, 4] (borne inférieure = 0 par défaut)
    l2 = list(range(3, 6)) # l2 = [3, 4, 5]
    l3 = list(range(2, 11, 3)) # l3 = [2, 5, 8] (3ième paramètre = pas

    L'opérateur in permet de tester l'appartenance d'un élément à une liste :

    3 in l1, 5 in l2, 6 in l3 # (True, True, False)

    Python définit différentes méthodes sur les objets list, telles que append, insert, clear, copy, sort, reverse, etc. Pour plus d'informations sur ces méthodes, référez-vous à la documentation Python : https://docs.python.org/3/tutorial/datastructures.html.

  • Parcours d'une liste :

    for x in liste :
        print(x)

    Ce qui est équivalent à :

    i = 0
    while i < len(liste) :
        x = liste[i]
        print(x)
        i = i + 1

    Notez que la fonction range permet de simplifier l'écriture d'une boucle avec compteur :

    for i in range(5) :
        print(i)
  • ATTENTION : l'affectation d'une liste à une variable ne crée pas une copie de la liste ! Comme les listes sont des objets, il s'agit d'une copie de référence et non de contenu.

    a = [1, 2, 3]
    b = a # copie de référence (copie superficielle)
    b = list(a) # copie profonde
    b = a[:] # copie profonde
  • REMARQUE : Python permet d'appliquer aux chaînes de caractères les opérateurs dédiés aux listes. Par conséquent, les chaînes peuvent se manipuler de manière similaire aux listes à l'aide de ces opérateurs. Pour les méthodes définies sur une str, voir la documentation (https://docs.python.org/3/library/stdtypes.html#string-methods). Notez enfin qu'une affectation d'une str à une variable crée toujours une copie profonde.

Introduction aux fonctions :

Une fonction est un ensemble d'instructions regroupées sous un nom et s'exécutant à la demande. La syntaxe définie par Python se compose :

  • du mot-clé def suivi de l'identificateur de la fonction, de parenthèses entourant les paramètres de la fonction séparés par des virgules, et du caractère "deux points" (qui termine toujours une instruction composée)

  • d'une chaîne de documentation ou "docstring" (facultative) indentée comme le corps de la fonction;

  • du bloc d'instructions indenté par rapport à la ligne de définition, et qui constitue le corps de la fonction.

#!/usr/bin/python3

# définition de fonction
def divisionEntiere (a, b) :
    
    """
        Calcule et renvoie :
            - le quotient de la division entière de a par b,
            - le reste de la division entière de a par b.
    """
    
    q = a // b
    r = a % b

    return q, r

# script principal
r1, r2 = divisionEntiere(10, 3)
print(r1, r2) # 3 1

Notez qu'en Python, le résultat renvoyé par une fonction n'apparaît pas dans son entête. C'est par conséquent l'instruction return qui va déterminer si la fonction :

  • ne renvoie rien (pas de return),

  • renvoie un unique résultat (un ou plusieurs return uniques, c'est-à-dire suivis d'une seule expression) ou

  • renvoie plusieurs résultats (un ou plusieurs return multiples, c'est-à-dire suivis de plusieurs expressions séparées par des virgules). On peut récupérer les résultats dans une seule variable, mais c'est alors un un tuple (c'est-à-dire une liste qui ne peut pas être modifiée).

    x = divisionEntiere(10, 3)
    x[0] # 3
    x[1] # 1
  • Notez que différents return d'une même fonction peuvent très bien renvoyer des variables de types différents !

  • REMARQUE : une fonction peut être définie en ligne de commande. Il faut appuyer deux fois sur la touche "enter" lorsque le code de la fonction est terminé, pour revenir au prompt de l'interpréteur. La fonction est alors disponible pour la session courante. Cette manière de faire est bien sûr réservée à des fonctions courtes qui ne sont pas conservées d'une session à l'autre !

Exercices :

  1. Écrivez un script qui, à partir d'une liste donnée d'entiers positif, recherche et affiche la plus grande valeur.

    liste = [0, 3, 1, 4, 3, 6, 2]
    liste.sort()
    print(liste[-1]) # 6
  2. Écrivez une fonction qui crée une liste des multiples de m inférieurs à n. Testez-la avec des valeurs entrées par l'utilisateur.

    #!/usr/bin/python3
    
    def multiple(m, n) :
        i, res = 2, m
        multiples = []
        
        while res < n :
            multiples.append(res)
            res = i * m
            i += 1
    
        return multiples
    
    m, n = int(inout("m = ")), int(input("n = "))
    print(multiple(m, n))
  3. Soient deux listes contenant respectivement le nombre de jours et le nom de chaque mois de l'année. Écrivez un script qui crée une nouvelle liste en fusionnant les deux premières listes. Elle contiendra donc tous les éléments des deux listes en les alternant, de telle manière que chaque nom de mois sera suivi du nombre de jours correspondant.

    Le script doit ensuite afficher le contenu du tableau fusionné sous la forme : Le mois de Janvier compte 31 jours.

    #!/usr/bin/python3
    
    def mois(liste1, liste2) :
        liste = []
        i = 0
    
        while i < len(liste1) :
            liste.append("Le mois de " + liste1[i] + " comporte " + str(liste2[i]) + " jours.")
            i += 1
    
        return liste
    
    listeJours = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    listeMois = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre']
    liste = mois(listeMois, listeJours)
    
    for mois in liste :
        print(mois)
  4. Écrivez une fonction qui affiche un message indiquant si un élémént x est présent dans une liste. Définissez d'abord la fonction present qui utilise une boucle while. Définisssez ensuite la fonction presentPython qui doit pleinement utiliser les possibilités offertes par Python.

    Comparez ensuite les performances de ces deux implémentations à l'aide du script present.py :

    #!/usr/bin/python3
    
    import random
    import time
    
    ## Définitions de fonctions
    # TODO
    def present (elt, tab) :
        i = 0
    
        while i < len(tab) :
            if tab[i] == elt :
                print(str(elt) + " est contenu dans la liste")
                return None
            i += 1
        print(str(elt) + " n'est pas contenu dans la liste")
    
    def presentPython (elt, tab) :
        if elt in tab :
            print(str(elt) + " est contenu dans la liste")
        else :
            print(str(elt) + " n'est pas contenu dans la liste")
    
    ## Script de test
    # on produit un tableau d'un million de notes aléatoires
    notes = []
    
    for i in range(1000000) :
        notes.append(random.choice(range(21)))
    
    # on compare notre algorithme de présence à l'implémentation de Python
    # - avec un entier présent :
    t1 = time.clock()
    present(20, notes)
    t2 = time.clock()
    print('notre réponse en ', "{:.6f".format(t2 - t1), ' sec.')
    t1 = time.clock()
    presentPython(20, notes)
    t2 = time.clock()
    print('la réponse de Python en ', "{:.6f}".format(t2 - t1), ' sec.')
    # - avec un nombre absent :
    t1 = time.clock()
    present(-1, notes)
    t2 = time.clock()
    print('notre réponse en ', "{:.6f".format(t2 - t1), ' sec.')
    t1 = time.clock()
    presentPython(-1, notes)
    t2 = time.clock()
    print('la réponse de Python en ', "{:.6f}".format(t2 - t1), ' sec.')
  5. Écrivez une fonction qui vérifie si les éléments d'une liste sont symétriques par rapport au centere de la liste. Testez cette fonction sur une chaîne de caractères (palindrome).

    #!/usr/bin/python3
    
    def palindrome(liste) :
        i = 0
    
        while i < len(liste) / 2 :
            if liste[i] != liste[-1 -i] :
                print(liste + " n'est pas un palindrome.")
                return None
            i += 1
    
        print(liste + " est un palindrome.")
    
    mot1, mot2 = input("mot1 = "), input("mot2 = ")
    palindrome(mot1)
    palindrome(mot2)
  6. Écrivez une procédure qui inverse l'ordre des éléments d'un tableau.

    #!/usr/bin/python3
    
    def reverse (liste) :
        l = []
        i = 0
    
        while i < len(liste) :
            l.append(lste[-1 -i])
            i += 1
        return l
    
    liste = [3, 5, 7; 4]
    print(reverse(liste))
  7. Écrivez une fonction qui transpose une matrice M représentée sous forme de listes de lignes (qui sont elles-mêmes des listes) ainsi qu'une fonction qui affiche le contenu d'une matrice. Écrivez un script pour tester ces fonctions.

    #!/usr/bin/python3
    
    def transposer (m) :
        """
            :param m: la matrice à transposer
            :return: la liste de lignes
        """
        matrice = []
    
        for liste in m :
            matrice.append(liste[:])
        
        return matrice
    
    def afficher (ligne) :
        """
            affiche la ligne passée en paramètre
        """
        for i in ligne :
            print(list(i))
    
    li = [[1, 2, 3], [4, 5, 6]]
    m = transposer(li)
    afficher(m)
  8. Écrivez une fonction qui supprimer un élément d'une liste. Elle renvoie vrai si l'élément a été supprimé, faux sinon. Testez cette fonction avec un script qui demande à l'utilisateur d'entrer des valeurs à supprimer dans une liste et s'arrête lorsque cette valeur n'a pas pu être supprimée.

    #!/usr/bin/python3
    
    def supp (liste, elt) :
        if elt in liste :
            liste.remove(elt)
            print("suppression de " + str(elt))
            return True
        
        print("suppression impossible de " + str(elt))
        return False
    
    liste = [3, 5, 6, 7, 2, 1, 8, 9]
    suppression = True
    
    while suppression :
        print(liste)
        elt = int(input("ntrez un chiffre : "))
        suppression = supp(liste, elt)
  9. Implémentez le tri par sélection à l'aide d'une procédure qui trie les éléments d'un tableau. Pour rappel, voici le principe : on détermine la position du plus petit élément du tableau, on le met en première position (en l'échangeant avec le premier élément) et on répète le procédé sur le reste du tableau. Une fois votre procédure implémentée et testée, comparez son efficacité avec la méthode sort de Python à l'aide d'un script similaire à celui de l'exercice 4.

    #!/usr/bin/python3
    
    import time
    import random
    
    def tri (li) :
        for i in range (1, len(li) - 1) :
            min = i
            for j in range(i + 1, len(li)) :
                if li[j] < li[min] :
                    min = j
            if (min != i) :
                temp = li[i]
                li[i] = li[min]
                li[min] = temp
        return li
    
    def triPython (li) :
        return li.sort()
    
    ## Script de test
    # on produit un tableau d'un million de notes aléatoires
    notes = []
    for i in range(1000000) :
        notes.append(random.choice(range(21)))
    
    # on compare notre algorithme de tri à l'implémentation tri de Python
    t1 = time.clock()
    tri(notes)
    t2 = time.clock('notre réponse en ', "{:.6f}".format(t2 - t1), ' sec.')
    t1 = time.clock()
    triPython(notes)
    t2 = time.clock('la réponse de Python en ', "{:.6f}".format(t2 - t1), ' sec.')

Phython : Séance 3

Expression régulière :

Une expression régulière est un modèle décrivant un ensemble de caractères (respectant le modèle) et respectant une syntaxe ("..." ou r"...").

Symboles de base :

  • ^ : le début de la chaîne

  • $ : la fin de la chaîne

  • . : n'importe quel caractère

Pour symboliser les caractères spéciaux dans les expressions régulières, il est nécessaire d'échapper le backslash faisant précéder d'un autre backslash : \ (échappement).

Remarque : échappement du \ automatique avec syntaxe r"...".

ex : '\\n'r'\n'

  • [hij] : le caractère h, i ou j

  • [^hij] : n'importe quel caractère sauf h, i et j

  • [b-i] : un caractère alphabétique entre b et i

  • [^b-i] : n'importe quel caractère sauf un caractère alphabétique entre b et i

  • (x|y) : x ou y

  • \d : un chiffre → [0-9]

  • \D : pas un chiffre → [^0-9]

  • \s : un espace → [\t\n\r\f\v]

  • \S : pas un espace → [^\t\n\r\f\v]

  • \w : un alphanumérique → [a-zA-Z0-9_]

  • \W : pas un alphanumérique → [^a-zA-Z0-9_]

Symboles de répétition :

À placer après ce à quoi ils s'appliquent :

  • * : 0-n fois

  • ? : 0 ou 1 fois

  • + : au moins 1 fois

  • {x} : x fois

  • {x,y} : entre x et y fois

  • {,y} : entre 0 et y fois

  • {x,} : au moins x fois

Syntaxe de regroupement :

  • ( : début d'un groupe

  • ) : fin d'un groupe

  • \numero : récupération du groupe : chaque groupe d'une expression régulière est identifié par son numéro d'apparition dans l'instruction courante (commençant à 1)

Syntaxe : Exemples :

  • "^$" ou "" → chaîne vide

  • "^.*$" ou ".*" → n'importe quelle chaîne

  • r"W+" → n'importe quelle chaîne

  • ".*[aeiouy]$" → se terminant par une voyelle

  • r"^(BE|be)\d{2}(\s?\d{4}){3}$ → n° compte en banque belge (BE61 0235 4895 9879)

Notion de module :

Module = ensemble (librairie) de méthodes qui doivent être importées (import) avant emploi.

  • import random # nombres pseudo-aléatoires

  • import time # temps et calendriers

  • import re # expressions régulières

Import de fonctions :

On peut enregistrer des définitions de fonctions (et uniquement cela) dans un fichier monFichier.py.

Dans tout autre fichier Python ou en ligne de commande : import monFichier.

Les fonctions sont alors disponibles via monFichier.maFonction(...).

Remarque : le fichier doit être présent dans le répertoire courant ou dans un répertoire couvert par le PYTHON PATH.

Import : exemple :

>>>import math
>>>math.sqrt(25)
5.0
>>>math.pow(10,5)
100000.0
>>>math.sin(math.pi)
&.2246...e-16

Le module re :

Il permet de manipuler les expressions régulières.

Quelques méthodes :

  • search et match recherche une expression dans une chaîne.

  • sub remplace une expression dans une chaîne.

  • findall obtient une liste de sous-chaînes vérifiant une expresion.

  • split obtient une liste de chaînes sur base de séparateurs.

Search :
  • re.search(pattern,str,flags=0)

    • pattern : expression régulière

    • str : chaîne à traiter

    • flags : option (facultatif)

  • renvoie

    • objet de type match si expression trouvée, n'importe où dans la chaîne (parcours de gauche à droite)

    • None sinon

Search : Exemples :

>>>import re
>>>re.search(r"abc", "abcdef")
<_sre.SRE_Match object; span=(0, 3), match='abc'>
>>>re.search(r"ab*", "abcdef")
<_sre.SRE_Match object; span=(0, 2), match='ab'>
>>>re.search("abc", "abababababab")
>>>re.search(r"^b+$", "abbbbbb")
<_sre.SRE_Match object; span=(0, 7), match='abbbbbb'>
Search : Multiline :

Option multiline pour recherche en début de ligne

re.search(pattern,str,flags=re.M)

Exemple :

re.search(r"^a", "dbcbna") # no match
re.search(r"^a", "dcb\na", flags=re.M) # match

Match :

  • re.match(pattern,str,flags=0)

    • pattern : expression régulière

    • str : chaîne à traiter

    • flags : option (facultatif)

  • renvoie

    • objet de type match si expression trouvée en début de chaîne

    • None sinon

Match : Exemple :

>>>if re.match(r"abc", "abcdef"):
...		print("trouvé")
...	else:
...		print("pas trouvé")
...
trouvé
>>>

Sub :

  • re.sub(pattern,repl,str,count=0,flags=0)

    • pattern : expression régulière

    • repl : chaîne de remplacement

    • str : chaîne à traiter

    • count : nb max de remplacements (= remplacer partout)

    • flags : option (facultatif)

  • renvoie la chaîne après traitement

Sub : Exemples :

>>>re.sub("soir", "jour", "bonsoir")
'bonjour'
>>>re.sub("-", ":", "w-x-y-z", count=2)
'w:x:y-z'

Findall :

  • re.findall(pattern,str,flags=0)

    • pattern : expression régulière

    • str : chaîne à traiter

    • flags : option (facultatif)

  • renvoie une liste des sous-chaînes vérifiant l'expression régulière

Findall : Exemples :

>>>re.findall("([0-9]+)", "j'aurai 20 ans en 2018")
['20', '2018']
>>>re.findall(r"\S+", "une liste de mots")
['une', 'liste', 'de', 'mots']

Split :

  • re.split(pattern,str,maxsplit=0,flags=0)

    • pattern : séparateurs (expression régulière)

    • str : chaîne à traiter

    • maxsplit : nb max de séparations (0 = tout séparer)

    • flags : option (facultatif)

  • renvoie

    • une liste des chaînes délimitées par les séparateurs

      si plus que maxsplit séparateurs, la dernière chaîne contient des séparateurs

Split : Exemples :

>>>re.split("/", "02/764 46 46")
['02', '764 46 46']
>>>re.split(",", "un,deux,trois,quatre", maxsplit=2)
['un', 'deux', 'trois,quatre']

Exécuter des commandes Unix :

Le module subprocess permet de générer des processus sur système d'exploitation et permet aussi de se connecter à leurs canaux de communication (input/output/error).

Getoutput :

subprocess.getoutput(cmd)cmd est la commande à exécuter dans un shell, sous forme de string.

Il renvoie l'output de la commande sur stdout ou stderr sous forme d'une str (les éventuels passages à la ligne y sont représentés par '\n').

Getoutput : Exemple :

>>>import subprocess
>>>subprocess.getoutput('ls')
'ex1.py\nex2.py\nex3.py'
>>>ex = subprocess.getoutput('ls')
>>>print(ex)
ex1.py
ex2.py
ex3.py

Exercices :

  1. Écrivez un script qui :

    • lit 10 lignes au clavier

    • affiche la ligne lue si elle n'est pas vide

    • compte le nombre de lignes vides

    • affiche le nombre de lignes vides introduites

    #!/usr/bin/python3
    
    nbLignesVides = 0
    
    for i in range(1, 11) :
        ligne = input("ligne n°" + str(i) + " = ")
        if ligne != "" :
            print(ligne)
        else :
            nbLignesVides += 1
    
    print("Nombre de lignes vides : " + str(nbLignesVides))
  2. Écrivez un script qui :

    • lite des lignes au clavier, tant qu'une ligne vide n'est pas introduite

    • affiche la ligne lue si elle contient au moins un mot de plus de 10 caractères

    #!/usr/bin/python3
    
    i = 1
    ok = True
    
    while ok :
        ligne = input("ligne n°" + str(i) + " = ")
        if ligne == "" :
            ok = False
        elif len(ligne) >= 10 :
            print(ligne)
        i += 1
  3. Écrivez un script qui :

    • lit des lignes au clavier

    • vérifie si un nombre entier (non signé) a été introduit

      • si oui, affiche le nombre

      • sinon affiche la ligne lue suivie de " n'est pas un nombre non signé"."

    Le script se termine dès que l'utilisateur introduit avec une chaîne vide.

    Test avec :
    0 OK
    00 KO
    -0 OK
    +340 OK
    +012 KO
    -015 KO
    +105 OK
    -100 OK
    #!/usr/bin/python3
    
    import re
    
    i = 1
    ok = True
    
    while ok :
        ligne = input("ligne n°" + str(i) + " = ")
        if ligne == "" :
            ok = False
        elif re.match(r"^[\+-]?0.+$", ligne) == None :
            print(ligne)
        else :
            print(ligne + " n'est pas un nombre signé")
            i += 1
  4. Écrivez un programme qui affiche l'heure dans le format suivant : "Il est 10h30" à partir du résultt de la commande date sns argument.

    #!/usr/bin/python3
    
    import subprocess as sp
    date = sp.getoutput('date +%Hh%M')
    print("Il est " + date)
  5. Écrivez un programme qui affiche la liste des utilisateurs qui ont le bash comme shell de login.

    Rappel : chaque ligne du fichier /etc/passwd présente le format suivant : login:x:uuid:commentaire:homeRep:loginShell.

    Pour ce faire, récupérez le contenu du fichier /etc/passwd, pour chaque ligne, isolez le nom de login et le loginShell, n'affichez que les logins où le loginShell est /bin/bash.

    #!/usr/bin/python3
    
    import subprocess as sp
    import re
    
    etc_passwd = sp.getoutput('cat /etc/passwd')
    etc_passwd = re.split("\n", etc_passwd)
    
    
    for ligne in etc_passwd :
        v = re.split(':', ligne)
        if v[-1] == '/bin/bash' :
            print(v[0])

Python : Séance 4 :

Expressions régulières (compléments) :

Sub (Rappel) :

re.sub(pattern,repl,str)

  • pattern : expression régulière

  • repl : chaîne de remplacement

  • str : chaîne à traiter

renvoie la chaîne après traitement

Groupe (Rappel) :

  • ( : début d'un groupe

  • ) : fin d'un groupe

  • numéro : récupération du groupe : chaque groupe d'une expression régulière est identifié par son numéro d'apparition dans l'instruction courante(commençant à 1).

Groupe : Exemple :

Éliminer les espaces autour des symboles de ponctuation :

>>>re.sub(r"\s*([,.;:])\s*", r"\1", "a ,b ; c:d  .")
'a;b:c:d.'

Seach(Rappel) :

  • re.searcn(pattern,str,flags=0) où :

    • pattern : expression régulière

    • str : chaîne à traiter

    • flags : option (facultatif)

  • renvoie :

    • objet de type match si expression trouvée, n'importe où dans la chaîne

    • None sinon

Match (Rappel) :

  • re.match(pattern,str,flags=0) où :

    • pattern : expression régulière

    • str : chaîne à traiter

    • flags : option (facultatif)

  • renvoie :

    • objet de type match si expression trouvée, en début dans la chaîne

    • None sinon

Méthodes GROUPS et GROUP :

  • Définies sur objets de type match

  • Récupérer des sous-chaînes correspondant à des groupes

  • groups() renvoie un tuple de toutes les sous-chaînes correspondant aux groupes du pattern de recherche

  • group(n) renvoie la sous-chaîne correspondant au groupe n et, si n vaut 0, renvoie la chaîne trouvée

GROUPS et GROUP : Exemple :

>>>m1 = re.match(r"(\w{3}), (\d\d) (\w{3}).*\s", "Sat, 17 Fev 2018 17:11:08 CET")
>>>m1.groups()
('Sat', '17', 'Fev')
>>>m1.group(1)
'Sat'
>>>m1.group(2)
'17'
>>>m1.group(0)
'Sat, 17 Fev 2018 17:11:08'

Les conteneurs standards :

Les conteneurs de séquences :

  • Chaînes de caractères (str) → ex : "ddgdss456hh"

  • Listes (list) → ex : ^'test',['e',True],2.56]

  • Tuples (tuple) similaires mais non modifiables → ex :

    >>>t1=('test',['e',True],(2,3),2.56)
    >>>t1[0]
    'test'
    >>>t&1[-3][1]
    True
    >>>t1[2]
    (2,3)

    Les parenthèses ne sont pas oblogatoires →ex :

    >>>t2=a,2,[2.5]
    >>>t2
    ('a',2,[2.5])

Tableaux associatifs :

Information → clé:valeur

Implémentation → les dictionnaires : dict

Syntaxe : {"clé1":valeur1,"clé2":valeur2,"clé3":valeur}

Caractéristiques :
  • Les clés sont des str.

  • Itérable mais pas d'ordre fixe de parcours

  • Taille = nb de paires, donnée par fonction len(...)

  • L'opérateur in vérifie l'appartenance d'une clé + permet parcours itératif des clés

Création d'un dictinonnaire :
  • grâce aux parenthèses.

    • vide : d1 = {}

    • initialisé : d2 = {"a":"b","c":"d"}

    • initialisé en compréhension : d3 = {x:x.upper() for x in ('a','b','c')} équivalent à d3= {"a":"A","b":"B","c":"C"}

  • grâce au constructeur dict(...) :

    • paramètres = liste de tuples (clé,valeur) : d4 = dict([('a','b'),('c','d')]) → {'a':'b','c':'d'}

    • paramètres de la forme clé="valeur" où la clé est donnée sans guillemets : d5 = dict(nom='Dulieu',prénom='Jos',age=51) → {'nom':'Dulieu','prénom':'Jos','age':51}

Accès :

Accès à valeur via clé en indice

>>>d = dict('Jan':1,'Fev':2)
>>>d['Jan']
1

Clés=valeurs 'hachables'

D'autres fonctionnalités d'un objet DICT :
lesMois = {'Jan': 1, 'Fev': 2, 'ar': 3}
lesMois['Avr'] = 4 # ajout d'une paire
del lesMois['Jan'] # supprime la paire 'Jan':1
lesMois.keys() # retourne la liste des clés
lesMois.values() # retourne la liste des valeurs
lesMois.items() # retourne une liste de tuples (clé,valeurs)

Exercices :

  1. Écrivez un script Python qui gère un dictionnaire.

    • Le programme lit sur stdin des lignes constituées de 2 mots (mot1 et mot2) formant une commande (mot1) séparée de son argument (mot2) par une tabulation. Les commandes acceptées sont :

      • IN clé : qui affiche la valeur stockée dans le dictionnaire correspondant à la clé ou un message indiquant l'absence de la clé.

      • DEL clé : qui supprime la clé si elle existe.

      • ALL : cette commande n'a pas d'argument, elle affiche le contenu du dictionnaire.

      • Toute autre valeur introduite constitue une information à stocker dans le dictionnaire, mot1 étant la clé et mot2 la valeur.

      La ligne doit être vérifiée par une expression régulière et les 2 mots extraits grâce à la méthode groups().

    • Le programme doit utiliser une fonction lire() à écrire qui lit une ligne, la vérifie et retourne un tuple de 2 valeurs, les 2 mots extraits de la ligne.

    #!/usr/bin/python3
    
    import re
    
    def lire() :
        ligne = input()
        
        if (ligne == "") :
            return None
        
        m1 = re.match(r"(\w+)\t?(\w*)", ligne)
        return m1.groups()
    
    dico = {}
    ok = True
    while ok :
        tuples = lire()
        if tuples == None :
            print("ligne vide")
            ok = False
            break
        
        commande = tuples[0].lower()
        argument = tuples[1]
        
        if commande == "all" :
            if argument != "" :
                print("Il y a un argument en trop dans la commande all")
                ok = False
            elif len(dico) != 0 :
                for t in dico.items() :
                    print(t)
            else :
                print("Table vide !")
        elif commande == "in" :
            if argument == "" :
                print("Il manque un argument dans la commande in")
                ok = False
            elif argument in dico :
                print(dico[argument])
            else :
                print("La clé '" + argument + "' n'existe pas dans le dico")
        elif commande == "del"
            if argument in dico :
                del dico[argument]
        else :
            if argument == "" :
                print("Il manque le deuxième mot")
                ok = False
            else :
                dico[commande] = argument
  2. Écrivez un script Python qui génère un rapport résumant les connexions au système qui sont terminées.

    • La liste des connexions est fourniepar la commande Linux last à appeler dans le programme. Si la commande last ne fournit que deux ou trois connexions sur votre machine, copiez le fichier last_put.txt (contenant le résultat de la commande last sur albert) dans votre répertoire courant et remplacez la commande par cat last_out.txt.

    • Les enregistrements correspondant à une connexion terminée sont sélectionnés en utilisant une expression régulière, qui sert également à extraire les informations intéressantes.

    • Les différentes connexions sont compilées dans un dictionnaire dont la clé est le login de l'utilisateur et la valeur est une liste composée de 2 informations : le nombre de connexions terminées et la durée totale en minutes.

    • En fin de traitement, le programme affiche la liste des utilisateurs triée (en utilisant la foncion sorted()) suivant l'ordre alphanumérique, donnant pour chacun le nombre de connexions et la durée en heure-minutes au format "01h01m".

    Variante : remplacez le nombre de connexions par la liste des connexions (jour,heure,durée).

    #!/usr/bin/python3
    
    import subprocess as sp
    import re
    
    dico = {}
    last = sp.getoutput('cat last_out.txt')
    l = re.split('\n', last)
    
    for ligne in l :
        if re.search(r"\((\d\d):(\d\d)\)", ligne) :
            ligne = re.split('\s', re.sub("(\s|\t)+", " ", ligne).strip())
            print(ligne)
            login = ligne[0]
            time = ligne[-1]
            m1 = re.match(r"\((\d\d):(\d\d)\)", time)
            time = int(m1.group(1)) * 60 + int(m1.group(2))
            print(time)
            if login in dico :
                liste = dico[login]
                liste[0] += 1
                liste[1] += time
            else :
                dico[login] = [1, time]
    
    sorted(dico)
    
    for item in dico.items()
        login = item[0]
        nbConn = item[1][0]
        time = item[1][1]
        heure  = time // 60
        minute = time % 60
        if heure < 10 :
            heure = "0" + st(heure)
        if minute < 10 :
            minute = "0" + str(minute)
        print(login + " : nombre de connexions = " + str(nbConn) + " & tume = " + str(heure) + "h" + str(minute) + "m")

Python : Séance 5 :

Les fichiers séquentiels :

Trois types de fichiers :

  • Text Files : texte - code ASCII

  • Raw Binary Files : format "mémoire" - lecture/écriture totalement libre

  • Buffered Binary Files : format "mémoire" - lecture/écriture via buffer

Traitement en trois étapes :

  • Ouverture :

    f1 = open(filename,mode)

    f1 est un objet de type file.

    Ouvre le fichier et place la position courante en début de fichier.

    mode Meaning
    'r' open for reading (default)
    'w' open for writing, truncating the file first
    'r+' open for both reading and writing
    'a' open for writing, appending to the end of the file if exists
    'b' binary mode, appended to other mode if necessary
    't' text mode (default)
  • Lectures et/ou écritures :

    payload = f.read() : payload est de type str et renvoie la fin du fichier depuis la position courante. Il faut être certain d'avoir assez de mémoire.

    >>>f.read()
    'This is the entire file.\n'
    >>>f.read() # EOF
    ''

    payload = f.read(size) : payload est de type str et size est un int. Ça renvoie au plus size bytes lus depuis la position courante et avance la position courante.

    payload = f.readline() : payload est de type str et ça renvoie la fin de la ligne depuis la position courante. Il faut être certain d'avoir assez de mémoire (Ok dans un grand nombre de cas).

    >>>f.readline()
    'This is the first line of the file.\n'
    >>>f.readline()
    'Second line of the file\n'
    >>>f.readline() # EOF
    ''

    Lecture avec boucle :

    >>>for line in f :
    ... print(line, end='') # default : end='\n'
    ...
    This is the fist line of the file.
    Second line of the file

    Écriture + conversion en string :

    f.write(str) écrit un str → conversion préalable si nécessaire

    >>>value = ('the answer', 42)
    # convert the tuple to string
    >>>s = str(value)
    >>>f.write(s)
    18 # nb of caracters written
  • Fermeture :

    f.close() ferme le fichier et libère les ressources.

    >>>f.close()
    >>>f.read()
    Traceback (most recent call last) : File
    "", line 1, in ? ValueError : I/O
    operation on closed file

Mot clé "with" :

Bonne pratique : utilisation du mot clé "with" avec les fichiers car il ferme automatiquement le fichier à la fin du bloc (en cas de sortie normale ou de sortie abrupte) et est l'équivalent du try-with-resources statement de Java.

>>>with open('workfile', 'r') as f :
... read_data = f.read()
>>> f.closed
True

Pour revenir en arrière :

Deux étapes :

  • Faire une lecture séquentielle + sauvegarder l'id des endroits où on voudra revenir en arrière.

    f.tell()
  • Revenir en arrière aux endroits voulus.

    f.seek(id)

f.tell() renvoie un int représentant la position courante dans le fichier. Attention, ce int est un nombre opaque dépendant de l'implémentation de Python. Ce n'est pas spécialement le nombre de bytes lus depuis le début du fichier !

f.seek(int) : int est l'id d'une position courante dans le fichier. Cette méthode positionne le curseur du fichier à l'endroit portant l'id fourni par l'int.

>>>with open('out', 'r') as file :
... file.readline()
... file.readline()
... id = file.tell()
... l1 = file.readline()
... print(l1) # impression ligne 3 du fichier
... file.seek(id)
... l1 = file.readline()
... print(l1) # nouvelle impression ligne 3

Paramètres en ligne de commande :

Il faut importer le module sys dans un script

La liste sys.argv contient : le nom du script à l'indice 0 et les noms des paramètres présents sur la ligne de commande lors de l'exécution du script, aux indices suivants.

Exemple :

>>> monScript toto 52

monScript.py

import sys
...
print(sys.argv[0]) # 'monScript'
print(sys.argv[1]) # 'toto'
print(sys.argv[2]) # '52'
...

Obtenir la taille d'un fichier :

import os
infoFichier = os.stat(filename)
tailleFichier = infoFichier.st_size

https://docs.python.org/3.0/library/stat.html

Exercices :

Les fichiers que vous allez traiter dans les exercices suivants pourraient éventuellement être très volumineux. C'est pourquoi vous ne pouvez pas les stocker entièrement en mémoire RAM.

  1. Nous allons construire une version simplifiée de la commande Unix "cat"/ Nous vous demandons de construire un programme qui prend en paramètre le nom d'un fichier texte et qui affiche le contenu de celui-ci à l'écran.

    #!/usr/bin/python3
    
    import sys
    
    nomFichier = sys.argv[1]
    
    with open(nomFichier, 'r') as f :
        for line in f :
            print(line, end = '')
  2. Nous allons construire une version simplifiée de la commande Unix "head". Nous vous demandons de construire un programme qui prend en paramètres le nom d'un fichier texte "f" et un nombre entier naturel "n". Si le fichier contient au moins "n" ligne(s), votre programme affichera les "n" première(s) ligne(s) de "f". Sinon, votre programme affichera tout le contenu de "f".

    #!/usr/bin/python3
    
    import sys
    
    nomFichier = sys.argv[1]
    n = int(sys.argv[2])
    
    with open(nomFichier, 'r') as f :
        for i in range(n) :
            print(f.readline(), end = '')
  3. Nous allons construire une version simplifiée de la commande Unix "tail". Nous vous demandons de construire un programme qui prend en paramètres le nom d'un fichier texte "f" et un nombre entier naturel "n". Si le fichier contient au moins "n" ligne(s), votre programme affichera les "n" dernière(s) ligne(s) de "f". Sinon, votre programme affichera tout le contenu de "f".

    #!/usr/bin/python3
    
    import sys
    
    nomFichier = sys.argv[1]
    n = int(sys.argv[2])
    liste = []
    
    with open(nomFichier, 'r') as f :
        for ligne in f :
            liste.append(ligne)
    
    if len(liste) > n :
        for i in range(len(liste) - n, len(liste)) :
            print(liste[i])
    else :
        for l in liste :
            print(l)
  4. Nous allons construire une version simplifiée de la commande Unix "wc". Nous vous demandons de construire un programme qui prend en paramètres le nom d'un fichier texte "f" et qui affiche le nombre total de caractères (imprimables ou non), le nombre de mots et le nombre de lignes de "f".

    #!/usr/bin/python3
    
    import sys
    import re
    
    nomFichier = sys.argv[1]
    nbLignes = 0
    nbCaracteres = 0
    nbMots = 0
    
    with open(nomFichier, 'r') as f :
        for ligne in f :
            nbLignes += 1
            nbCaracteres += len(ligne)
            nbMots += len(re.findall("[a-zA-Z]", ligne))
    
    print("nbLigns = " + str(nbLignes))
    print("nbCaracteres = " + str(nbCaracteres))
    print("nbMots = " + str(nbMots))
  5. Nous allons effectuer la fusion de deux fichiers qui contiennent chacun une suite d'entiers triée.

    Une suite de "n" entiers(s) <x_1, X_2, ..., x_n> est persistée dans un fichier texte de "n" lignes en écrivant x_1 sur la première ligne du fichier, x_2 sur la deuxième, etc.

    Nous vous demandons de construire un programme qui prene en paramètres trois noms de fichiers textes. Les deux premiers fichiers contiendront chacun une suitetriée. Voyre programme produira une troisième suite triée qui correspondra à la fusion des deux suites précédentes. Cette troisième suite sera sauvée dans le fichier nommé suivant le troisième paramètre de votre programme.

    Par exemple, si les deux premières suites sont <1, 23, 47> et <0, 4, 25, 26, 27>, votre programme produira et sauvera la suite <0, 1, 4, 23, 25, 26, 27, 47>. Les éventuels doublons sont inclus dans la suite produite.

    #!/usr/bin/python3
    
    import sys
    import re
    
    def ajoutListe(nomFichier, liste) :
        with open(nomFichier 'r') as f :
            for ligne in f :
                l = re.split(', ', ligne.strip('<>'))
                for i in l :
                    liste.append(int(i))
        return liste
    
    fichier1 = sys.argv[1]
    fichier2 = sys.argv[2]
    fichier3 = sys.argv[3]
    liste = []
    liste = ajoutListe(fichier1, liste)
    liste = ajoutListe(fichier2, liste)
    liste.sort()
    
    with open (fichier3, 'w') as f :
        s = '<'
        i = O
        for item in liste :
            s += str(item)
            if i < len(liste) - 1 :
                s += ', '
            i += 1
        s ++ '>'
        f.write(s)
  6. Nous allons produire quelques statistiques concernant un "fichier de logs" d'un routeur. Le format de ce fichier est simple. Il contient une ligne par message qui a transité sur le routeur. Chaque ligne est composée de l'adresse IP de la source du message, l'adresse IP de la destination du message, et la taille du message en nombre de bytes. De plus, le fichier de logs a été préalablement trié sur base des adresses sources et ensuite, pour les messages autant la même adresse source, sur base des adresses de destination.

    Nous vous demandons de construire un programme qui prend en paramètres le nom d'un fichier de logs et qui affiche à l'écran, pour chaque couple d'adresses source et destination présent dans le fichier de logs, la somme totale des tailles des messages qui ont transité par le routeur.

    Par exemple, si votre programme traite le fichier suivant :

    192.168.0.3 192.168.0.4 1024
    192.168.0.3 192.168.0.4 1024
    192.168.0.3 192.168.0.5 512
    192.168.0.4 192.168.0.6 512
    192.168.0.4 192.168.0.6 512
    192.168.0.4 192.168.0.6 1024

    Il affichera les lignes suivantes :

    192.168.0.3 192.168.0.4 2048
    192.168.0.3 192.168.0.5 512
    192.168.0.4 192.168.0.6 2048
    #!/usr/bin/python3
    
    import re
    import sys
    
    nomFichier = sys.argv[1]
    dico = {}
    
    with open(nomFichier, 'r') as f :
        for ligne in f :
            liste = re.split('\s', ligne)
            cle = (liste[0], liste[1])
            valeur = int(liste[2])
            
            if cle in dico :
                dico[cle] += valeur
            else :
                dico[cle] = valeur
    
    sorted(dico.items(), key = lambda x : x[0][0])
    for item in dico.items() :
        print(item[0][0] + ' ' + item[0][1] + ' ' + str(item[1]))
  7. Cet exercice est une extension de l'exercice précédent. Pour chaque routeur source, affichez aussi la somme totale des tailles de tous les messages qui ont été envoyés par ce routeur. De plus, affichez, en dernier, la somme totale des tailles de tous les messages qui ont transités par le routeur. L'affichage se fera de la même manière que dans l'exemple ci-dessous. Si votre programme traite le même fichier que dans l'exercice précédent, il affichera les lignes suivantes :

    >>>>> 192.168.0.3 192.168.0.4 2048
    >>>>> 192.168.0.3 192.168.0.5 512
    --> 192.168.0.3 2560
    >>>>> 192.168.0.4 192.168.0.6 2048
    --> 192.168.0.4 2048
    4608

Python : Programmation Orientée Objet :

Ce qui va suivre provient du cours d'OpenClassRooms "Apprenez la programmtion orientée objet avec Python" !

Comme dans la plupart des langages de programmation, chaque déclaration de la classe commence avec le mot-clé class suivi du nom de cette classe.

Voici un exemple :

class Rectangle :
    width = 3
    height = 2

    def calculate_area(self) :
        return self.width * self.height

Dans l'exemple ci-desus, notre classe s'appelle Rectangle. Les noms de classe, par convention, commencent par une majuscule. S'il y a plusieurs mots dans le no d'une classe, on utilise générlement le CapitalizedCase (la différence avec le kamelCase est que la première lettre est également en majuscule), en mettant une majuscule à la première lettre de chaque mot.

Toutes les variables et méthodes qui composent cette classe (ses "attributs") sont définies en dessous dans sa portée ("scope", en anglais), avec une identation supplémentaire de 4 espaces (pas de TAB).

Quand on déclare une méthode, on utilise le mot-clé def - exactement comme pour les fonctons. Les méthodes d'instance incluent toujours "self" en premier paramètre. Ce paramètre référence l'objet lui-même, et il permet d'accéder à ses autres attributs.

Note : en programmation orientée objet, il existe trois types d'attributs :

  • les attributs d'instance (propres aux instances créées)

    Ils sont des variables définies à l'aide de self. Elles sont relatives à l'instance, et ne peuvent être accédées sans instanciation. Dans le cadre des méthodes, ce sont les méthodes classiques d'une classe, qui possèdent self en premier paramètre.

  • les attributs de classe (propres à la classe, et partagés entre les instances)

    Ils sont des variables définies directement dans le corps de la classe. Elles peuvent être accédées pae la classe, sans passer par l'instanciation. Les attributs de classe peuvent se référencer entre eux, mais ne peuvent pas accéder aux attributs d'instance.

    Dans le cadre des méthodes, elles seront précédées par un @classmethod, et leur premier paramètre sera cls (à la place de self).

    Information : les attributs de classe sont souvent utilisés pour créer des données ou des actions globales à la classe, qui ne dépendent pas d'une instance. Les instances peuvent cependant y accéder. Modifier un attribut de classe dans une instance le modifiera dans toutes les autres !

  • les attributs statiques (qui sont presque indépendants de la classe)

    Ils sont des attributs qui n'ont pratiquement aucun lien avec la classe. Seules les méthodes peuvent être statiques, et l'ajout parr rapport aux attributs de classe est minime : on n'a plus besoin de spécifier le paramètre cls. Pour créer un attribut statique, il suffit de faire précéder la méthode par @staticmethod.

Attention : je vous recommande de privilégier les attributs d'instance qui permettent d'utiliser la programmation orientée objet à son plein potentiel. Les attributs de classe peuvent être très utiles, mais sont censés être plus rares. Enfin, les attributs statiques sont à l'opposé du paradigme orienté objet : évitez de les utiliser, quitte à utiliser un attribut de classe à la place.

Si chaque type d'attribut possède une utilité propre, essayez autant que possible de privilégier les attributs d'instance, qui permettent d'utiliser la programmation orientée objet à son plein potentiel.

Enfin, vous avez les variables - elles permettent de définir des valeurs par défaut à nos objets. Ici, nous avons défini des attributs dits "de classe", et ces variables seront partagées par toutes les autres instances de la classe Rectangle.

Attention : Contrairement à certains autres langages de programmation, avec Python il n'est pas nécessaire de prédéfinir les variables existant dans votre classe, ou leur type, à l'avance.

Note sur le paramètre self : le nom "self" n'est qu'une convention : on pourrait tr!s bien le renommer en "instance_actuelle" ou "this", ou ecore "toto". Votre code fonctionnerait aussi bien. C'est cependant une convention très forte en Python, donc prenez-la au mot.

Il existe une catégorie spéciale de méthode nommée constructeur. Chaque classe en a un, et il est utilisé pour créer des objets à partir de cette classe. En Python, notre constructeur est une méthode magique nommée __init__, que l'on peut utiliser un peu comme ceci :

class Rectangle :
    def __init__(self, length, width, color="red") :
        self.length = length
        self.width = width
        self.color = color

Une méthode "magique" parce qu'elle modifie le comportement interne de la classe. En Python, il existe plusieurs méthodes magiques, qui comment et se terminent par deux underscores (ou "dunders").

Les constructeurs vous permettent de passer des valeurs initiales à un objet en cours de création. En définissant des valeurs plutôt dans le constructeur que dans la classe, nous pouvons initialiser des objets avec différentes longueurs et largeurs. De plus, les attributs définis dans la méthode magique __init__ ne seront pas partagés par les autres instances. Ce sont des attributs d'instance, qui seront propres à chaque objet créé.

Le color="red" est un raccourci très pratique - vous pouvez appeler ce constructeur en définissant ou non une couleur. Si vous ne spécifiez pas de couleur, la valeur ("red", soit rouge) sera utilisée par dédfaut. Vous pouvez créer des "paramètres optionnels" dans toute méthode ou fonction. Assuez-vous simplement que vos paramètres optionnels se trouvent après les autres paramètres dans la liste.

La plupart des classes auront besoin d'un constructeur. Si vous n'en fournissez pas, Python utilisera - ce qui est commode - un constructeur par défaut qui ne fait rien. Note : ce constructeur par défaut fait partie de la classe "objet" dont toutes les classes héritent, mais nous y reviendrons plus tard.

Vous ne savez pas encore comment implémenter certaines méthodes. Ce n'est pas grave, laissez simplement le corps de la méthode vide. En Python, nous pouvons utiliser pour cela le mot-clé pass, comme ceci :

def empty_method(self, length) :
    pass

Nous utilisons ces constructeurs pour créer des objets d'une classe spécifique. Par exemple, pour créer un Rectangle de longueur 5 et de largeur 3 :

rectangle = Rectangle(5, 3)

Attention : il est important que les paramètres que vous fournissez correspondent aux paramètres du constructeur, c'est-à-dire que ça inclut les positions des paramètres dans le constructeur.

Lorsque nous instancions un objet, nous devons habituellement l'affecter à une variable pour pouvoir l'utiliser.

Dans l'exemple précédent, le rectanle de dimension 5 sur 3 est rouge car "rouge" est la valeur par défaut du paramètre optionnel du constructeur "color". On peut également utiliser l'une de ces deux options pour spécifier une couleur :

rect1 = Rectangle(1, 2, "blue")
rect2 = Rectangle(3, 1, color="pink")

La deuxième option est plus explicite, et moins susceptible de prêter à confusion si vous avez plusieurs paramètres optionnels.

Notez que vous pouvez aussi nommer les paramètres obligatoires (rectangle = Rectangle(length=2, width=3, color="white")). Le nommahe des paramètres peut être une grande aide à la lecture du code, pensez-y !

Nous pouvons accéder aux attributs d'un objet et leur affecter des valeurs comme ceci :

rectangle = Rectangle(5, 3)
print(rectangle.length)
rectangle.color = "yellow"

Les attributs rectangle.length et rectangle.color fonctionnent ici exactement comme d'autres variables le feraient - ils peuvent faire partie d'expressions ou de calculs, être passés comme paramètres à des fonctions/méthodes, et être modifiés. Le grand intérêt des objets est que leurs valeurs sont stockées en eux. Vous pouvez donc passer le rectangle de fonction fonction en modifiant ses attributs, il gardera en mémoire ses modifications sur le temps.

Nous pouvons également assigner des variables aux retours de méthodes, comme les fonctions :

area = rectangle.calculate_area()
print(area)

Python s'occupe de l'argument self de la méthode calculate_area() pour nous ! Comme self fait simplement référence à cet objet, Python sait déjà sur quel objet vous invoquez la méthode. En fait, lors de l'appel d'une méthode d'instance, Python passe l'instance rectangle en tant que premier argument "self" à la classe Rectangle, sans crier garde ! Faire rectangle.calculate_area() revient à faire Rectangle.calculate_area(rectangle) ! Vous avouerez que la première version est plus élégante et compréhensible.

Comme dit précédemment, les variables et les méthodes sont des attributs de classe. Donc, il existe également une méthode magique qui récupère la valeur de l'attribut, __getattribute__(name_of_attribute).

rectangle = Rectangle(5, 3)
color = rectangle.__getattribute__('color')
print(color)
calculate_area = rectangle.__getattribute__('calculate_area')
print(calculate_area())

# revient à la même chose que :

rectangle = Rectangle(5, 3)
print(rectangle.color)
print(rectangle.calculate_area())

L'une des choses les plus utiles de la programmation orientée objet, c'est le cconcept d'heritage. L'héritage vous permet de créer une sous-classe (ou classe enfant) d'une classe parent, qui contient les attributs du parent ansi que d'autres attributs spécifiques à l'enfant.

Nous utilisons l'héritage en programmation orientée objet pour plusieurs raisons différentes, mais liées entre elles :

  1. La raison la plus simple est la même que celle celle pour laquelle nous écrivons des fonctions - la réutilisabilité.

  2. L'héritage permet également l'extensibilité - c'est-à-dire la possibilité d'étendre la fonctionnalité d'un programme sans avoir à modifier le code existant.

  3. Enfin, l'un des avantages de l'héritage concerne la façon dont nous concevons les systèmes. L'héritage , et les classes en général, nous permettent d'exprimer des relations entre différentes parties de notre code.

    Tout comme nous regroupons des données et du code au sein d'un paquet en utilisant les classes, nous relions les classes qui sont conceptuellement apparentées grâce à l'héritage.

    Les classes et l'héritage sont utiles, car ils rendent les systèmes plus faciles à comprendre. Cela permet aux développeurs et aux équipes de développement de construire les modèles conceptuels des comportements et des données dans le système - ce qui prend encore plus d'importance lors de la maintenance et de l'extension de projets vastes et complexes.

    L'élément principal de syntaxe utilisé pour définir les sous-classes se trouve dans la définition de classe elle-même : FilmCassette(Film). Le parent de la classe est placé entre parenthèses après le nom de la classe dans la toute première ligne de la classe !

    Le principal intérêt de l'héritage est d'étendre notre classe de base, en lui ajoutant des attributs !

    En cas d'héritage multiple - sujet que nous aborderons brièvement plus loin - les classes parents sont séparées par des parenthèses, de façon similaire à celle dont fonctionnent les paramètres de fonctions, comme ceci :class SousClasse(ParentUn, ParentDeux, ...).

    Si vous ne spécifiez pas de classe parent, vous dites en fait à Python de faire de la classe une sous-classe d'Object. Object constitue la base de toutes les classes, ce qui peut prêter à confusion. Vous vous souvenez que toutes les classes possèdent un constructeur sans argument par défaut ? C'est parce qu'elles héritent d'Object.

La surcharge désigne le concept selon lequel une classe enfant crée une nouvelle implémentation d'une méthode héritée. Lorsqu'une méthode dans une classe enfant est créée avec le même nom et la même signature (nom et liste de paramètres) qu'une méthode dans le parent, la méthode enfant l'emporte.

ABC signifie "Abstract Base Class", ou "classe de base abstraite". Il s'agit du mécanisme utilisé par Python pour implémenter ce que l'on nomme une classe abstraite.

Une classe abstraite est une classe qui ne peut pas être instanciée - la seule façon de l'utiliser est de créer une sous-classe. Une classe abstraite peut aussi insister pour qu'une méthode soit implémentée par ses enfants. Essayez de trouver le décorateur @abstractmethod dans la documentation Pythonn.

Voici un exemple :

from abc import ABC # permet de définir des classes de base

class Shape(ABC) :
    def area(self) :
        return 0
    
class Square(Shape) :
    def __init__(self, length) :
        self.length = length
    
    def area(self) :
        return length * length

Bien que la surcharge nous permette de modifier entièrement des comportements hérités, il peut parfois êrtre utile d'avoir accès au code des méthodes des classes parents, depuis les classes enfants.

L'un des emplacements les plus courants pour cette utilisation se trouve dans les constructeurs. Pour cela, nous utilisons super() - comme ceci :

class Drink :
    """Une boisson."

    def __init__(self, price) :
        """Initialise un prix."""
        self.pricee = price
    
    def drink(self) :
        """Boire la boisson."""
        print("Je ne sais pas ce que c'est, mais je le bois.")
    
class Coffee(Drink) :
    """Café."""
    
    prices = {"simple": 1, "serré": 1, "allongé": 1.5}
    
    def __init__(self, type) :
        """Initialise le type du café."""
        self.type = type
        super().__init__(price=self.prices.get(type, 1))
    
    def drink(self) :
        """Boire le café."""
        print("Un bon café pour me réveiller !")

L'approche avec super() vous permet de réutiliser le code plutôt que de le copier, et assure le regroupement des fonctionnalit&s de façon logique - soit deux des plus grands avantages de la programmation orientée objet !

Si une classe peut avoir une classe parent, alors logiquement cette classe parent peut également avoir un parent ! C'est une hiérarchie d'héritage qui émerge de cela, car il y a de multiples niveaux d'héritage. En haut de la hiérarchie, on trouve la classe de base.

Comme toutes les classes héritent d'Object en Python, elles auront une classe de base commune. Néanmois, on ne fait généralement pas figurer Object sur les digrammes d'héritage, car il n'est pas très utile de l'inclure.

L'héritage multiple suppose qu'une classe ait de multiples calasses parents. Dans une hiérarchie d'héritahe, il y a plusieurs niveaux d'héritage - une classe a un parent qui a un parent.

L'héritage multiple a mauvaise réputation en programmation orientée objet - les systèmes qui utilisent l'héritahe multiple peuvent être difficiles à comprendre. De plus, certains langages de programmation proposent des implémentations médiocres d'héritages multiples qui provoquent des problèmes.

Le plus souvent, l'héritage multiple n'est pas une solution à votre problème, mais il n'en reste pas moins qu'il existe quelques situations oµ il représente la meilleure solution possible. En tout cas, comme vous risquez de rencontrer l'héritage multiple dans votre activité professionnelle, nous le traiterons donc ici.

Le modèle que nous avons utilisé ici est souvent nommé un Mixin - nommé ainsi car vous ajoutez ("mix in") une fonctionnalité nécessaire. Si vous avez programmé en d'autres langages, vous connaissez peut-être les interfaces, traits, ou classes de types - qui sont tous des concepts similaires, mais distincts.

Le problème du diamant (ou le "Deadly Diamond of Death", soit littéralement le "diamant de la mort qui tue") est l'un des problèmes principaux de l'héritage multiple qui se passe quand deux classes parents implémentent la même méthode.

Les énumérations :

Ce qui va suivre a été copié de la documentation sur les énumérations de Python 3.7. !

Une énumération est un ensemble de noms symboliques, appelés membres, liés à des valeurs constantes et uniques. Au sein d'une énumération, les membres peuvent être comparés entre eux et il est possible d'itérer sur l'énumération elle-même.

Contenu du module :

Ce mdule définit quatre classes d'énumération qui permettent de définir des ensembles uniques de noms et de valeurs : Enum, IntEnum, Flag et IntFlag. Il fournit également un décorateur, unique(), ainsi qu'une classe utilitaire, auto.

class enum.Enum

Classe de base pour créer une énumération de constantes.

class enum.IntEnum

Classe de base pour créer une énumération de constantes qui sont également des sous-classes de int.

class enum.IntFlag

Classe de base pour créer une énumération de constantes pouvant être combinées avec des opérateurs de comparaison bit-à-bit, sans perdre leur qualité de IntFlag. Les membres de IntFlag sont aussi des sous-classes de int.

class enum.Flag

Classe de base pour créer une énumération de constantes pouvant être combinées avec des opérateurs de comparaison bit-à-bit, sans perdre leur qualité de Flag.

enum.unique()

Décorateur de classe qui garantit qu'une valeur ne puisse être associée qu'à un seul nom.

class enum.auto

Les instances sont remplacées par une valeur appropriée pour les membres Enum. La valeur initiale commence à 1.

Création d'une Enum :

Une énumération est créée comme une class, ce qui la rend facile à lire et à écrire. Une autre méthode de création est décrite dans API par fonction. Pour définir une énumération, il faut hériter de Enum de la manière suivante :

>>>from enum import Enum
>>>class Color (Enum) :
... RED = 1
... GREEN = 2
... BLUE = 3
...

Note : La valeur d'un membre peut être de n'importe quel type : int, str, etc. Si la valeur exacte n'a pas d'importance, utilisez des instances de auto et une valeur appropriée sera choisie pour vous. Soyez vigilant si vous mélangez auto avec d'autres valeurs.

Note : Nomenclature :

  • La classe Color est une énumération (ou un enum).

  • Les attributs Color.RED, Color.GREEN, etc., sont les membres de l'énumération (ou les membres de l'enum) et sont fonctionnellement des constantes.

  • Les membres de l'enum ont chacun un nom et une valeur; le nom de Color.RED est RED, la valeur de COLOR.BLUE est 3, etc.

Note : Même si on utilise la syntaxe en class pour créer des énumérations, les Enums ne sont pas des vraies classes Python. En quoi les Enums sont différentes des vraies classes Python ?

Les membres d'une énumération ont une représentation en chaîne de caractères compréhensible par un humain :

>>>print(Color.RED)
Color.RED

... tandis que leur repr contient plus d'informations :

>>>print(repr(Color.RED))
<Color.RED: 1>

Le type d'un membre est l'énumération auquel ce membre appartient :

>>>type(Color.RED)
<enum 'Color'>
>>>isinstance(Color.GREEN, Color)
True
>>>

Les membres ont également un attribut qui contient leur nom :

>>>print(Color.RED.name)
RED

Les énumérations sont itérables, l'ordre d'itération est celui dans lequel les membres sont déclarés :

>>>class Shake(Enum) :
... VANILLA = 7
... CHOCOLATE = 4
... COOKIES = 9
... MINT = 3
...
>>>for shake in Shake :
... print(shake)
...
Shake.VANILLA
Shake.CHOCOLATE
Shake.COOKIES
Shake.MINT

Les membres d'une énumération sont hachables, ils peuvent ainsi être utilisés dans des dictionnaires ou des ensembles :

>>>apples = {}
>>>apples[Color.RED] = 'red delicious'
>>>apples[Color.GREEN] = 'granny smith'
>>>apples == {Color.RED : 'red delicious', Color.GREEN : 'granny smith'}
True

Accès dynamique aux membres et à leurs attributs :

Il est parfois utile de pouvoir accéder dynamiquement aux membres d'une énumération (par exemple dans des situations où ils ne suffit pas d'utiliser Color.RED car la couleur précise n'est pas connue à l'écriture du programme). Enum permet de tels accès :

>>>Color(1)
<Color.RED: 1>
>>>Color(3)
<Color.BLUE: 3>

Pour accéder aux membres par leur nom, utilisez l'accès par indexation :

>>>Color['RED']
<Color.RED: 1>
>>>Color['GREEN']
<Color.GREEN: 2>

Pour obtenir l'attribut name ou value d'un membre :

>>>member = Color.RED
>>>member.name
'RED'
>>>member.value
1

Duplications de membres et de valeurs :

Il n'est pas possible d'avoir deux membres du même nom dans un enum :

>>>class Shape(Enum) :
... SQUARE = 2
... SQUARE = 3
...
Traceback (most recent call last):
...
TypeError: Attempted to reuse key: 'SQUARE'

Cependant, deux membres peuvent avoir la même valeur. Si deux membres A et B ont la même valeur (et que A est défini en premier), B sera un alias de A. Un accès par valeur avec la valeur commune à A et B renverra A. Un accès à B par nom renverra aussi A :

>>>class Shape(Enum) :
... SQUARE = 2
... DIAMOND = 1
... CIRCLE = 3
... ALIAS_FOR_SQUARE = 2
...
>>>Shape.SQUARE
<Shape.SQUARE: 2>
>>>Shape.ALIAS_FOR_SQUARE
<Shape.SQUARE: 2>
>>>Shape(2)
<Shape.SQUARE: 2>

Note : Il est interdit de créer un memvre avec le même nom q'un attribut déjà défini (un autre membre, une méthode, etc.) ou de créer un attribut avec le nom d'un membre.

Le reste de la documentation sur les Enums en Python.