8.9. Assembler les pièces

Il est temps d’utiliser tout ce que nous avons appris. J’espère que vous avez été attentif.

Exemple 8.20. La fonction translate, première partie


def translate(url, dialectName="chef"): 1
    import urllib                       2
    sock = urllib.urlopen(url)          3
    htmlSource = sock.read()           
    sock.close()                       
1 La fonction translate a un argument optionnel dialectName, qui est une chaîne spécifiant le dialecte que nous allons utiliser. Nous allons voir comment il est employé dans une minute.
2 Attendez une seconde, il y a une instruction import dans cette fonction ! C’est parfaitement légal en Python. Vous êtes habitué à voir des instructions import au début d’un programme, ce qui signifie que le module importé est disponible n’importe où dans le programme. Mais vous pouvez également importer des modules dans une fonction, ce qui signifie que le module importé n’est disponible qu’à l’intérieur de cette fonction. Si un module n’est utilisé que dans une seule fonction, c’est un bon moyen de rendre votre code plus modulaire (quand vous vous rendrez compte que votre bidouille du week-end est devenue une oeuvre respectable de 800 lignes et que vous déciderez de la segmenter en une dizaine de modules réutilisables, vous apprécierez cette possibilité).
3 Ici, nous obtenons le code source de l’URL passée en paramètre.

Exemple 8.21. La fonction translate, deuxième partie : de bizarre en étrange

    parserName = "%sDialectizer" % dialectName.capitalize() 1
    parserClass = globals()[parserName]                     2
    parser = parserClass()                                  3
1 capitalize est une méthode de chaîne que nous n’avons pas encore vue. Elle met simplement en majuscule la première lettre d’une chaîne et met le reste en minuscules. En la combinant à un formatage de chaîne, nous avons pris le nom d’un dialecte et l’avons transformé en un nom de classe Dialectizer lui correspondant. Si dialectName est la chaîne 'chef', parserName sera la chaîne 'ChefDialectizer'.
2 Nous avons le nom d’une classe sous forme de chaîne (parserName) et l’espace de noms sous forme de dictionnaire (globals()). En les combinant, nous pouvons obtenir une référence à la classe désignée par la chaîne (rappelez-vous que les classes sont des objets et peuvent être assignés à des variables comme n’importe quel autre objet). Si parserName est la chaîne 'ChefDialectizer', parserClass sera la classe ChefDialectizer.
3 Maintenant nous avons un objet de classe (parserClass) et nous voulons une instance de la classe. Nous savons déjà comment on fait ça, en appelant la classe comme une fonction. Le fait que la classe soit référencée par une variable locale ne fait absolument aucune différence, nous appelons simplement la variable locale comme une fonction et obtenons une instance de la classe. Si parserClass est la classe ChefDialectizer, parser sera une instance de la classe ChefDialectizer.

Pourquoi un tel effort ? Après tout, il n’y a que 3 classes Dialectizer, pourquoi ne pas utiliser simplement une instruction case (il n’y a pas de case en Python, mais nous pourrions utiliser une série d’instructions if) ? Pour une seule raison, l’extensibilité. La fonction translate n’a absolument aucune idée du nombre de classes Dialectizer que nous avons défini. Imaginez que nous définissions une nouvelle classe FooDialectizer demain, translate continuerait de fonctionner en recevant 'foo' en paramètre dialectName.

Encore mieux, imaginez que nous mettions FooDialectizer dans un module séparé et que nous l’importions par from module import. Nous avons déjà vu que cela l’ajoute à globals(), donc translate fonctionnerait toujours sans modification, même si FooDialectizer était dans un autre fichier.

Maintenant, imaginez que le nom du dialecte provienne de l’extérieur du programme, par exemple d’une base de données ou d’une valeur entrée par un utilisateur dans un formulaire. Vous pouvez utiliser n’importe quelle architecture Python côté serveur pour générer dynamiquement des pages Web, cette fonction pourrait prendre une URL et un nom de dialecte (les deux sous la forme de chaîne) dans la chaîne d’une requête de page Web et renvoyer la page Web «traduite».

Finalement, imaginez un framework Dialectizer avec une architecture de plug-ins. Vous pourriez mettre chaque classe Dialectizer dans un fichier séparé, laissant uniquement la fonction translate dans dialect.py. Avec un modèle de nommage uniforme, la fonction translate pourrait importer dynamiquement la classe appropriée du fichier approprié, uniquement à partir du nom de dialecte (vous n’avez pas encore vu d'importation dynamique, mais je promet de la traiter dans un prochain chapitre). Pour ajouter un nouveau dialecte, vous ajouteriez simplement un nouveau fichier correctement nommé dans le répertoire des plug-ins (par exemple foodialect.py contenant la classe FooDialectizer). Appeler la fonction translate avec le nom du dialecte 'foo' ferait charger le module foodialect.py, importer la classe FooDialectizer et lancer la traduction.

Exemple 8.22. La fonction translate, troisième partie

    parser.feed(htmlSource) 1
    parser.close()          2
    return parser.output()  3
1 Après tout ce que je vous ai demandé d’imaginer, cela va sembler plutôt ennuyeux, mais la fonction feed est responsable de toute la transformation. Nous avons l’ensemble du source HTML rassemblé en une seule chaîne, donc nous n’avons à appeler feed qu’une seule fois. Cependant, vous pouvez appeler feed autant de fois que vous le voulez et le parser continuera son travail. Si vous vous inquiétez de l’utilisation mémoire (ou si vous savez que vous aurez à traiter de très grandes pages HTML), vous pouvez écrire une boucle dans laquelle vous lisez quelques lignes de HTML et les passez au parser. Le résultat serait le même.
2 Comme feed gère un tampon interne, vous devez toujours appelez la méthode close du parser lorsque vous avez terminé (même si vous lui avez passé la totalité en une seule fois comme nous venons de le faire). Dans le cas contraire vous risquez de vous apercevoir que votre sortie est tronquée.
3 Rappelez-vous que output est la fonction que nous avons définie dans BaseHTMLProcessor qui assemble toutes les pièces de sortie que nous avons stockées en tampon et les retourne sous forme d’une chaîne unique.

Et rien qu’en faisant ça, nous avons «traduit» une page Web, rien qu’à partir d’une URL et d’un nom de dialecte.

Pour en savoir plus