IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Apprendre une technique pour gérer plus facilement les arguments passés à un script Python
Un tutoriel de Nothus

Le , par Nothus

0PARTAGES

A la fin de ce petit article, je proposerai une ébauche de module téléchargeable depuis GitHub. Le code indiqué ici est valide pour 3.6, sans garantie pour les versions antérieures - même si tout devrait globalement fonctionner pour Python 3.

Sous Python, l'accès aux arguments (sans passer par les circonvolutions des modules officiels de Python), se fait très facilement :
Code python : Sélectionner tout
1
2
import sys 
print(sys.argv)

Qui affichera... pas grand chose dans l'IDLE de Python, sauf un laconique [''] - ce qui nous indique déjà que son retour est une liste !

Il existe d'autres manières, plus complètes mais souvent plus délicates à mettre en œuvre.

Or le plus régulièrement, particulièrement lorsque le script n'a pas une destination particulière à augmenter en complexité ou en partage (ou par flemme, ou par envie de s'embêter plus tard...), il est plus simple d'utiliser sys.argv même si on y trouve rapidement des limites. L'ennemi du bien étant le mieux, il est aussi tout à fait possible de ne jamais s'occuper des arguments et de tout mettre dans un fichier de configuration. Pour ma part je préfère un INI ou autre bien fichu et documenté que quelques lignes.

En voici quelques raisons, sous forme d'un bref comparatif :

Avantages des arguments
  • les arguments peuvent modifier - "à la marge" - certains comportements, par exemple lorsqu'un serveur se lance (choix du port)
  • les arguments n'imposent pas la création d'un fichier pour démarrer (si problème d'écriture sur le disque ; ce qui évite en soi un problème de sécurité - éviter d'écriture n'importe quoi - mais en rajoute un autre - permettre potentiellement d'écrire n'importe quoi)
  • les arguments sont plus rapides à utiliser, pour peu que l'on connaisse sur le point des ongles toutes les ramifications de son script
  • les arguments peuvent donner plus facilement accès à la documentation (-h)


Avantages des fichiers de configuration
  • sans accès à l'écriture sur le disque, il est possible d'ajouter une protection supplémentaire pour éviter à un script de se lancer
  • l'argument est peu lisible, les erreurs plus faciles ("-r" détruit tout et "-t" fait une sauvegarde : on se trompe de touche, et c'est le drame...)
  • les fichiers de configuration n'ont pas vraiment de limites de tailles ou même permettent des choses intéressantes, comme les sections des fichiers INI
  • un fichier de configuration peut se sauvegarder, se partager, parfois plus facilement qu'une succession de drapeaux peu verbeux


Mais la bataille entre partisans des fichiers de configuration ou des arguments de script n'est pas le sujet... sys.argv indique toujours le chemin (ou son absence) et donc le nom du fichier (ou son absence) qui "porte" le script. Ainsi le même code qu'au-dessus dans "demo1.py" donnera dans mon cas ['C:/Users/Julien/Google Drive/Développement/lanceur/demo1.py'].

Les autres éléments de la liste seront les différentes briques qui forment la totalité des arguments : sans distinction entre les drapeaux (les clés d’un futur dictionnaire en quelque sorte) et les contenus associés (les valeurs de ce même futur dictionnaire). Ainsi mon "demo1.py" de tout l'heure, lancé ainsi : demo1.py -a 1 2 3 4 donnera [" ... chemin...", "-a","1","2","3","4"] !

Pire, le terminal (c-à-d interpréteurs de commandes) utilisé peut retirer certains éléments de ma commande, comme par exemple le double & qui lie deux scripts (if-then) ou utilisé de manière unique en toute fin signifiant que le script n'est plus directement rattaché à la console sous Linux. Bref ce que vous recevez par sys.argv ne doit pas être considérer comme strictement la commande de l'utilisateur (parce qu'il utilise un script bash entre ; parce qu'il lie les commandes, etc) mais comme des indications à prendre en compte.

Pour trouver ce ce qui doit nous revenir, nous allons donc prendre tout ce qu'il y a après la clé 0. Par exemple :
Code python : Sélectionner tout
1
2
import sys 
print(sys.argv[1:])


Il suffit ensuite de parcourir cette liste raccourcie pour construire notre dictionnaire des arguments ; ce sont les tirets qui donneront l’indicateur que le drapeau est soit court (un tiret) ou long (deux tirets), et surtout n’est pas une valeur. Voici un proposition qui reprend cette construction et l’affine (notamment si une valeur est passée sans drapeau, elle s’ajoute dans le dictionnaire sous la clé "0").

Code python : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import sys 
  
arguments = {0:[]} #attention, le drapeau "-0" renvoie une chaîne de caractère "0" et l’entier 0.   
  
argvs = list(sys.argv) 
path = argvs[0]  
for i in argvs[1:]: 
    if i[0]=="-": #indicateur que c’est un drapeau et non une valeur  
        try:  
            arguments[i] 
        except:  
            arguments[i] = [] 
        cle = i  
    else: 
        try: 
            cle # si une clé n’est pas encore définie, on ajoute cela dans un drapeau 0, qui n’est jamais qu’un INT(0) pour la clé du dictionnaire des arguments  
        except:  
            cle = 0  
        try:  
            arguments[cle].append(i) 
        except:  
            pass 
try:  
    if cle not in arguments: 
        arguments[cle] = [] #ce cas se présente s’il n’y a qu’un drapeau passé en argument (afin qu’il ne se perd pas)  
except:  
    pass  
  
print(arguments)


Testez demo1.py "pouet" -a "oui" -b -a "non" : vous recevrez alors en message console : {0 : ["pouet"], "a" : ["ok", "ko"], "b" : []}. Donc tout va bien ! Vous avez ainsi déjà vos arguments bien classés.

Gardez à l’esprit que j’ai priorisé un ajout et non un écrasement pour deux drapeaux identiques : demo1.py -a "non !" -a "si !" renvoie donc une liste pour le drapeau "a" qui est ["non!", "si !"] et pas seulement "si !". Ce brouillon de script peut facilement être glissé dans une classe, et y associer des opérations de transformation dès le traitement de sys.argv. Ou encore des modalités particulières d’accès comme par exemple :

Code python : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import sys  
import re  
  
class Configuration: 
  
    argvs = list()  
    path = ""  
    arguments = {0:[]} 
  
    def __init__(self): 
        self.argvs = list(sys.argv) 
        self.path = self.argvs[0]  
        for i in self.argvs[1:]: 
            if i[0]=="-":  
                cle = i 
                try:  
                    self.arguments[cle] 
                except:  
                    self.arguments[cle] = [] 
            else: 
                try: 
                    cle 
                except:  
                    cle = 0  
                try:  
                    self.arguments[cle].append(i) 
                except:  
                    pass 
        try:  
            if cle not in self.arguments: 
                self.arguments[cle] = [] 
        except:  
            pass  
  
    def acceder(self,cle,defaut=[]): 
        try: 
            return self.arguments[cle] 
        except:  
            return defaut  
  
if __name__=="__main__": 
  
    C = Configuration() 
  
    print(C.arguments)  
  
    print(C.acceder("b","Pas de b !")) 
  
    print(C.acceder("a","Pas de a !"))

C’est facile et lors d’un import, c’est propre et assez généraliste pour passer dans toutes les versions de Python 3 comme je l’indiquais en introduction. Reste qu’il n’y a pas vraiment un accès totalement différencié pour chacun de mes arguments : tout sont renvoyés de la même façon. Je vais donc devoir créer une petite classe TTT ("traitement" en bon français réduit!), qui se chargera de stocker sous forme de fonctions l’accès à une variable (en somme le décorateur property un poil modifié car il prend en compte un paramètre "utilisateur" et pas seulement l’objet...).

Et puis pour ajouter une french-touch – pardon, une "touche française" :
  • on peut ajouter une méthode desinfection (la bonne traduction de sanitize en anglais), pour enlever les indicateurs de drapeaux longs ou courts qui ne nous intéressent pas ici
  • on ajoute de la documentation, du coup logiquement caler dans ma classe de traitement (au cas où l’on en change : ma classe Configuration, elle, ne change pas)


Et voici ce que devient mon "demo1.py" :

Code python : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import sys  
import re  
  
class Configuration: 
  
    argvs = list()  
    path = ""  
    arguments = {0:[]} 
  
    desinfecter = True  
  
    objTraitement = None  
  
    def __init__(self,desinfecter=None): 
        self.desinfecter if desinfecter is True else False  
        self.argvs = list(sys.argv) 
        self.path = self.argvs[0]  
        for i in self.argvs[1:]: 
            if i[0]=="-":  
                cle = self.desinfection(i) if self.desinfecter is True else i  
                try:  
                    self.arguments[cle] 
                except:  
                    self.arguments[cle] = [] 
            else: 
                try: 
                    cle 
                except:  
                    cle = 0  
                try:  
                    self.arguments[cle].append(i) 
                except:  
                    pass 
        try:  
            if cle not in self.arguments: 
                self.arguments[cle] = [] 
        except:  
            pass  
  
    def desinfection(self,i): 
        i = re.sub("^[\-]+","",i)  
        return i  
  
    def acceder(self,cle,defaut=[]): 
        try: 
            return getattr(self.objTraitement,cle)(self.arguments)  
        except:  
            try:  
                return self.arguments[cle] 
            except: 
                return defaut  
  
    def documenter(self,cle): 
        try: 
            return getattr(self.objTraitement,cle).__doc__  
        except: 
            return False  
  
    def traitement(self,objTraitement): 
        self.objTraitement = objTraitement  
  
if __name__=="__main__": 
  
    class TTT: 
  
        def a(self,args): 
            """ 
                Additionner les nombres passés en arguments  
            """  
            return sum(map(int,args["a"]))  
  
        def c(self,args): 
            """ 
                Ceci est la documentation... de toute façon la valeur sera toujours non.  
            """  
            return "non !"  
  
    C = Configuration() 
    C.traitement(TTT())  
  
    print(C.arguments)  
  
    print(C.acceder("b","Pas de b !")) 
  
    print(C.acceder("a","Impossible de faire le total"))  
  
    print(C.documenter("a"))

Vous pouvez aussi jouer sur la classe TTT pour ne plus plus passer les arguments systématiquement et imposer à votre classe de configuration de passer par elle, pour arriver à quelque chose qui ressemble à ça :

Code python : Sélectionner tout
1
2
3
4
5
def acceder(self,cle,defaut=[]): 
        try: 
            return getattr(self.objTraitement,cle)()  
        except:  
            return defaut


Voilà, ce petit article est terminé. Vous pourrez trouver ces deux classes dans mon GitHub : n’hésitez pas à y ajouter vos commentaires ou vos remarques de bugs.

Bon test et bon développement !

Julien.

Une erreur dans cette actualité ? Signalez-nous-la !