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"):
import urllib
sock = urllib.urlopen(url)
htmlSource = sock.read()
sock.close()
|
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.
|
|
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é).
|
|
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()
parserClass = globals()[parserName]
parser = parserClass()
|
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'.
|
|
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.
|
|
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)
parser.close()
return parser.output()
|
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.
|
|
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.
|
|
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
- Vous croyiez que je plaisantais quand je parlais de traitement
côté serveur. C’est ce que je pensais aussi, jusqu’à ce que je
trouve ce
«traducteur» en ligne. Malheureusement, le code
source n’a pas l’air d’être disponible.