Outils pour utilisateurs

Outils du site


issue155:python

A quick update on the recipe-scrapers library that I wrote about last month. Right now, there are 47 different recipe sites that are now supported, 5 of which I wrote for the project. I have a few more sites that I am working on for the project, so that number will continue to grow. Many of the scrapers are dedicated to non-United States sites in many different languages. I will probably explain the process of creating your own scraper in a future article. As most of you know, I act as an unofficial support outlet for Page, the GUI designer for Python using Tkinter. On average, I spend probably 5 hours a week answering questions from users, both brand new to Page and users who have been using Page for years. I also help out Don Rozenberg with testing new development builds, trying to break it in as many ways as I can. Once I can’t break it anymore, Don usually creates a release. It’s a very time consuming job, but one I really enjoy.

Une rapide mise à jour à propos de la bibliothèque recipe-scrapers dont j'ai parlé le mois dernier. Actuellement, 47 sites différents de recettes sont supportés, comprenant les 5 dont j'ai parlé pour le projet. Je travaille sur quelques autres sites pour le projet, ce qui fait continuellement monter leur nombre. Beaucoup des extracteurs sont dédiés à des sites hors des USA dans de nombreuses langues différentes. J'expliquerai probablement le processus de la création de votre propre extracteur dans un article futur.

Comme la plupart d'entre vous le savent, je suis un point de contact de support officieux de Page, le concepteur en affichage graphique pour Python utilisant Tkinter. En moyenne, je passe 5 heures par semaine à répondre aux questions des utilisateurs, que ce soit de tout nouveaux utilisateurs ou des utilisateurs qui utilisent Page depuis des années. J'aide aussi Don Rozenberg en testant les compilations du développement, en essayant de les casser par des moyens aussi différents que possible. Lorsque je n'arrive plus à les détruire, Don crée généralement une version publiée. C'est un travail qui demande beaucoup de temps, mais que j'adore.

Of all the questions I get about using Page, the one I get the most is how to create a program with multiple forms. The answer to this is really pretty easy, but is not as straight-forward as a user would think, hence the questions. The next question is how do I get the forms or windows to communicate with each other. My good friend Halvard from Norway asked how to have one form read information from another in “real time”. Again, while the answer is easy, it’s not something that most users would try before they ask the question. So, in this edition, I intend to present a very simple demo to help understand the process. AND there’s an added benefit in that this solution is not limited to a Page GUI program. It can be used for any Python program including CLI programs. Your imagination is the only limit. So, I thought I’d throw together a quick demo using Page to show how to deal with both ideas. I’m going to use Page since the process is much easier to show in a GUI, and since the first question is about dealing with multiple form Page programs. The project will consist of two Page forms, one called “Parent” and the other called “Child”. The Parent program will launch the Child program and will receive data from the child.

Parmi toutes les questions que je reçois sur l'utilisation de Page, la plus courante est comment créer un programme avec plusieurs formulaires. La réponse à cela est vraiment très simple, mais elle n'est pas aussi directe que ce que pourrait penser l'utilisateur, d'où les questions. La question suivante est comment je fais en sorte que les formulaires ou les fenêtres communiquent entre eux. Mon cher ami Halvard de Norvège m'a demandé comment faire pour qu'un formulaire lise une information dans un autre formulaire en « temps réel ». À nouveau, alors que la réponse est simple, ce n'est pas quelque chose que les utilisateurs essaient avant de poser leur question. Aussi, dans ce numéro, j'ai l'intention de présenter une démo simple pour aider à comprendre le processus. ET, s'y ajoute un bénéfice : cette solution n'est pas limitée au programme d'affichage graphique Page. Elle peut être utilisée pour n'importe quel programme en Python, même avec ceux en ligne de commande. Votre imagination est la seule limitation.

Aussi, j'ai pensé mettre sur pied une rapide démo en utilisant Page pour montrer comment gérer les deux idées. Je vais utiliser Page car le programme est beaucoup plus facile à montrer en affichage graphique et parce que la première question a trait aux programmes Pages avec plusieurs formulaires.

Le projet sera constitué de deux formulaires Page, l'un appelé « Parent », l'autre, « Child » (Enfant). Le programme Parent lancera le programme Enfant et recevra des données de cet enfant.

Form Design We’ll create the Parent form first. I won’t bore you with the form creation details, I’ll just provide a screenshot of the form and give you a small amount of important information about some special attributes of some of the widgets. Below is what the Parent form looks like. I didn’t spend much time on making it pretty and mainly kept the attributes for everything on the form to their defaults where possible.

Conception des formulaires

Nous créerons d'abord le formulaire Parent. Je ne vous ennuierai pas avec les détails de la création du formulaire. Je vous fournirai simplement une copie d'écran de celui-ci et vous donnerai une pincée d'informations importantes sur des attributs spéciaux de certains gadgets.

Ci-dessous, voici à quoi ressemble le formulaire Parent. Je n'ai pas passé beaucoup de temps à l'arranger et j'ai principalement gardé les attributs à leur valeur par défaut partout où c'était possible.

As you can see, it’s a very simple form. Two buttons and four labels. The two buttons are named or aliased (from left to right) as btnLaunch and btnExit. There two static labels (status and received) and two dynamic labels, one that is a simple coloured square which shows the connection status to the child form and one (that shows as a fancy ribbed box) which has the textvariable set as DataReceived so it can be updated easily by code. This will, as the variable suggests, contain the data that comes from the child form. Before I saved the form, I moved it to the middle-left of the screen. The child form will be positioned to the middle-right side of the screen. btnLaunch has the command attribute set to “on_btnLaunch” and the btnExit has the command attribute set to “on_btnExit”. These are the names of the callback functions for each of the buttons. The only other in the top_level form is the title of “I am Parent”.

Comme vous pouvez le voir, le formulaire est très simple. Deux boutons et quatre étiquettes. Les deux boutons ont pour nom ou alias (de gauche à droite) btnLaunch et btnExit. Puis il y a deux étiquettes statiques (status et received) et deux étiquettes dynamiques ; l'une est un simple carré coloré qui montre l'état de la connexion au formulaire enfant et l'autre (qui se présente sous la forme d'un champ nervuré raffiné) a une variable de type texte appelée DataReceived, pour être ainsi facilement mise à jour par programmation. Comme le suggère son nom de variable, elle contiendra les données qui arriveront du formulaire enfant. Avant de sauvegarder le formulaire, je l'ai déplacé vers la gauche au milieu de l'écran. La forme enfant se positionnera à droite au milieu de l'écran.

btnLaunch a l'attribut de commande positionné sur « on_btnLaunch » et l'attribut de commande de btnExit est paramétré sur « on_btnExit ». Ce sont les noms de fonctions de rappel de chacun des boutons. La seule autre chose dans le formulaire top_level (de niveau haut) est le titre « I am Parent » (Je suis le Parent).

Next, we’ll design our child program. This one is a bit more complicated, but not horribly so. As you can see, there is a small simple keypad similar to a “10 key” with 3 helper buttons, all in a frame. There is also a label that will display the value of the key presses that has the textvariable set to DisplayLabel. The helper buttons are Clear, Bksp (Backspace) and Enter. At this point the enter key does nothing. There is also an Exit button. The three helper buttons have the command attributes set to on_btnClear, on_btnBackspace and on_btnEnter, which, again, are the callback functions, and the exit button has its command attribute set to on_btnExit. The 11 keypad buttons don’t use the command attribute set, since it’s easier to set the callback function, which requires a parameter containing which button was clicked, by using the bind command – which we will see in a few minutes. We’ll set the bindings in the _support module.

Ensuite, nous construirons notre programme enfant. Il est un peu plus compliqué, mais pas trop quand même.

Comme vous pouvez le voir, il y a un petit pavé numérique du genre « 10 touches » avec trois boutons annexes, le tout dans un cadre. Il y a aussi une étiquette qui affichera la valeur de la touche enfoncée, dont la variable texte est paramétrée sur DisplayLabel. Les boutons annexes sont Clear (Effacer), Bksp (Backspace, retour arrière) et Enter (Entrée). Pour le moment, le bouton Enter ne sert à rien. Il y a aussi un bouton Exit (Sortie).

Les trois boutons complémentaires ont leurs attributs de commande paramétrés sur on_btnClear, on_btnBackspace et on_btnEnter, qui sont, là encore, des fonctions de rappel, et le bouton Exit a son attribut de commande paramétré sur on_btnExit.

Les 11 boutons du pavé n'utilisent pas d'attributs de commande paramétrés, car il est plus facile de paramétrer des fonctions de rappel, qui nécessitent un paramètre contenant le bouton sur lequel on a cliqué, en utilisant la commande bind, ce que nous verrons dans quelques minutes. Nous paramétrerons les liaisons dans le module _support.

The Communications Magic To communicate between programs, we use a shared python file, in this case called “shared.py”. Really apropos filename, huh? This module is imported into both (or as many programs as needed) programs as a standard import…

La magie des communications

Pour communiquer entre les programmes, nous utilisons un fichier python partagé, appelé dans notre cas « shared.py ». Un titre très appoprié, n'est-ce pas ? Ce module est importé dans chaque programme (ou dans autant de programmes que nécessaire) comme un import classique…

Import shared The file itself is actually an empty file. There is nothing in it. However, since both of our programs have imported it, they can each read from and write to it. However, you need to be careful to make sure that before you try to read the value of a variable from it, the value must have already been written to the shared module. We’ll discuss this some more when we examine the code.

Import partagé

Le fichier lui-même est en fait un fichier vide. Il n'y a rien dedans. Cependant, comme il est importé par nos deux programmes, ils peuvent le lire et y écrire.

Malgré tout, vous devez rester prudent pour vous assurer que, avant d'essayer de lire la valeur d'une variable à l'intérieur, la valeur a déjà été écrite dans le module partagé. Nous en parlerons un peu plus quand nous examinerons le code.

The Code The code for the two _support.py modules will be presented below. It’s presented almost in its entirety. The code for the GUI files won’t be presented, nor will the .tcl files, however the GUI.py files will be available from the pastebin repository so you can actually run the programs. As always, we’ll start with the imports section for the parent_support.py file. Notice we import child.py, child_support.py, and shared.py import sys import child import child_support import shared The next function (below) is provided by Page for us, which is the set_Tk_var function. This gives us the access to the Label that displays the values that are generated by the child program.

Le Code

Le code des deux modules _support.py sera présenté ci-dessous. Il est montré quasiment complet. Le code des fichiers d'affichage graphique n'est pas montré, ni celui des fichiers .tcl ; cependant, les fichiers GUI.py seront disponibles dans le dépôt pastebin de sorte que vous pourrez vraiment lancer les programmes.

Comme toujours, nous commencerons par la section des imports pour le fichier parent_support.py. Notez que nous importons child.py, child_support.py et shared.py :

import sys import child import child_support import shared

La fonction suivante (ci-dessous) nous est fournie par Page ; c'est la fonction set_Tk_var. Elle nous donne accès à l'étiquette qui affiche les valeurs générées par le programme enfant.

Next up is the init function. This is the very last thing that gets run within the program before the GUI is shown to the user, so we run any initialization and setup tasks from this function. The top part of the function is already written for us by Page. I always provide the comment box, just to give me a “landmark” to easily find the function. We’ll discuss my added code below… The first two lines of my added code, set two variables in the shared module. That way, when the child program starts up, the variables are already there and can be written to when needed. We also use those variables in the next function so they need to be initialized right away. If we don’t, Python will throw an error.

Ensuite (page suivante, en bas à gauche), voici la fonction init. C'est la toute dernière chose qui est lancée dans le programme avant que l'affichage graphique ne soit révélé à l'utilisateur ; aussi, nous lançons toutes les tâches d'initialisation et de paramétrage à partir de cette fonction. La partie haute a déjà été écrite par Page pour nous. Je fournis toujours le champ de commentaire, juste pour me donner un « repère » pour trouver la fonction facilement. Nous parlerons ci-dessous du code que j'ai ajouté…

Les deux premières lignes du code que j'ai ajouté paramètrent deux variables dans le module partagé. De cette façon, quand le programme enfant démarre, les variables sont déjà présentes et peuvent être écrites quand on en a besoin. Nous utilisons aussi ces variables dans la fonction suivante ; c'est pourquoi nous devons les paramétrer tout de suite. Si nous ne le faisons pas, Python sortira une erreur.

The next thing we do is assign an alias for the status label, which is the red square that shows when the child program is running and connected to the shared module. Finally, I set up a timer function that Tkinter provides called “root.after”. This is an event that fires every X milliseconds to take care of just about any kind of repetitive task you want to do. You can also create multiple ‘root.after’ timers that run simultaneously. The basic syntax is: handle = root.after(ms, callback) In the case above, the handle is called “comm1” which lets me know that this particular timer is used to communicate with the child process. Notice that I set the time to 0, which means that the callback function will be called immediately, and the final parameter is the name of the callback function.

Ensuite, nous assignons un alias à l'étiquette d'état, qui est le carré rouge qui montre quand le programme enfant tourne et qu'il est connecté au module partagé. Enfin, j'ai paramétré une fonction compteur que Tkinter fournit sous le nom « root.after ». C'est un événement qui se déclenche toutes les X millisecondes pour s'occuper de toutes les sortes de tâches répétitives que vous voulez accomplir. Vous pouvez aussi créer plusieurs compteurs « root.after » qui tournent en parallèle. La syntaxe de base est :

   handle = root.after(ms, callback)
   

Dans le cas ci-dessus, le handle est appelé « comm1 » ce qui vous fait savoir que ce compteur particulier est utilisé pour communiquer avec le traitement enfant. Notez que j'ai réglé le temps sur 0, ce qui signifie que la fonction de rappel est appelée immédiatement et le paramètre final est le nom de la fonction de rappel.

Bottom right is the callback function code… First, we set two global variables, comm1 which is the handle for the timer, and LblStat which is the alias for our little red square. Next, we access the shared module to see if the child process is running by checking shared.child_active to see if it’s True. This is set as soon as the child program starts up. If it is, we set the square colored label to “Green” to show that the child process is running and then we check to see if shared.ReadyToRead is set to True – which is basically a flag that says that one of the numeric keys has been clicked. If it is, we get that data, put it into the display label with the .set() method, and clear the ReadyToRead flag, so we can wait for the next click event on the child process. If the shared.child_active flag is False, we reset the coloured square to Red. This way, when the child program is exited, we will know it visually.

En bas à droite, voici le code de la fonction de rappel :

D'abord, nous paramétrons deux variables globales, comm1 qui est le handle du compteur et LblStat qui est l'alias de notre petit carré rouge. Ensuite, nous accédons au module partagé pour voir si le processus enfant tourne en vérifiant si shared.child_active vaut True (Vrai). Il en est ainsi dès que le programme enfant démarre. Si c'est le cas, nous réglons le carré coloré sur « Green » (Vert) pour montrer que le programe enfant tourne, puis nous vérifions si shared.ReadyToRead est positionné sur True - c'est en gros un drapeau qui dit qu'une des touches numériques a été cliquée. Si c'est le cas, nous recevons les données, les mettons dans l'étiquette d'affichage avec la méthode .set() et effaçons le drapeau ReadyToRead de façon à attendre le prochain événement de clic dans le processus enfant. Si le drapeau shared.child_active est False (Faux), nous remettons la couleur du carré à Red (Rouge). De cette façon, quand le programme enfant se terminera, nous le saurons visuellement.

Finally, we “re-arm” the timer routine, this time to check 100 ms from that point. Now, we look at the on_btnExit callback function (shown above). It’s very simple. We simply call the destroy_window() function that will cleanly end the program. The destroy_window function is provided by Page as is the on_btnExit callback skeleton, since we added the callback name in the command attribute for the exit button. All we have to do is add the line “destroy_window()”. Next, we’ll look at the btnLaunch callback function (above). This is how we make the child program start. Again, we used the command attribute for the button in Page, so the skeleton is started for us…

Enfin, nous « ré-armons » la routine du compteur, cette fois-ci pour décompter 100 ms à partir de là.

Maintenant, nous regardons la fonction de rappel on_btnExit (présentée ci-dessus). Elle est très simple. Nous appelons simplement la fonction destroy_window() qui terminera proprement le programme. La fonction destroy_window() est fournie par Page tout comme l'épine dorsale du rappel on_btnExit, puisque nous avons ajouté le nom du rappel dans l'attribut de commande du bouton Exit. Tout ce que nous devons faire est d'ajouter la ligne « destroy_window() ».

Ensuite, nous regardons la fonction de rappel btnLaunch (ci-dessus). C'est par là que nous démarrons le programme enfant. À nouveau, nous utilisons l'attribut de commande du bouton dans Page, de sorte que la structure générale est prête pour nous.

The only line we need to enter here is the last one. Since we have already imported the child.py GUI file at the top of the code, we just need to call the create_Toplevel1() function. This is the entry point for the program when it is called from another program. Finally, I’ve provided the destroy_window() (below) which is provided by Page, just so you can see it. Now we’ll look at the child program. It is a bit more complicated, but not overly so. Again, I’m going to provide the code here for only the child_support.py module.

La seule ligne que nous avons besoin de saisir ici est la dernière. Comme nous avons déjà importé le fichier d'affichage graphique child.py au début du code, nous devons simplement appeler la fonction create_Toplevel1(). C'est le point d'entrée du programme quand il est appelé par un autre programme.

Enfin, j'ai mis destroy_window() (ci-dessous), fournie par Page, juste pour que vous la voyiez.

Maintenant, nous regardons le programme enfant. Il est un peut plus compliqué, mais pas trop. Ici encore, je vais fournir le code uniquement pour le module child_support.py.

Again, we’ll start with the import section. Notice here, that we need to import only the shared module, since we don’t need to call any functions from the parent. import sys import shared Top right is the definition for the label that shows the running value from the keypad button entries. The first thing we do is set up a global variable to hold the accumulated value of the keypad entries. We then call the function setup_bindings() that attaches the callback function to all of the buttons of the keypad. Finally, we set the shared.child_active flag to True.

À nouveau, nous commençons par la section des imports. Notez ici que nous ne devons importer que le module partagé, car nous n'avons besoin d'appeler aucune fonction du parent.

import sys

import shared

En haut à droite, voici la définition de l'étiquette qui montre la valeur courante venant des saisies sur les boutons du pavé numérique :

La première chose que nous faisons est de paramétrer une variable globale pour garder la valeur cumulative des saisies du pavé. Ensuite, nous appelons la fonction setup_bindings() qui relie la fonction de rappel à tous les boutons du pavé. Enfin, nous paramétrons le drapeau shared.child_active à True.

Since we are going to pass parameters to the callback for the keypad buttons, it’s much easier to deal with things here (below) than to try to do it within Page through the command attribute. Now we define the callback routine code (next page, top left) for when a keypad button is clicked. Notice this must be done from scratch, since Page has no idea of the need for the function. In the callback, we simply take the value (which is the number of the button) and append it, as a string, to the valu variable. We also check to see if the period key (value 10) was pressed, and if it was, then we add the period into the display value. Finally, we put the data into the DisplayLabel through the .set() method, and set shared.ReadyToRead to True, so the parent knows to pull the data.

Comme nous allons passer des paramètres à la fonction de rappel des boutons du pavé, il est plus facile de s'arranger avec les choses ici (ci-dessous) plutôt que d'essayer de le faire dans Page par le biais de l'attribut de commande.

Maintenant, nous définissons le code de la routine de rappel (page suivante, en haut à gauche) dans le cas où on aurait cliqué sur un bouton du pavé. Notez qu'il faut le faire à partir de zéro, car Page n'a aucune idée de la nécessité de cette fonction.

Dans la fonction de rappel, nous prenons simplement la valeur (qui est le numéro du bouton) et le rajoutons, sous forme de chaîne, à la variable valu. Nous vérifions aussi si la touche du point (valeur 10) a été enfoncée, et, si c'est le cas, nous ajoutons le point à la valeur affichée. Enfin, nous mettons les données dans DisplayLabel par la méthode .set(), et réglons shared.ReadyToRead à True, de sorte que le parent sait qu'il doit récupérer les données.

We don’t do anything with the Enter button, so we just leave the skeleton for later use (far right). As usual,, I’ve put the code for the programs on Pastebin. You can find the links below: parent.py - https://pastebin.com/AZXXvuAU parent_support.py - https://pastebin.com/3iBHgCN0 child.py - https://pastebin.com/bwZLnkHc child_support.py - https://pastebin.com/Vg0K1w5G I hope this article has given you some food for thought that can be used in your own programming. Until next time, keep coding.

Nous ne faisons rien du bouton Entrée ; aussi, nous gardons juste la structure pour un usage ultérieur (à droite, en haut).

Comme d'habitude, j'ai mis le code des programmes sur Pastebin. Voici les liens ci-dessous :

parent.py - https://pastebin.com/AZXXvuAU

parent_support.py - https://pastebin.com/3iBHgCN0

child.py - https://pastebin.com/bwZLnkHc

child_support.py - https://pastebin.com/Vg0K1w5G

J'espère que cet article vous a donné de la matière à réflexion utile susceptible de vous aider dans votre propre programmation.

Jusqu'à la prochaine fois, continuez à coder !

p 26, col 4, encart, lignes noires :

Here is the init function from the child program. Again, you can see where my code starts.

Voici la fonction init du programme enfant. À nouveau, vous pouvez voir où commence mon code.

p 25, col 4, encart, 3 § noirs :

The callback for the Exit button is mostly the same as for the parent, but we also set shared.child_active to False, so the Parent knows to change the status square from Green to Red and not to try to poll.

La fonction de rappel du bouton Exit est principalement la même que pour le parent, mais nous réglons aussi shared.child_active à False, de sorte que le parent sait qu'il doit changer le carré du vert au rouge et ne plus essayer de l'interroger.

The btnClear callback function sets the global valu to an empty string, sets data into the shared module and the display label, and then sets the shared.ReadyToRead flag to True.

La fonction de rappel btnClear charge la globale valu avec une chaîne vide, règle les données dans le module partagé et l'étiquette d'affichage, puis positionne le drapeau ReadyToRead à True.

Finally, we deal with the callback for the Backspace button. We simply strip the last character from the valu string, display it, pass it to the shared module, and set the flag so that the parent program will read it.

Enfin, nous nous occupons de la fonction de rappel du bouton Backspace (Retour Arrière). Nous supprimons juste le dernier caractère de la chaîne valu, l'affichons, la passons au module partagé et réglons le drapeau de sorte que le programme parent la lise.

issue155/python.txt · Dernière modification: 2020/04/04 18:00 par andre_domenech