Billets de miamondo

  • Pysimplegui

    PySimpleGUI

    Par Le 12/09/2020

     Bonjour,

    La semaine dernière, alors que j'étais plongé dans le code de mon environnement de bureau fait maison, je me suis demandé s'il existait des alternatives à l'interface graphique Tkinter. Une rapide recherche sur Internet m'a confirmé qu'il en existait plusieurs, plus ou moins difficiles et plus ou moins bien documentées. L'une d'entre elles a tout particulièrement retenu mon attention. Il s'agit de PySimpleGUI.

    PySimpleGUI est en grande partie basée sur Tkinter et promet à l'utilisateur, une expérience de programmation graphique d'une simplicité indécente. La structure du code est censée être beaucoup plus intuitive. Séduit par ce discours de bonimenteur, j'ai décidé de voir ce que cet outil avait dans le ventre. J'ai donc remplacé tkinter par PySimpleGUI dans le programme instanciant mon environnement de bureau personnel. Passé l'enthousiasme des premières heures, j'ai vite compris que la simplicité allait s'avérer plus compliquée que prévue. L'expérience n'a pas répondu à mes attentes, ce qui ne signifie pas que l'outil n'a aucun intérêt. C'est juste qu'il ne fonctionne pas.

    Mais tout d'abord, laissez-moi vous présenter les bases de ce GUI qui diffèrent de celles de tkinter: 

    Avec tkinter, on instancie d'abord la fenêtre mère puis on vient y placer des widgets enfants. C'est-à-dire que la poule précède les oeufs. Cela nous donne:

    Code

    Tkinter
     

    Avec PySimpleGUI, c'est le contraire, on instancie d'abord les oeufs dans un layout ( = agencement) avant de les donner à la poule qui est instanciée en dernier. En clair, l'instanciation des widgets enfants précède celle du widget parent.

    Code2

    Pysimple


    Veuillez noter que la place des accolades dans le layout a toute son importance. Dans le code ci-dessus, les boutons sont agencés sur deux rangées différentes. Si j'eus souhaité que les boutons eussent été sur la même rangée, j'eus écrit ceci :

    Code3 1

     

    Voici la traduction de ce qu'écrit un membre de l'équipe PySimpleGUI. Le texte original est ici:

    Cette fenêtre peut vous sembler "laide", car aucun effort n'a été fait pour la rendre agréable à regarder. Elle est purement fonctionnelle. Il y a 30 éléments dans la fenêtre. Trente éléments. Compte tenu de ce qu'elle fait, c'est miraculeux ou tout au moins incroyablement impressionnant. Pourquoi ? Parce qu'en moins de 50 lignes de code, cette fenêtre a été créée, affichée, a recueilli les résultats et les a affichés dans une autre fenêtre.

    50 lignes. Il vous faudra 50 lignes de code tkinter ou Qt pour obtenir les 3 premiers éléments de la fenêtre écrite, si vous voulez faire cela.

    Non, soyons clairs ici... cette fenêtre nécéssitera une quantité massive de code en utilisant les GUI conventionnels. C'est un fait et si vous voulez me prouver que j'ai tort, alors faites-le, s'il vous plaît. Veuillez écrire cette fenêtre en utilisant tkinter, Qt, ou WxPython et envoyez le code! Notez que cette fenêtre a même une barre de menu en haut, ce qu'on peut facilement ne pas remarquer.

     

    Voici la ci-devant fenêtre:

    Ex2

     

    Et voici l'image d'un beau melon:

    Melon

     

    Hein? Non, c'est juste pour faire la transition et enchaîner sur la différence de traitement des images entre les deux GUI. Avec tkinter, il faut utiliser PIL ou Pillow et traiter les images pour que celles-ci puissent être affichées par le GUI.
    Avec PySimpleGUI, c'est le Saint-Esprit qui s'en occupe. Vous, vous avez juste à renseigner le chemin de l'image, pourvu qu'elle soit en *.png (comme tkinter), et vous aurez la satisfaction de voir ce beau melon s'afficher sur un bouton par exemple.

    Donc, je résume :

    • Une syntaxe plus facile et plus intuitive,
    • Pas de gestionnaire de placement proprement dit (grid() ou pack()). Il suffit de placer les widgets dans des listes,
    • Traitement des images par l'intercession du Saint-Esprit,
    • Une gestion exclusivement commandée par les événements.

    Si vous prenez le temps de flâner sur le site de PySimpleGUI, dont les pages curieusement, sont un peu longues à charger, vous découvrirez qu'il est possible de créer des fenêtres sympas, qui, comme il est écrit dans un paragraphe, ne sont pas enlaidies par un design vieillot sorti tout droit des années 90...

     

    Ex

     

    En ce qui me concerne, après avoir "basculé" mon code sur pysimpleGUI:

    • l'affichage multiécrans s'est transformé en affichage au petit bonheur la chance.
    • Certains boutons du menu principal n'affichent pas les sous-menus.
    • La barre des tâches s'affiche sur un écran mais pas sur l'autre, tandis que le menu s'affiche sur l'écran qui n'a pas la barre des tâches, mais ça dépend. Parfois, c'est le contraire.
    • Les applications s'ouvrent... ou pas. Là encore, ça dépend.
    • Le traitement des images est une catastrophe. Mais ça dépend.

    En fait, avec PySimpleGUI, j'ai eu l'impression d'être le témoin privilégié d'une expérience de physique quantique, qui autorise un objet à se trouver de manière aléatoire, dans deux états différents au même instant.

    Au bout d'une semaine, j'ai capitulé, je me suis remis à boire et je suis revenu sur la version tkinter de mon environnement de bureau. Même si ce dernier est toujours en chantier, même s'il présente encore de nombreuses imperfections, je l'utilise au quotidien et il est opérationnel (sur deux écrans). PySimpleGUI est, de mon point de vue, une intercouche qui complique inutilement l'utilisation de tkinter, ou qui, pour le dire plus clairement, ne simplifie pas de manière substantielle, la complexité toute relative de tkinter. En tout cas, ce qui est sûr, c'est que la simplicité de PySimpleGUI est bien trop compliquée.

     

  • Labortablo 2

    Labortablo, mon environnement de bureau entièrement codé en Python

    Par Le 19/08/2020

    Bonjour,

    Le projet avance bien. Je vais mettre en ligne régulièrement des  vidéos pour vous faire part de l'avancée des travaux. Le code est ici.

  • Usvainfinity svg

    Où s'arrête l'infini?

    Par Le 19/08/2020

    Bonjour,

    En Python, pour créer une boucle infinie, il suffit d'écrire ces quelques lignes:

    i = 0
    while 1 < 2 :
        print(i)
        i += 1

    while 1 < 2 signifie tant que 1 est inférieur à 2. Or, dans notre espace-temps, 1 < 2 est une vérité mathématique immuable. Cela signifie que si vous êtes à l'intérieur de la boucle, vous êtes perdu(e)s, vous n'avez aucune chance de vous en sortir. Si vous êtes à l'extérieur de la boucle et que c'est vous qui avez lancé ce programme, vous n'avez plus le contrôle. La variable i va gonfler. Vous aurez beau lui affecter une autre valeur, même négative, la boucle va continuer à incrémenter cette dernière, et votre ordinateur finira par planter, faute de place dans ses espaces de stockage. On peut donc en déduire que l'infini conduit non pas à l'éternité mais à la mort.

    Cette boucle est proscrite car elle est incontrôlable. C'est l'homme qui se prend pour Dieu et qui déclenche un processus qui lui échappe! Mais le problème, c'est qu'on a parfois besoin de laisser le temps s'écouler en direction de l'infini. C'est la cas si vous décidez par exemple de coder une horloge. Il faudra bien que cette dernière laisse défiler les secondes!

    Essayons donc avec le code ci-dessous:

    import datetime
    import time

    ts = 1
    for i in range(0, int(datetime.datetime.now().timestamp())):

        print(i, int(datetime.datetime.now().timestamp()))

        time.sleep(ts)

    • 0 est le point de départ de la boucle.
    • Le but à atteindre pour interrompre la boucle est int(datetime.datetime.now().timestamp()). Il s'agit d'un nombre entier qu'on appelle Epoch et qui correspond au nombre de secondes écoulées depuis le 1er janvier 1970 (temps de référence Unix). C'est l'instant présent, insaisissable frontière entre le passé et le futur. Évidemment, s'il n'est pas emprisonné dans une variable, ce nombre s'incrémente d'une seconde toutes les secondes.
    • La variable ts est le time.sleep. Le programme se met en pause une seconde à chaque tour de boucle. Je lance cette dernière et je traduis sa condition en langage humain: Rajoute 1 à la variable i et mets le programme en pause une seconde à chaque tour de boucle, dans l'intervalle de temps compris entre 0 et l'instant présent.

    Vous l'aurez compris, i et epoch sont incrémentés d'une seconde à chaque tour de boucle, si bien que la variable i court derrière l'objectif sans jamais pouvoir l'atteindre! Nous sommes donc bien en présence d'une boucle infinie, allez-vous en conclure. 


    Petite précision:

    Le commentateur Chris m'a fait la remarque que le deuxième code n'est pas une boucle infinie puisque les bornes de départ et d'arrivée sont définies une bonne fois pour toute et ne peuvent pas être modifiées en cours d'itération. Il a raison. La bonne d'arrivée ne s'incrémente pas. Je vous invite à lire son commentaire qui apporte des précisions à l'article. Et je prends acte de cette erreur.

    Il doit être possible cependant de faire une sorte de boucle infinie avec for in range(), en utilisant un générateur comme par exemple dans ce code inspiré d'un script trouvé sur stackoverflow:

    def adjustable_range(i, stop=None, step=None):

        if stop is None:

            i, stop = 0, i

        if step is None:

            step = 1

        while i < stop:

            change_bound = (yield i)

            if change_bound is None:

                i += step

            else:

                stop = change_bound

    myrange = adjustable_range(2)

    for i in myrange:

        print(i)

        myrange.send(2+i)

        time.sleep(1)


    Oui et non, tout dépend de l'endroit où se trouve l'observateur et tout dépend de ce que l'on entend par infini. Si vous êtes à l'intérieur de la boucle, alors je suis désolé de vous annoncer que vous allez passer le reste de votre éternité à tenter d'atteindre Epoch. Vers la fin, ça risque d'être un peu long. Par contre, si vous êtes à l'extérieur de la boucle, vous pouvez agir sur la variable ts en lui affectant la valeur 0. Le programme ne se met plus en pause. Avec ces nouvelles conditions, la variable i effectue 4255 tours de boucle en une seconde (du moins sur mon ordinateur). À ce rythme, elle finira par rattraper Epoch, et en retrouvant notre espace-temps, la boucle infinie va s'interrompre.

    Pourtant, même avec les conditions de depart (ts = 1), il me semble qu'à plus ou moins long terme, cette boucle finira par s'écrouler sur elle-même. Pourquoi? Parce que la variable i gonfle sans qu'il soit possible de réduire sa valeur. Par exemple, si elle atteint 1000000 et que vous décidez de la repasser à 0, ce changement ne sera pas pris en compte. C'est sans doute dû au fait que la variable i est en charge de l'itération de la boucle. On ne peut donc pas modifier sa valeur en cours de route. Enfin, c'est ce que j'en déduis, mais n'hésitez pas à me corriger si je fais fausse route.

    Personnellement, pour coder mom horloge, j'ai opté pour cette formule hybride qui permet de garder le contrôle du time.sleep et de réduire régulièrement.

    import datetime
    import time

    i = 0
    ts = 1
    while i < int(datetime.datetime.now().timestamp()):
        print(i, int(datetime.datetime.now().timestamp()))
        time.sleep(ts)
        if i==10000:
            i = 0
        i += 1

    Dans ce troisième code, vous pouvez agir sur la variable ts, en lui affectant 0. Ainsi, vous annulez son action. Vous pouvez également shunter la variable i en lui affectant 10001, ce qui fait qu'elle ne repassera plus à la valeur 0. Dans ces conditions, i finira par rattraper epoch et par interrompre la boucle.

    J'ignore s'il existe d'autres alternatives qui permettent d'avoir une boucle infinie sous contrôle. Si c'est le cas, je vous saurais gré de me le dire.

    Quelle conclusion peut-on tirer de cette petite excursion temporelle? On dit que notre univers est en expansion. Il gonfle, parait-il. Du coup, je me demande si notre espace-temps n'est pas prisonnier d'une boucle infinie. Est-ce que la mort est la variable qui permet à chacun d'entre nous de s'en extraire? Difficile de répondre à cette question vu que nous sommes encore en vie... Et quelle est la nature de cette boucle qui enveloppe notre univers? Si Dieu a choisi while 1 < 2, alors il a fait une sacrée connerie! Et nous sommes perdu(e)s. Même Greta Thunberg ne peut rien pour nous. Notre univers va gonfler avant de planter comme un ordinateur.

    Si par contre, Dieu s'est donné les moyens de pouvoir modifier les conditions qui gouvernent la boucle, alors l'univers pourra entamer une phase de contraction qui lui évitera d'exploser. Observez bien le symbole de l'infini qui illustre l'article. Il s'agit d'une boucle qui symbolise l'expansion et la contraction d'un volume fini. C'est peut-être aussi la meilleure définition de la récursivité.

  • Kronik

    Kronik

    Par Le 29/07/2020

    Sommaire : Présentation de l'application Le script start.py le script kronik.py Epoch Les itérateurs  Quelques réflexions sur la notion d'infinité

    Bonjour

    Aujourd'hui, j'ai envie de vous présenter une application codée par mes soins. Je l'ai baptisée Kronik. Il s'agit d'un planificateur de tâches comparable à Cron, mais dotée d'une interface graphique. J'ai commencé à écrire les premières lignes en Bash, mais je trouvais le résultat peu chatoyant. J'ai donc décidé d'emballer tout ça dans du Python et du Tkinter. Vous trouverez une vidéo à la fin de l'article.

    Le projet est perfectible, notamment au niveau du redimensionnement des fenêtres, mais je suis tout de même très satisfait du résultat, car il m'a fallu relever plusieurs défis:

    • Des calculs mathématiques et des ajustements temporels à la limite de mes capacités mentales.
    • L'utilisation d'un itérateur qui permet de ne pas s'engouffrer dans une boucle infinie ayant pour irrémédiable conséquence un crash de l'application.

    Alors, comment ça fonctionne? Eh bien, grâce à deux scripts Python:

    • start.py lance le programme proprement dit, lequel permet à l'interface chaise/clavier, de sélectionner les applications à planifier.
    • kronik.py est lancé par le fichier openbox/autostart, à chaque démarrage et se charge de gérer les tâches précédemment planifiées.

    Voici donc à quoi ressemble la page d'accueil:

     

    Kronik

     

    On trouve le titre enchâssé dans un widget LabelFrame, avec en dessous, un second cadre étiqueté qui permet de sélectionner une commande ou de renseigner directement le chemin absolu d'un fichier à exécuter (.py, .c, etc...). Le menu déroulant (Listbox) répertorie les commandes de toutes les applications installées sur votre ordinateur. Pour générer cette liste, ce n'est pas bien compliqué. Il suffit de boucler sur le répertoire /usr/share/applications. Ensuite, il faut ouvrir chaque fichier *.desktop pour en extraire la ligne qui commence par exec

    Sous ce menu déroulant, on trouve un champ d'entrée qui permet de renseigner le chemin absolu d'une application qui n'est pas présente dans /usr/share/applications. Enfin, on trouve un bouton Applications planifiées. Il ouvre une fenêtre Toplevel qui répertorie toutes les tâches planifiées et permet de supprimer chacune d'entre elles. Je souhaite améliorer cette fonctionnalité, en y incluant la fréquence (tous les jours, toutes les deux heures etc...)

     

    Fenêtre surgissante

     

    Continuons... Je sélectionne gnome-disks et je valide. Une fenêtre surgissante apparaît et me demande de confirmer en cliquant dessus. Je peux encore changer d'avis mais je décide de confirmer.

     

    kronik2

     

    J'atterris sur une fenêtre remplie de widgets Scale. Il est à noter que je me suis arrangé pour qu'il ne soit pas possible de déplacer deux curseurs simultanément. Si vous bougez un curseur, les autres sont désactivés ou reviennent à la position 0. Il y a également un widget Entry. Ce dernier permet à l'usager de renseigner une tâche qui va se lancer une seule fois. Pour une précision optimale, il faut que je rajoute les minutes et les secondes. Lorsque vous avez fait votre choix et que vous l'avez confirmé, la planification est lancée.

     

    Kronik

     

    2020 07 29 204650 788x622 scrot

     

    Ça, c'est la partie émergée de l'iceberg. Le travailleur de l'ombre, celui qui gère la planification depuis son repaire situé dans les profondeurs de la chaîne volcanique balafrant les Terres de Volgor, c'est kronik.py. Et on peut dire que sa conception m'a donné du fil à retordre, car elle m'a obligé à manipuler des dates transformées en secondes et paramétrées en prenant pour référence Epoch, c'est-à-dire le 1er Janvier 1970. Je ne suis lancé dans de savants calculs qui ont failli me rendre fou, d'autant plus que je voulais trouver une combine pour prendre en compte le temps écoulé lorsque l'ordinateur n'est plus sous tension. C'est-à dire que lorsque je rallume mon ordinateur après une bonne nuit de sommeil, les paramètres temporels ne sont plus les mêmes qu'au moment de l'extinction. Ils ont été mis à jour grâce à l'intervention d'Epoch. Voici donc le fichier généré par Kronik le bossu. Il s'appelle scheduledApps:

    • /home/miamondo/.config/kronik/start.py:86400:47872:1596000773:1596039301
    • gnome-disks:86400:83624:1596039457:1596042233
    • /home/miamondo/.config/kronik/system_update/system_update.py:86400:86308:1596042147:1596042239

    Les paramètres sont séparés par deux petits points. Actuellement, on trouve trois tâches planifiées. Prenons la dernière d'entre elles. Il s'agit d'une petite application que j'ai inclus dans le programme, pour mettre à jour ma distribution Archlinux. Je l'ai codée il y a deux ans.

    1. Chemin de l'application: system_update.py
    2. Fréquence d'exécution du programme: 86400 secondes soit 24 heures
    3. Temps restant avant exécution du programme: 86308 secondes
    4. Précédente exécution du programme (ou premier lancement): 1596042147 secondes après Epoch (01/01/1970)
    5. Tems écoulé depuis Epoch: 1596042239 secondes. C'est l'instant présent. 

    Si vous êtes perspicaces, vous aurez remarqué qu'entre le second et le troisième paramètre, l'écart est de 92 secondes. C'est exactement le même nombre qui sépare le quatrième et le cinquième paramètre... Et normalement, si j'éteins mon ordinateur et que je le redémarre  dans deux heures, les écarts seront identiques (évidemment pas forcément 92 secondes!). Je ne vous raconte pas la prise de tête pour configurer tout ça!

     

    system_update

     

    Deuxième gros défi, il faut que le temps et le programme s'écoulent sans interruption. Il n'est pas possible de créer une boucle infinie du type while 1: go on!. Au bout de 998 tours, elle va se crasher, et le programme avec. Il faut écrire du code itératif ou récursif, mais en prenant soin de définir une condition qui permette théoriquement de sortir de la boucle. J'utilise donc un itérateur, qui tourne tant que la variable d' incrémentation n'a pas atteint Époch, ce qu'elle ne fera jamais... mais elle ne le sait pas... Et il ne faut surtout pas lui dire! Voici le bout de code :

            for i in range(0, int(datetime.datetime.now().timestamp())):
                self
    .go_on()

        def go_on(self): ... suite du code.

    Ce code de deux lignes, plus philosophique qu'informatique, nous interroge sur la notion d'infinité. Il s'agit bien d'une boucle infinie puisque la variable i ne rattrapera jamais Epoch, condition sine qua non pour arrêter le programme. Epoch est lui-même un itérateur. Et pourtant, si on change la valeur de la variable i en lui affectant Epoch, alors la boucle s'arrête... Et ça, ce n'est pas possible avec une boucle du type while 1 < 2. Celle-ci est réellement une prison infinie, un châtiment mathématique perpétuel que l'on ne peut arrêter puisque 1 sera pour toujours inférieur à 2.    

    Voilà donc pour cette petite application qui m'a demandé du temps à coder, mais qui m'a aussi apporté beaucoup de satisfaction. Elle est perfectible, certes, mais elle est déjà opérationnelle. N'hésitez pas à me faire part de vos remarques, notamment au niveau du code itératif. Il n'a pas encore planté mais sait-on jamais... Peut-être y-a-t-il une faiblesse qui m'échappe. 

    J'essaierai, dans un prochain billet, d'expliquer la différence entre un itérateur et une fonction récursive mais la vulgarisation de ces théories abstraites n'est pas chose aisée. 

  • Wallpaper

    La planète Archlinux

    Par Le 14/07/2020

    Bonjour,

    Dans ce court billet, je mets à votre disposition deux liens d'un même document, en version PDF et en version ODT. Ils pointent vers un manuel d'une cinquantaine de pages qui détaille l'installation d'Archlinux. J'en suis l'auteur. Il s'agit d'une mise à jour du manuel initial paru sur ce même blog il y a un peu plus d'un an. J'ai simplifié le descriptif du processus d'installation, vérifié tous les liens, et refait la mise en page.

     

    Archlinux est une distribution à publication continue, qui tourne sur mon ordinateur depuis plus d'un an et qui me donne entière satisfaction. Contrairement à Ubuntu, distribution grand public, Archlinux n'est pas conseillée pour les débutant(e)s. Son installation, plus exigeante, s'adresse à celles et ceux qui possèdent dèjà des connaissances de base en système Linux et qui savent ouvrir un terminal pour exécuter des lignes de commande.

    Cela dit, bien qu'elle soit ardue, l'installation de cette distribution est très intéressante car elle permet de comprendre l'architecture et le fonctionnement d'un système d'exploitation Linux. Par conséquent, son intérêt pédagogique est évident.

    On peut diviser le manuel en deux parties. Les quatre premiers chapitres expliquent de manière détaillée, l'installation du système de base, tandis que le cinquième et dernier chapitre vous propose de post-installer une configuration d'Openbox personnalisée par mes soins. N'hésitez pas à me laisser vos commentaires. Toute remarque, si tant est qu'elle soit constructive, est la bienvenue.

    Le fond d'écran qui illustre cette article est celui que j'ai également choisi pour ma distribution. Il s'agit d'un tableau du peintre russe Ivan Chichkine intitulé Pluie dans le bois de chênes (1891). J'aime beaucoup le réalisme de ce peintre.

     

expand_less