Licence CC BY-NC-ND, Thierry Parmentelat
préparatifs¶
input()¶
voyez la fonction input() pour poser une question et lire la réponse dans le terminal
essayez vous-même
# pour poser la question et avoir la réponse
reponse = input("une question: ")
# le retour de input(), c'est directement la chaine qu'on a tapée, sans newline ni rien
print(reponse)notebooks¶
si vous envisagez de faire ce TP dans un notebook, assurez-vous de bien lire cette section
v1: un coup, les mots-clé en dur¶
on veut écrire un programme qui
pose une question, du genre
bonjour, à vous:attend la réponse tapée par l’utilisateur
puis:
selon que la réponse contient le mot
bienou le motmal
on affiche un message positif ou négatif (par exempleC'est super!etOhhhh, c'est triste.)si la phrase est vide par contre, on affiche
Tu n'es pas bavard.enfin si rien de tout cela, on affiche
Je ne comprends pas
dans tous les cas après la première réponse le programme s’arrête
une session pourrait ressembler à ceci
>>> watson()
bonjour, à vous: j'ai mal dormi aujourd'hui
Ohhhh, c'est triste.
C'est fini, au revoir !
>>># pour ceux qui travaillent dans un notebook
def watson():
...
watson()solution v1¶
ouvrez-moi
une façon de faire la v1
# implementer une fonction basique qui demande une phrase et
# retourne un message positif si la phrase contient 'bien',
# un message negatif si la phrase contient 'mal', et un message
# "tu n'est pas bavard" si elle est vide
def watson():
"""
demarre le docteur watson
parameter
---------
aucun
return
------
None
"""
phrase = input("bonjour, à vous: ").lower()
if "mal" in phrase:
print("Ohhhh, c'est triste.")
elif "bien" in phrase:
print("C'est super.")
elif not phrase:
print("Tu n'es pas bavard.")
else:
print("je ne comprends pas...")
print("C'est fini, au revoir !")
if __name__ == "__main__":
watson()
v2 - les mots-clé dans une liste¶
on change un peu le code; on veut reconnaitre les phrases positives ou négatives sur la base de plusieurs mots; par exemple
NEGATIVE_WORDS = ["mal", "triste", "marre"]
POSITIVE_WORDS = ["bien", "super", "content", "contente"]v3 - meilleure idée ?¶
est-ce que de mettre les mots-clés dans une liste c’est une bonne idée ? est-ce qu’on peut améliorer la performance du code du coup ?
v4 - les mots-clé dans un fichier¶
on veut pouvoir définir les mots-clé dans un fichier; on choisit le format suivant
$ cat watson.txt
POSITIVE bien super content contente
NEGATIVE mal triste marreajoutez dans votre code une fonction
init_watson(filename) qui retourne - par exemple - un tuple de deux ensembles de mots
v5 - boucle sans fin¶
au lieu de faire le traitement une seule fois, on va le faire indéfiniment; pour que tout de même on puisse sortir de là, on rajoute une nouvelle catégorie de mots dans le fichier de config:
$ cat watson-config.txt
POSITIVE bien super content contente
NEGATIVE mal triste marre
EXIT bye quit exitet si un des mots de la phrase est contenu dans la nouvelle catégorie on arrête le programme complètement; c’est-à-dire qu’une session ressemblerait à ceci
>>> watson()
bonjour, à vous: j'ai bien dormi
C'est super, mais encore...
j'ai mal au dents
Ohhhh, c'est triste, mais encore...
c'est fini
je ne comprends pas...
je veux dire exit
C'est fini, au revoir !
>>>solution v5¶
ouvrez-moi
une façon de faire la v5
# ameliore watson4 en ajoutant a 'while True' pour faire une boucle infinie
# bien penser à ajouter une condition de sortie avec des exit_words
def init_watson(config_filename):
"""
load the config_file_name and returns set of
sentiment words in a tuple
parameter
---------
config_file_name: the name of the file to load
return
------
the tuple of sets (positive_words, negative_words, exit_words)
"""
positive_words, negative_words, exit_words = set(), set(), set()
with open(config_filename, "r", encoding="utf8") as f:
for line in f:
# we remove the \n char is if it is at the end of the line
# it might not be the case at the end of the file
line = line.rstrip()
if not line:
continue
kind, *words = line.split()
match kind:
case "POSITIVE":
positive_words.update(set(words))
case "NEGATIVE":
negative_words.update(set(words))
case "EXIT":
exit_words.update(set(words))
return positive_words, negative_words, exit_words
def test_phrase_sentiment(sentence, sentiment):
"""
take a sentence and return True if any words
in sentiment is in phrase
parameter
---------
phrase: a set of str
sentiment: a set of str containing sentiment words
return
------
the interection between the words in the sentence and the words
in the sentiment set
"""
return sentence.intersection(sentiment)
def watson():
"""
start doctor watson
"""
positive_words, negative_words, exit_words = init_watson(
"watson-config.txt"
)
prompt = 'bonjour, à vous: '
while True:
phrase = set(input(prompt).lower().split())
if test_phrase_sentiment(phrase, negative_words):
prompt = "Ohhhh, c'est triste, mais encore...\n"
elif test_phrase_sentiment(phrase, positive_words):
prompt = "C'est super, mais encore...\n"
elif test_phrase_sentiment(phrase, exit_words):
break
elif not phrase:
prompt = "Tu n'es pas bavard. Que peux-tu me dire...\n"
else:
prompt = "je ne comprends pas...\n"
print("C'est fini, au revoir !")
if __name__ == "__main__":
watson()
v6 - rendre la fonction réglable¶
on veut pouvoir passerà la fonction
watson()un paramètre
config(qui par défaut vaut"watson-config.txt")un paramètre
debug(qui par défaut estFalse), et s’il est mis, on affiche un message qui montre où on a trouvé le mot
# on utilise un autre fichier de config, et en mode debug
>>> watson("watson-config.en", True)
sdfsdf
je ne comprends pas...
awesome this morning
DEBUG: *awesome* this morning
C'est super, mais encore...
had a nice dream
DEBUG: had a *NICE* dream
C'est super, mais encore...
exit
C'est fini, au revoir !v7 - refactoring¶
on décide de refactorer le code en créant des classes; par exemple
une classe
Feelingcorrespondant à chacun des deux ensembles de motsune classe
Sentencequi sera crée pour chaque réponseune classe
Watsonqui est l’application elle-même...
en option, on peut aussi en faire un vrai programme Python qui se lance depuis la ligne de commande (voir pour ça la librairie argparse)
solution v7¶
ouvrez-moi
une façon de faire la v7
# pylint: disable=missing-module-docstring, missing-class-docstring, missing-function-docstring
# v7: refactored
# for smart argument parsing and integrated help
import argparse
# for type hints
Name = str
# not everything is needed, but just to illustrate dunder methods...
class Sentence:
def __init__(self, sentence):
self.sentence = sentence
self.words = self.sentence.split()
def __str__(self):
return str(self.sentence)
def __contains__(self, word):
return word in self.sentence
def __len__(self):
return len(self.words)
def __getitem__(self, i):
return self.words[i]
class Feeling:
def __init__(self, name, words):
self.name = name
self.words = set(words)
def resonates(self, sentence : Sentence) -> bool:
"""
returns whether the sentence contains any word in the feeling
"""
return self.words & {word.lower() for word in sentence.words}
def outline(self, sentence : str) -> str:
"""
returns sentence, but with the words in the feeling outlined
"""
def outline(word):
return f"*{word}*" if word.lower() in self.words else word
return " ".join(outline(word) for word in sentence.words)
class Watson:
# could go in the config as well, but would probably require
# an alternative format like e.g. yaml
answers = {
'POSITIVE': "C'est super, mais encore...",
'NEGATIVE': "Ohhhh, c'est triste, mais encore...",
'EXIT': "C'est fini, au revoir !"
}
def __init__(self, config_filename):
self.config_filename = config_filename
# store in self.feelings a dict NAME -> Feeling
self.feelings = dict()
with open(config_filename, "r", encoding="utf8") as f:
for line in f:
line = line.rstrip()
if not line:
continue
name, *words = line.split()
self.feelings[name] = Feeling(name, words)
def run(self, debug=False):
prompt = 'bonjour, à vous: '
active = True
while active:
sentence = Sentence(input(prompt).lower())
for name, feeling in self.feelings.items():
if feeling.resonates(sentence):
print(self.answers[feeling.name])
if debug:
print("DEBUG:", feeling.outline(sentence))
if name == 'EXIT':
active = False
break
else:
print("je ne comprends pas...")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"-c", "--config", default="watson-config.txt",
help="give the config file to load feelings")
parser.add_argument(
"-d", "--debug", action="store_true",
help="print debug information")
args = parser.parse_args()
Watson(args.config).run(args.debug)
etc..¶
c’est toujours améliorable... par exemple:
on pourrait imaginer mettre les réponses aussi dans le fichier de config dans ce cas un autre format serait sans doute mieux adapté; que pensez-vous de yaml dans ce contexte ?
...