prog data

Identification

Infoforall

42 - Programme en tant que donneé


Nous allons voir aujourd'hui qu'un programme n'est qu'une donnée comme une autre (vous avez déjà vu).

Nous allons d'ailleurs aujourd'hui créer notre propre interpréteur !

Logiciel nécessaire pour l'activité : Python 3 : Thonny, IDLE ...

Résumé :

1 - Programme en tant que donnée : la théorie

Pour comprendre ce que nous allons faire aujourd'hui, il est important de comprendre qu'un programme n'est au final :

  • qu'une suite de caractères dans le cas d'un code-source
  • qu'une suite d'octet (ou de bits) dans le cas d'un code-binaire

Et encore : comme on encode les caractères comme des suites d'octets, on obtient uniquement des bits dans les deux cas.

Qui dit donnée, dit programme ou fonction capable de les traiter.

Vous avez déjà vu de nombreux exemples de programmes capables de faire cela :

  • Le compilateur C
  • On lui fournit un code-source en entrée et le transforme en code binaire.

    Le prototype de la fonction équivalente imagée serait :

    1
    def compilateur(code_source:str) -> bytes:
  • L'interpréteur Python
  • On lui fournit un code-source en entrée et le transforme à la volée en un code binaire (ou un code compréhensible pour un autre programme qui pourra le faire.

    Le prototype de la fonction équivalente imagée serait :

    1
    def interpreteur(code_source:str) -> bytes:

    D'ailleurs, vous lancez souvent vos scripts Python depuis Thonny mais vous pourriez faire la même chose en utilisant directement un appel à Python. Imaginons que la console ci-dessous pointe actuellemnet dans le répertoire contenant un code-source Python nommé directement.py. Pour lancer son exécution, il suffit de taper cela :

    alias@mon_ordi:~$ cd Documents/prog_python/ alias@mon_ordi:~/Documents/prog_python$ python3 dichotomie.py
  • La création d'un processus
  • On fournit le PID du processus parent (PPID donc) et le code binaire d'un programme au système d'exploitation qui va le transformer en processus et va fournir l'identifiant PID permettant de le gérer.

    Le prototype de la fonction équivalente imagée serait :

    1
    def creation_processus(ppid:int, code_binaire:bytes) -> int:
  • Le téléchargement d'un programme
  • Que se passe-t-il lorsque vous voulez télécharger un programme ?

    A l'aide de l'URL fourni sous forme d'une chaîne de caractères, le serveur va localiser le fichier voulu et va vous renvoyer les octets encodant le programme (que ce soit un code-source ou un code binaire).

    1
    def telechargement(url_fichier:str) -> bytes:

La suite de l'activité n'est qu'un mini-TP totalement optionnel pour la NSI : la seule chose attendue aujourd'hui est de comprendre qu'un programme (ou une fonction) n'est qu'une donnée comme une autre. Donnée qui peut être traitée par un autre programme. Nous l'avons vu de façon conceptuelle.

La suite de l'activité montre concrétement :

  • Partie 2 : qu'on peut envoyer des données à une fonction sans nécessairement leur attribuer un nom de paramètre précis
  • Partie 3 : qu'on peut envoyer des données à un programme lors de son appel
  • Partie 4 : qu'on peut donc créer un interpréteur : un programme qui lit un code source pour réaliser des actions
  • Partie 5 : qu'on peut donc envoyer un programme à notre interpréteur

Mais tout ceci est optionnel. C'est juste pour vous montrer comment cela peut fonctionner.

2 - Envoyer des données à une fonction

Vous savez qu'on peut envoyer des données à une fonction en fournissant des argumnets qui seront stockés dans les paramètres.

01° Utiliser le programme ci-dessous en le sauvegardant sous le nom "transfert.py".

Faire quelques tests pour vous convaincre qu'on parvient bien ) transmettre les arguments fournis (normalement, à ce nouveau là de la NSI, la question devrait vous paraître stupide...).

1 2 3 4 5 6
DEBUG = True def addition(a:int,b:int) -> int: if DEBUG: print(a) if DEBUG: print(b) return a + b
>>> %Run transfert.py >>> addition(5,10) 5 10 15

En réalité, on peut transférer autant d'arguments qu'on veut à une fonction et elle peut alors les stocker dans autant de paramètres qu'il le faut.

Pour récupérer ces arguments supplémentaires, il faut indiquer un paramètre assez particulier : un paramètre qui commence par une étoile. Habituellement, on le nomme *args. En Python, il s'agit alors d'un tuple qui va récupérer tous les arguments qu'on a pas pu placer dans les arguments définis "normalement".

02° Utiliser le programme ci-dessous en le sauvegardant sous le nom "transfert.py".

1 2 3 4 5 6 7 8 9 10 11
DEBUG = True def addition(a:int, b:int, *args:tuple) -> int: if DEBUG: print(a) print(b) print(args) reponse = a + b for v in args: reponse = reponse + v return reponse

Question :

Qu'est-ce qui montre que *args est bien un tuple en Python ?

>>> %Run transfert.py >>> addition(5,10) 5 10 () 15 >>> addition(5,10,20,30,40) 5 10 (20, 30, 40) 105

Si on veut garder une belle fonction, il faudra par contre expliquer qu'on ne veut que des entiers par exemple.

Nous ne sommes même pas obligés de garder a et b d'ailleurs : on pourrait juste faire ceci mais rien ne garantit en réalité que la réponse soit bien un intéger :

1 2 3 4 5 6 7 8
DEBUG = True def addition(*args:tuple) -> int: if DEBUG: print(args) reponse = 0 for v in args: reponse = reponse + v return reponse
>>> %Run transfert.py >>> addition(5,10,20,30,40) (5, 10, 20, 30, 40) 105

On peut donc transférer des arguments non nommés à une fonction.

Mais on peut aussi transmettre des arguments nommés sans que la fonction n'ai besoin de les avoir clairement dans ces paramètres.

Il faut alors prévoir un paramètre particulier qui va recevoir tous les arguments nommés non déclarés. Le nom de ce paramètre particulier doit commencer par deux étoiles. On le nomme habituellement **kwargs.

03° Utiliser le programme ci-dessous en le sauvegardant sous le nom "transfert.py".

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
DEBUG = True def calculer(*args, **kwargs): if DEBUG: print(args) if DEBUG: print(kwargs) reponse = None if kwargs['operation'] == '+': reponse = 0 for v in args: reponse = reponse + v if kwargs['operation'] == '*': reponse = 1 for v in args: reponse = reponse * v return reponse

Question :

Qu'est-ce qui montre que **kwargs est bien un dictionnaire en Python ?

>>> %Run transfert.py >>> calculer(5,10, operation='+') (5, 10) {'operation': '+'} 15 >>> calculer(5,10, operation='*', la_reponse=42) (5, 10) {'operation': '*', 'la_reponse': 42} 50

3 - Envoyer des données à un programme

Pourquoi parler de *args et **kwargs alors que ce ne sont pas des attendus d'un élève de NSI ? Pour vous montrer qu'il existe des mécanismes capables de récupérer les arguments non explicites fournies à une fonction.

Et on peut faire la même chose avec nos programmes...

Finalement, un processus peut être vue comme une sorte de fonction exécutée par le processus-parent, non ?

Pour l'instant, l'appel au programme a été fait sous cette forme :

>>> %Run transfert.py

Mais on pourrait aussi faire ceci :

>>> %Run transfert.py 5 10

On pourrait ainsi fournir directement des données au programme : l'interpréteur Python est activé avant %Run et on lui envoie des arguments, à savoir :

  • Le nom du script à interpréter : transfert.py
  • Un premier argument : 5
  • Un deuxième argument : 10

Pour visualiser ceci avec Python, nous allons devoir utiliser le module sys qui permet d'interagir avec le système d'exploitation. Souvenez-vous que c'est lui qui gère les processus et donc l'exécution effective des programmes.

04° Utiliser le programme ci-dessous en le sauvegardant sous le nom "transfert.py". Faire différents appels pour visualiser qu'on parvient bien à lui envoyer des données et pas simplement lui demander de tourner avec ces données internes.

1 2 3 4 5
import sys DEBUG = True print(sys.argv)

Questions :

  • Qu'est-ce qui montre que sys.argv est un tableau en Python ?
  • Quel est le type du 5 une fois reçu pour le programme ?
>>> %Run transfert.py ['transfert.py'] >>> %Run transfert.py 5 ['transfert.py', '5'] >>> %Run transfert.py 5 10 ['transfert.py', '5', '10'] >>> %Run transfert.py * 5 10 ['transfert.py', '*', '5', '10'] >>> %Run transfert.py + 5 10 ['transfert.py', '+', '5', '10']

05° Réaliser la fonction calculer qui permettra au PROGRAMME d'afficher les résultats demandées (voir les exemples d'appels du programme).

Je ne m'intéresse pas ici à la manière de récupérer la réponse d'un programme pour l'envoyer à un autre programme. On se contentera pour l'instant d'un simple print.

De la même façon, on ne fera aucune vérification des données envoyées. En terme de solidité du code créé, c'est pour joyeux...

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
import sys DEBUG = True def calculer(op:str, nbs:list) -> int: return None if __name__ == '__main__': a = sys.argv # on stocke le tableau contenant les arguments envoyés nb_a = len(a) # on stocke le nombre d'arguments envoyés if nb_a > 3: operation = a[1] nombres = [a[i] for i in range(2, nb_a)] # attention, on lance l'appel sans vérifier la conformité des paramètres... r = calculer(operation, nombres) print(r)
>>> %Run transfert.py >>> %Run transfert.py + 5 10 15 >>> %Run transfert.py * 5 10 50 >>> %Run transfert.py * 5 10 10 500

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
import sys DEBUG = False def calculer(op:str, nbs:list) -> int: """Renvoie l'addition ou la multiplication de tous les nombres recus""" if op == '+': reponse = 0 for n in nbs: reponse = reponse + int(n) elif op == '*': reponse = 1 for n in nbs: reponse = reponse * int(n) return reponse if __name__ == '__main__': a = sys.argv # on stocke le tableau contenant les arguments envoyés nb_a = len(a) # on stocke le nombre d'arguments envoyés if nb_a > 3: operation = a[1] nombres = [a[i] for i in range(2, nb_a)] # attention, on lance l'appel sans vérifier la conformité des paramètres... r = calculer(operation, nombres) print(r)

Nous avons donc vu :

  • Qu'on pouvait envoyer des données à une fonction :
    • en récupérant les données en nombre prévu dans les paramètres de la fonction directement
    • en récupérant les autres données éventuels non nommées dans un paramètre *args de type tuple en Python
    • en récupérant les autres données éventuels nommées dans un paramètre **kwargs de type dict en Python.
  • Qu'on pouvait envoyer des données à un programme :
    • on récupère ces données sous forme d'un tableau avec l'utilisation de sys.argv en Python
    • Attention, tous les éléments du tableau sont des chaînes de caractères (type str de Python). Pensez à les convertir au besoin.

Il reste à voir comment transmettre un programme à un autre programme.

Dans les deux parties suivantes, nous allons donc :

  • Créer notre propre programme-interpréteur (et notre propre langage donc)
  • Lui envoyer un programme !

4 - Réaliser notre propre interpréteur

5 - Envoyer un programme à notre interpréteur

.

Activité publiée le 30 03 2021
Dernière modification : 06 04 2021
Auteur : ows. h.