#Python – Application « Mon jardin » : Analyse du code (1ère partie)

Edit du 04/04/2017

Je viens de déposer la totalité du code source sur Framagit : 

https://framagit.org/Ordinosor/mon-jardin/tree/master

Pour lancer l’application:

  • il faut télécharger le fichier compressé contenant le dossier mon_jardin et le fichier jardin.py.
  • Puis, il faut décompresser le fichier et placer mon_jardin et jardin.py dans votre répertoire courant (par exemple, chez moi, c’est « /home/benoit »).
  • Ensuite, il faut rendre jardin.py exécutable. C’est ce fichier qui lance l’application.
  • Enfin, il faut entrer dans un terminal la commande suivante, par exemple chez moi :  /home/benoit/jardin.py. Bien sûr, vous remplacez /home/benoit par votre répertoire courant.

Bonjour à tous,

Il y a quelques jours, je vous avais présenté mon application destinée aux jardiniers en herbe. Elle est composé de six modules. Je suis en train de reprendre le code de chacun d’entre eux pour en éliminer les bugs et les risques de crash.

Je me suis dit que ce serait peut-être une bonne idée de vous présenter et d’analyser le code de chaque module dans une vidéo. Je commence par le premier qui créé la page d’accueil et la page des menus. Il s’appelle jardin.py :

Voici le code:

https://pastebin.com/embed_iframe/BBGVRccR

Et voici la vidéo explicative:

Advertisements

Auteur : Ordinosor

Bienvenue sur Miamondo, mon blog personnel. "Mia mondo", c'est de l'espéranto et ça signifie "Mon monde" en français. Je m'appelle Benoît alias Ordinosor, Français expatrié en Allemagne. Mes centres d'intérêt sont les distributions GNU/Linux, le langage de programmation Python, la science-fiction et l'espéranto.

23 réflexions sur « #Python – Application « Mon jardin » : Analyse du code (1ère partie) »

  1. J’avais recopié les fichiers que tu avais mis sur le pastbin lors de ton précédent post, mais
    le code n’est pas fonctionnel. Il manque les images, et égalements des modules (calendrier_perpetuel,
    agenda_perpetuel, …)

    Tu devrais mettre le code sur un dépôt public, comme te l’as suggéré « microniko » en commentaire.

    Je suis pas du tout spécialiste, mais j’ai quelques remarques quand même…

    – # -*- coding: utf8 -*- en début de fichier : il me semble que ce n’est plus nécessaire avec python3. C’est déjà de l’utf8 par défault

    – # ——- pour séparer les parties du code : jettes un coup d’oeil à ces pages : http://sametmax.com/le-pep8-et-au-dela-par-la-pratique/ http://sametmax.com/le-pep8-en-resume/
    http://sametmax.com/cours-et-tutos/ : y’a plein de conseils et d’astuces python sur ce site(peut-être le connais tu déjà?)

    – def _init_(self): devrait être __init__ ? (manque un tiret bas de chaque côté)

    – ordre des imports : respecter une logique
    Exemple :

    import os
    import shutil
    import collections
    from PIL import Image, ImageTk

    from tkinter import *
    import tkinter.messagebox
    import tkinter.filedialog

    from module_globs import Globales
    from calendrier_perpetuel import Calendar
    from agenda_perpetuel import Agenda_perpetuel
    from module_notes import Notes
    from phototheque_jardin import Phototheque_Jardin
    from legume import Legumes
    from fruit import Fruits

    – structurer le projet :

    Exemple:

    |–src/
    |–launch.py
    |–classes/
    |–app.py
    |–infos.py
    |–images/
    |–windows/
    |–jardin.py
    |–legume.py
    |–fruit.py
    |–scripts/
    |–doc/
    README

    Le programme se lancerait depuis le fichier launch.py qui taperait dans le fichier app.py
    Le fichier infos.py contiendrait quelques variables : version du programme, nom du programme,etc..
    Le dossier scripts servirait à mettre, entre autres, le script pour créer un exécutable windows (tu voulais le faire il me semble…)
    C’est juste un exemple de base, à adapter et à améliorer selon tes besoins.

    Sinon, j’ai pas testé les classes et les méthodes, j’attends que le code disponible soit fonctionnel…

    Bonne continuation !

    Aimé par 1 personne

    1. Salut,
      Merci pour tes conseils. Oui, je vais le mettre sur un dépôt public le plus vite possible avec tout ce qui va bien : les images etc…, au plus tard demain soir.
      C’est __init__(self) en effet. Petite étourderie que je viens de corriger.

      Pour ce qui est de structurer le projet, il va falloir en effet que je m’y attelle… Je me donne comme objectif la fin de la semaine pour avoir une application terminée.

      En tout cas, merci beaucoup pour ton aide.

      J'aime

  2. je remets l’arborescence,

    /src/
    /src/launch.py
    /src/classes/
    /src/classes/app.py
    /src/classes/infos.py
    /src/images/
    /src/windows/
    /src/windows/jardin.py
    /src/windows/legume.py
    /src/windows/fruit.py
    /scripts/
    /doc/
    README

    J'aime

  3. Heureux d’apprendre que ton appli sera accessible sur un dépôt public 🙂
    Moi qui voulait bricoler un truc pour gérer les semis (périodes et associations). J’avais commencé avec Python/Django mais c’est au point mort… Si je trouve du temps pour contribuer, ça serait avec plaisir.

    J'aime

    1. Salut,
      Je suis bien content d’avoir réussi à mettre mon code sur un dépôt public. Au début, c’était une vraie galère mais au final, l’outil Framagit m’a l’air très bien. Je regrette de ne pas l’avoir fait plus tôt. J’ai perdu du temps à travailler tout seul dans mon coin.

      Pour ce qui est de ton bug, je vais regarder ça de plus, près aujourd’hui.

      J'aime

  4. Merci pour ce partage.

    Quelques remarques d’ordre général avant de me plonger plus en profondeur dans le code (j’ai jamais fait de tkinter, ce sera l’occasion pour moi de découvrir 🙂 ) :
    * trop de commentaire tue le commentaire : le but d’un commentaire est de compléter le code, pour préciser ce qui ne saute pas forcément aux yeux à le première lecture. Commenter chaque ligne, à l’inverse, ça alourdit le code et le rend moins lisible, surtout quand le commentaire n’apporte rien par rapport à la ligne (typiquement, des commentaires comme « # importation du module Tkinter », « # compréhension de liste » ou encore « # Appel de la méthode show_list_fruits() » n’apportent rien à la compréhension du code, ils ne font que l’alourdir)
    * pour expliquer ce que fait une série d’instructions, il est préférable de mettre quelques lignes de commentaire avant, plutôt qu’un commentaire par ligne de code. Ainsi on commence par lire tout le commentaire, puis tout le code.
    * privilégiez les commentaires sur une ligne séparée plutôt qu’en fin de ligne
    * pour que le code soit lisible, il faut faire des lignes courtes. Sans aller dans l’extrêmisme du PEP-8 (79 caractères par ligne), essayez de ne pas dépasser les 100 caractères. Pour celà, vous pouvez découper les longues suites de « .machin() » en plusieurs lignes via des variables intermédiaires, en « précalculant » les paramètres de fonctions dans des variables, mais aussi et surtout, dans le cas de votre code, aller à la ligne dans les longues suites de paramètres. Par exemple :
    self.edit = Label(self.editeur, width = 55, justify = ‘left’, bg = ‘#EFEFEF’, text = str(self.readfile_variete[i2-2]) + str(self.readfile_variete[i2-1]).strip(), highlightthickness = 2, highlightcolor = « red », highlightbackground = « green », font = « Times 14 », anchor = ‘w’, padx = 10, pady = 10, takefocus = 1)
    Peut devenir en sortant le calcul du paramètre text et en ajoutant des retours à la ligne :
    edit_text = str(self.readfile_variete[i2-2]) + str(self.readfile_variete[i2-1]).strip()
    self.edit = Label(self.editeur, width = 55, justify = ‘left’, bg = ‘#EFEFEF’,
    text = edit_text, highlightthickness = 2, highlightcolor = « red »,
    highlightbackground = « green », font = « Times 14 », anchor = ‘w’,
    padx = 10, pady = 10, takefocus = 1)
    (en alignant les lignes « découpées » sur la fin de la parenthèse de Label(
    Le code sera ainsi bien plus aéré et bien plus facile à lire.
    * séparez les longues fonctions en blocs de quelques lignes, en insérant des lignes vides (toujours pour avoir quelque chose de plus aéré)
    * n’utilisez self. que pour stocker des données dont vous avez besoin dans plusieurs méthodes différentes, pas pour les variables locales à une méthode (et théoriquement, pour être propre, toutes les variables self. devraient être déclarées dans le __init__… pas obligatoire, mais vivement conseillé, pour ne pas avoir à tester si elles existent lorsqu’on y accède). Ainsi, dans l’exemple plus bas, self.file_image_legumes devient file_image_legumes, aucun intérêt de garder ça comme attribut d’instance, puisque le fichier est refermé dès qu’on sort du bloc with. Ceci permet en outre d’utiliser des noms plus simples, puisqu’on évite les conflits.

    D’un point de vue plus technique, pensez à utiliser les exceptions. Par exemple, au lieu d’ouvrir les fichiers en mode append puis de les refermer aussitôt pour les lire, faites directement une lecture, en rattrapant l’exception qui se produit si le fichier n’existe pas. Par exemple, la fonction « show_list_vegetables » de jardin.py deviendrait :
    def show_list_vegetables(self) :
    « Déroule la liste des légumes enregistrés dans le menu du bouton. »
    # Lecture des images
    try:
    with open(« images_legumes », ‘r’) as file_:
    images = file_.readlines()
    except IOError:
    images = []
    # Lecture des légumes
    try:
    with open(« legumes », ‘r’) as file_:
    legumes = file_.readlines()
    except IOError:
    legumes = []
    # Suppression des fins de ligne
    images = [strip(image) for image in images]
    legumes = [strip(legume) for legume in legumes]
    # Dictionnaire contenant le nom du légume et l’image associée à ce légume
    self.dico_legumes = collections.OrderedDict()
    for i, legume in enumerate(legumes):
    self.dico_legumes[i] = (images[i], legume)

    (à noter que la ligne initialisation dico_legumes devrait être dans __init__, et que pour l’utilisation que vous faites l’OrderedDict ne me semble pas la structure la plus appropriée, une liste ferait très bien l’affaire, puisque vous n’utilisez qu’une suite d’entiers comme clés, mais je n’ai pas regardé en détail le reste du code pour voir s’il justifie l’OrderedDict).

    Et j’ajouterai que si j’étais vous je stockerais tout dans un seul fichier plutôt que deux 🙂 Python offre plusieurs solutions très simples pour enregistrer dans des fichiers des données structurées, comme le contenu d’un dictionnaire : pickle ou json pour du stockage dans de simples fichier texte (voir yaml, qui est disponible en module optionnel), anydb pour faire une petite base de données…

    Bon courage ! La mise en forme du code est un travail fastidieux au début, mais vous verrez qu’à terme c’est très gratifiant, et ça simplifie énormément la maintenance du code, et ça deviendra automatique pour vous en écrivant du nouveau code. Pour vous aider, vous pouvez utilisez des outils de vérification automatique de code, comme pylint.

    Aimé par 1 personne

    1. Bonjour,

      Merci beaucoup pour ce long commentaire rempli de bons conseils. Il me reste encore du pain sur la planche avant d’arriver à produire un code élégant et fonctionnel mais on va traiter les problèmes un par un.

      En tout cas, je suis bien content d’avoir déposé mon code sur Framagit et comme je l’ai dit dans un commentaire précédent, si j’avais su, je l’aurais fait beaucoup plus tôt. J’ai perdu du temps à travailler tout seul dans mon coin.

      J'aime

      1. « file » est un nom qui existe déjà en Python, c’est l’un des types standards. L’ajout d’un _ est une convention en Python quand on veut donner à une variable un nom qui existe déjà sans écraser le premier (ça vient du PEP-8, qui préconise le _ quand on veut utiliser un nom réservé pour une variable). Une autre solution aurait été d’appeler par exemple la variable legumes_file, mais pour ma part je préfère donner des noms concis aux variables qui ne sont utilisées que sur quelques lignes de code de suite (ici, à peine deux), et réserver les noms plus longs à des variables ayant un scope plus large, pour lesquelles il est plus important d’avoir un nom très explicite.

        Pour info, j’ai vu tout à l’heure que Packt offre aujourd’hui un livre électronique sur la programmation en Python (et ils en offrent régulièrement, je récupère tous leurs livres gratuits quotidiens depuis environ 3 mois via un script Python, et j’en ai déjà une bonne dizaine sur Python) : https://www.packtpub.com/packt/offers/free-learning
        J’ai jeté un œil au sommaire, il y a 3 chapitres de « bonnes pratiques ». Lecture probablement intéressante si vous avez le temps et si vous lisez bien l’anglais 🙂

        J'aime

    2. On peut peut-être se tutoyer. Oui, ça m’intéresse. Concernant le code, je suis en train de le reprendre en simplifiant les noms de variables et en supprimant les self inutiles.
      J’ai également essayé de placer la variable dico_legumes dans __init__ mais là, le programme réagit différemment : après avoir supprimé un légume du menu déroulant, lorsque je clique de nouveau sur le bouton pour dérouler le menu, le légume supprimé est toujours présent. Il faut que je quitte l’application et que je la relance pour qu’il disparaisse.

      Par contre, si j’initialise dico_legumes dans la méthode self.show_list_vegetables(), comme je l’avais fait, pas de problème, ça fonctionne.

      J'aime

      1. En effet, mauvaise lecture de ma part !

        À cause du commentaire non indenté après la boucle for qui rempli dico_legumes j’ai cru que la fonction s’arrêtait là et que dico_legumes devait bien être une variable d’instance.

        Mais en fait la fonction continue après le commentaire, et utilise dico_legumes pour peupler le menu. Ensuite, dico_legumes n’est plus utilisé ailleurs. Du coup, dico_legumes devrait être une variable interne de la méthode (donc pas de self.), pas une variable d’instance. Le fait de l’initialiser dans show_list_vegetables fait que la variable d’instance se comporte un peu comme une variable interne, et ça masque donc le problème de définition.

        C’est typiquement pour éviter ce genre de soucis qu’il ne faut mettre en variable d’instance que ce qui a « besoin » d’être une variable d’instance, et garder localement dans les fonctions et méthodes tout ce qui ne sert qu’à la fonction/méthode elle même.

        Donc deux solutions. La plus simple, c’est de passer à une vraie variable interne.
        La plus propre, c’est de revoir la façon dont tout ça est géré :
        * initialiser self.dico_legumes dans __init__,
        * créer une fonction load_legumes qui est appelée par __init__ et qui reprend le début de show_list_vegetables : lecture des fichiers et remplissage de self.dico_legumes
        * dans show_list_vegetables, ne garder que la deuxième partie de la fonction, qui crée la liste à partir du contenu de self.dico_legumes
        * dans remove_vegetables, en plus de supprimer le légume du fichier, le supprimer aussi de self.dico_legumes

        Avantage de cette solution : on ne lit le fichier qu’une seule fois, puis on utilise les informations qu’on a stockées en mémoire, au lieu d’aller lire le fichier à chaque fois qu’on affiche le menu. Ne lire qu’une fois le fichier justifie de stocker son contenu dans une variable d’instance. Si on le lit à chaque fois, inutile de le conserver dans l’instance.

        J'aime

    3. Bonjour,

      Finalement, j’ai choisi la solution la plus simple, c’est-à-dire créer une variable interne dico_legumes. Je reviendrai peut-être sur le sujet plus tard mais maintenant je veux me concentrer sur la correction du code des fichiers legume.py et fruit.py.
      Sur Framagit, j’ai réorganisé mon programme en créant un lanceur nommé jardin.py et un répertoire mon_jardin dans lequel j’ai renommé l’ancien fichier jardin.py en accueil.py.

      jardin.py lance donc accueil.py qui donne ensuite accès aux autres sous-applications.

      Bonne journée.

      J'aime

      1. Coucou Ordinosor !

        Belle appli ! Je voulais faire un pull request sur ton repo mais j’ai preféré faire une liste de suggestions dispo ici : https://pastebin.com/JxxP9Xcu

        Ca n’a rien a voir mais j’ai cru comprendre sur la video que tu avais fait Stevenson ! J’aime bien la rando et j’ai croisé quelques Stevensoneux quand j’ai fait le gr3 =)

        Bon courage pour la suite !

        J'aime

      2. Salut Tony,

        Merci beaucoup pour ton aide. Vraiment sympa d’avoir pris le temps de faire cette liste de suggestions qui me sera bien utile. Je vais prendre le temps de l’analyser et je t’enverrai une réponse plus longue. Dans un premier temps, je voulais juste te préciser que je code en python3, jamais en python2.
        À bientôt,

        Benoît.

        J'aime

      3. Cool pour Stenvenson, je me souvient qu’il débute au Puy En Velay et que le début est commun avec le gr3 puis se separe, j’avais fait cette portion commune avec une mignonette rencontrée au Puy, ha les souvenirs !

        Pour revenir au code c’est cool pour Python3, par contre du coup moi j’avais supposé que tu utilisais Python2 car j’avais une erreur d’import pour le module PIL. Apres un echec avec pip et en faisant un tour sur google, le site de PIL (http://www.pythonware.com/products/pil/) n’as pas de support pour Py3, ou alors j’ai rater quelquechose ?

        Si jamais tu te souviens comment tu l’as installé hésite pas a me dire, que je puisse tester ton programme =)

        Cordialement =)

        J'aime

  5. Pour PIL : il faut installer Pillow et sa marche. C’est un peu un piege a con qu’ils aient changé le nom !

    Bref maintenant je peux faire tourner ton programme c’est cool =)

    J'aime

    1. PIL a été abandonné par ses mainteneurs, et d’autres l’ont repris en faisant un fork. D’où le changement de nom, ce n’est plus le projet initial, c’est un projet dérivé.

      J'aime

    2. Apres avoir regardé la vidéo explicative du code (j’avais vu que la presentation de l’UI), je me rend compte que je suis allé un peux vite et j’avais pas vu que tu utilisais open() en mode append. Je croyait que c’etait juste des restes de test en mode read et donc a supprimés. Autant pour moi =)

      Et pour le doublon fruits/legumes je vois que tu le savais deja et que tu l’as quand meme gardé ! C’est pas joli joli haha !

      J'aime

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s