Programmation système en Python

Modules

Un module est un fichier Python contenant des fonctions et des constantes.

  • import <module> : sys.path est parcouru pour trouver un fichier de nom <module>.py, ce module est chargé, puis compilé en bytecode, puis exécuté. L’exécution du code peut être invisible (définition de classes, de fonctions), ou pas. Le code n’est exécuté qu’une seule fois, même si le module est importé plusieurs fois
  • __name__ : nom du module courant s’il est importé, ou __main__ s’il est exécuté directement
  • __file__ : chemin absolu vers le fichier/module courant. Utile dans os.path.join(os.path.dirname(__file__), 'file.local')
  • python -m <module_name> : équivalent à l’exécution de import <module_name> (= chargement + exécution)
    • python -m pip : pip
    • python -m venv : virtualenv
    • python -m http.server : serveur http pour fichiers statiques
  • import <module> : les constantes et fonctions doivent être précédés de <module>.
  • from <module> import <func>, <const> : les fonctions et constantes n’ont pas besoin d’être préfixées
Exemple:
# mymath.py
PI=3.1415

def square(x):
    return x*x

print('*** mymath loaded')
$ python
> import mymath
*** mymath loaded
> mymath.PI
3.14159
> mymath.square(4)
16
$ python
> from mymath import square
> square(4)
16

Packages

Un package est un ensemble de modules organisé hiérarchiquement. C’est un répertoire, qui contient un fichier __init.py__ pour le distinguer d’un simple répertoire contenant des fichiers python.

  • from <package> import <module>
  • from <package>.<module> import <func>, <const>

Scripts

  • shebang à mettre en en-tête des scripts : #!/usr/bin/env python3 ; la commande python3 est alors cherchée dans le $PATH de l’utilisateur
  • ajouter aussi : # -*- coding: utf-8 -*- pour éviter les problèmes d’encodage de caractères

Module os

Environnement

  • os.name : nom du système d’exploitation, parmi posix (Linux, MacOS, BSD), ‘nt’ (Windows), ‘java’ (Android)
  • os.uname() : renvoit un objet avec 5 attributs d’infos détaillées sur l’OS
  • os.environ: mapping de toutes les variables d’environnement (au moment de l’import)
  • os.getenv(<varname>, <default> : donne la valeur d’une variable d’environnement, avec une valeur par défault si elle n’existe pas

Système de fichiers

  • os.getcwd() : renvoie le chemin d’accès courant
  • os.walk(DIR) : générateur qui parcours récursivement les fichiers et répertoires de DIR; à chaque répertoire (y compris DIR) un triplet (dirpath, dirnames, filenames) est généré
  • os.remove(FILEPATH) : supprime le fichier de chemin d’accès FILEPATH
  • os.rmdir(DIRPATH) : supprime le répertoire (qui doit être vide) de chemin d’accès DIRPATH

os.path

  • os.path.join(PATH1, PATH2) : renvoie un chemin d’accès, concaténation de PATH1 et PATH2
  • os.path.exists(PATH) : renvoie True s’il existe un fichier ou un répertoire de chemin d’accès PATH
  • os.path.getsize(FILEPATH) : renvoie la taille en octet du fichier FILEPATH
  • os.path.samefile(PATH1, PATH2) : True si les deux chemins d’accès font référence au même fichier (même i-node)

Module sys

  • sys.path : liste des répertoires ou des .zip où python cherche les modules. Cette liste est augmenté des répertoires de la variable d’env. PYTHONPATH si celle-ci est définie
  • sys.modules : dictionnaire de tous les modules chargés
  • sys.argv : liste des arguments lors d’une exécution en ligne de commande
  • sys.version_info : 5-uple qui décrit la version de l’interpréteur Python utilisé. Les deux premiers sont les numéros de version major et minor, par exemple 2 & 7, ou 3 & 6

Module urllib.parse (package urllib)

  • urllib.parse.urlsplit(url) : décompose une url en ses constituants (addressing scheme, network location, path, query, fragment identifier)
  • urllib.parse.urlunsplit(parts) : opération inverse de urlsplit
  • urllib.parse.urljoin(base, url) : construit une url absolue en combinant une url base avec une autre url url. Si url est incomplète, ses éléments manquants sont pris dans base.

Exemple d’une commande Unix écrite en Python

Le script suivant supprime tous les fichiers .pyc à partir d’un répertoire donné et à tous les niveaux de profondeur.

cleanpyc.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os, sys

def clean(directory_path):
    for root, _, filenames in os.walk(directory_path):
        for filename in filenames:
            if filename.endswith('.pyc'):
                os.remove(os.path.join(root, filename))

def main():
    clean(sys.argv[1])

if __name__ == '__main__':
    main()

Il peut être utilisé :

  • comme un script : ./cleanpyc.py <dir>
  • comme une commande Unix: il faut créer un lien symbolique de /usr/local/bin/cleanpyc par exemple, vers le fichier cleanpyc.py
  • comme un module exécutable : python -m cleanpyc <dir>
  • comme une fonction de librairie à importer dans un programme :
import ./cleanpyc
...
cleanpyc.clean(os.getenv('HOME'))
...

argparse : extraction des arguments et des options

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import argparse

def clean(directory_path, suffix):
    for root, _, filenames in os.walk(directory_path):
        for filename in filenames:
            if filename.endswith(suffix):
                os.remove(os.path.join(root, filename))

def main():
    # build an empty parser
    parser = argparse.ArgumentParser()

    # define arguments
    parser.add_argument("dir", type=str, help="directory path")
    parser.add_argument("-s", "--suffix", type=str, default=".pyc", help="suffix of to be deleted files")

    # instruct parser to parse command line arguments
    args = parser.parse_args()

    clean(args.dir, args.suffix)

if __name__ == '__main__':
    main()

Exercices à réaliser - consignes

  • il y a 3 exercices : strextract, brklink, ciqual. Ils doivent être validés obligatoirement au travers de GitLab
  • il faut créer un seul projet Gitlab de titre “Python Scripting” et créer une issue (et donc une MR) par exercice
  • le travail est à terminer obligatoirement à la fin de la période tampon
  • le code doit être écrit selon les (bonnes) pratiques suivantes :
    • les noms de variables doivent être choisis en anglais et permettre de deviner leur type et leur sens
    • les noms de méthodes doivent être choisis en anglais et permettre de deviner leur sens et le type de la valeur renvoyée le cas échéant
    • les exercices strextract et brklink peuvent et doivent être réalisés en moins de 50 et 100 lignes de code respectivement

Ressources à utiliser

strextract

Mots-clés, commandes : argparse, /usr/local/bin, re, walk

Ecrire un programme Python 3 qui parcourt récursivement tous les fichiers d’une hiérarchie (certains pourront être omis selon les options) et produit en sortie toutes les chaînes littérales (= les textes entourés par " ou ’) que contiennent ces fichiers.

Les chaînes seront affichées sous la forme où elles étaient dans le fichier, entourées des même symboles " ou ’.

Par exemple, si le contenu d’un des fichiers parcouru est:

<v-text-field
   label="Nom d'utilisateur"
   v-model='user.username'
   :disabled="userId > 0" :error-messages="usernameErrors"
   @input="$v.user.username.$touch(); $v.user.$touch()"
></v-text-field>

Ce fichier conduira à la production des lignes suivantes sur la sortie standard stdout :

"Nom d'utilisateur"
'user.username'
"userId > 0"
"usernameErrors"
"$v.user.username.$touch(); $v.user.$touch()"

On ne cherchera pas à capturer les chaînes multi-lignes.

strextract [options] <dir>
  • Le seul argument obligatoire de la commande est dir, chemin d’accès au répertoire à partir duquel est faite la recherche
  • Les options de la commande sont :
    • -h, --help : affiche l’usage de la commande
    • --suffix=<suffix> : limite la recherche aux fichiers de suffixe <suffix>
    • --path : chaque ligne produite est précédée du chemin d’accès au fichier associé suivi d’une tabulation
    • -a, --all: les fichiers cachés (ceux dont le nom commence par ‘.’) sont également inclus
  • Le programme doit être installé comme une commande Unix accessible à tous les utilisateurs
Exemple:
$ strextract --path --suffix vue .
...
/home/chris/myproject/User.vue    "Nom d'utilisateur"
/home/chris/myproject/User.vue    'user.username'
/home/chris/myproject/User.vue    "userId > 0"
...

brklnk

Mots-clés, commandes : requests, beautifulsoup, urljoin

Ecrire une commande brklnk en Python 3 qui parcourt tous les liens contenus dans une page web et affiche les liens cassés.

  • Le programme doit être installé comme une commande Unix accessible à tous les utilisateurs
  • La commande doit avoir les options suivantes :
    • --help : affiche l’usage de la commande
    • --depth=<n> : profondeur de recherche <n> (1 par défaut)
  • Un lien qui n’a pas de nom de domaine, (ex: “domicile.html”) doit être accédé avec le protocole et le domaine de l’url courant

Tester sur :

Importation de données dans une base à partir d’un fichier csv

Mots-clés, commandes : psycopg2, csv

Écrire un programme Python3 qui importe dans une base de données Postgres ou Sqlite3 les données de nutrition de Ciqual exportées sous forme d’un fichier CSV.

On créera une base de données dont le schéma permet d’inclure toutes les informations contenues dans Ciqual. Plus précisément, la base de données devra comporter les 4 tables suivantes :

nutrient
   id: int (autoincrement)
   name string

food
   id: string
   name: string
   grp_id: string (clé étrangère vers grp(id))

grp
   id: string
   name: string

nutdata
   id: int (autoincrement)
   food_id: string (clé étrangère vers food(id))
   nutrient_id: int (clé étrangère vers nutrient(id))
   value: string
Les premiers éléments de cette base seront :
nutrient(1, "Eau (g/100g)")
nutrient(2, "Protéines (g/100g)")
...
grp("01", "entrées et plats composés")
grp("02", "fruits, légumes, légumineuses et oléagineux")
...
food("25600", "Céleri rémoulade, préemballé", "01")
food("25601", "Salade de thon et légumes, appertisée, égouttée", "01")
...
nutdata(1, "25600", 1, "78.5")
nutdata(1, "25600", 2, "1.12")
...