Recherche de site Web

Apprenez les bases de Python : Construisons un simple suivi des dépenses


Si vous avez déjà essayé d'élaborer un budget, vous savez à quel point il peut être difficile de trouver un suivi des dépenses qui fonctionne comme vous le souhaitez. Mais que diriez-vous de le créer vous-même ? Apprenons les bases de Python en créant un simple suivi des dépenses que vous pouvez réellement utiliser.

Définir les exigences de notre application de suivi des dépenses

Quand j'ai eu cette idée, je voulais créer une application qui soit plus qu'une simple application de terminal en ligne de commande. Nous avons créé quelques-unes de celles-ci (comme la simple liste de tâches à faire en utilisant Python). Pour celle-ci, je voulais utiliser une interface graphique, donc j'ai décidé que nous importerions la bibliothèque Tkinter pour avoir quelques éléments d'interface utilisateur utilisables.

Les bibliothèques nous permettent de réutiliser du code. Il existe généralement une bibliothèque pour la plupart des choses que vous pourriez vouloir faire en Python. Les importer évite de réécrire tout le code qu'elles contiennent depuis le début.

Dans cette application, nous allons mettre en œuvre :

  • Saisie de dépenses
  • Définir un budget
  • Comparer nos dépenses à notre budget
  • Voir l'historique des dépenses

À la fin de cela, nous aurons une idée décente de la façon dont Python fonctionne et nous serons capables de compléter notre première application Python basée sur une interface graphique.

Mise en place de notre projet

Vous devriez vérifier pour vous assurer que votre appareil a Python installé en vérifiant la version de Python. J'ai déjà expliqué comment lier mon IDE préféré (Visual Studio) à Python. Une fois que vous aurez surmonté le processus d'installation de Python sur votre appareil et que vous l'aurez mis à jour à la version actuelle, nous pourrons commencer par créer un nouveau projet.

J'ai choisi Python pour ce projet car c'est l'un des langages les plus faciles pour les débutants. Plongeons dans la construction de notre suiveur de dépenses !

Création de la fenêtre principale de l'application

Puisque nous allons nous éloigner du terminal dans cette application, nous devrons plutôt configurer Tkinter pour construire l'interface utilisateur graphique (GUI) de notre application. La conception de l'interface utilisateur en Python peut être complexe, donc je vais rester simple et vous expliquer ce que fait le code que je vous donne. Voici comment ma fenêtre UI sera définie. Nous allons commencer par importer Tkinter :

import tkinter as tk
from tkinter import ttk, messagebox, simpledialog

Tkinter comprend toutes les fonctions nécessaires pour créer une interface utilisateur de base. Vous remarquerez que nous importons également Ttk, un moteur de thème intégré à Tkinter. Cela nous permet de contrôler l'apparence de notre interface utilisateur si nous le souhaitons. Nous allons également utiliser messagebox et simpledialog pour demander à l'utilisateur de définir le budget initial. Nous voulons aussi être en mesure de sauvegarder les données mensuelles au cas où nous voudrions y accéder à l'avenir, donc nous ajouterons également en haut de notre fichier :

import json
from datetime import datetime

En plus d'être très proche de mon propre nom, JSON est en fait assez cool en tant que système de sérialisation. Heureusement, il est standard avec Python, donc nous pouvons l'importer directement. L'importation de datetime nous aide à garder nos dates correctement formatées à travers toute l'application.

Maintenant que nous avons compris les importations, construisons réellement la fenêtre :

def create_widgets(self):
    Main Frame
    main_frame = ttk.Frame(self.root, padding="10")
    main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
    Expense Input Frame
    input_frame = ttk.LabelFrame(main_frame, text="Add Expense", padding="10")
    input_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=5, pady=5)
    ttk.Label(input_frame, text="Date (YYYY-MM-DD):").grid(row=0, column=0, sticky=tk.W)
    self.date_entry = ttk.Entry(input_frame)
    self.date_entry.grid(row=0, column=1, padx=5, pady=2)
    ttk.Label(input_frame, text="Category:").grid(row=1, column=0, sticky=tk.W)
    self.category_combobox = ttk.Combobox(input_frame, values=self.categories)
    self.category_combobox.grid(row=1, column=1, padx=5, pady=2)
    ttk.Label(input_frame, text="Amount:").grid(row=2, column=0, sticky=tk.W)
    self.amount_entry = ttk.Entry(input_frame)
    self.amount_entry.grid(row=2, column=1, padx=5, pady=2)
    ttk.Button(input_frame, text="Add Expense", command=self.add_expense).grid(row=3, column=0, columnspan=2, pady=5)
    Category Management Frame
    category_frame = ttk.LabelFrame(main_frame, text="Manage Categories", padding="10")
    category_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=5, pady=5)
    self.new_category_entry = ttk.Entry(category_frame)
    self.new_category_entry.grid(row=0, column=0, padx=5, pady=2)
    ttk.Button(category_frame, text="Add Category", command=self.add_category).grid(row=0, column=1, padx=5, pady=2)

Ces entrées ne font que styliser notre fenêtre. Si vous le souhaitez, vous pouvez jouer avec les chiffres et voir ce que vous obtenez, car les manipuler est inoffensif (à part rendre la fenêtre finale terrible). Nous devons également afficher le cadre et le bouton de sauvegarde, et configurer la grille pour qu'elle ait l'air bien :

 Display Frame
 display_frame = ttk.LabelFrame(main_frame, text="Expenses and Budget", padding="10")
 display_frame.grid(row=0, column=1, rowspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), padx=5, pady=5)
 self.expense_tree = ttk.Treeview(display_frame, columns=('Date', 'Category', 'Amount'), show='headings')
 self.expense_tree.heading('Date', text='Date')
 self.expense_tree.heading('Category', text='Category')
 self.expense_tree.heading('Amount', text='Amount')
 self.expense_tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
 scrollbar = ttk.Scrollbar(display_frame, orient=tk.VERTICAL, command=self.expense_tree.yview)
 scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
 self.expense_tree.configure(yscrollcommand=scrollbar.set)
 self.budget_label = ttk.Label(display_frame, text=f"Monthly Budget: ${self.monthly_budget:.2f}")
 self.budget_label.grid(row=1, column=0, sticky=tk.W, pady=2)
 self.total_expenses_label = ttk.Label(display_frame, text="Total Expenses: $0.00")
 self.total_expenses_label.grid(row=2, column=0, sticky=tk.W, pady=2)
 self.remaining_budget_label = ttk.Label(display_frame, text=f"Remaining Budget: ${self.monthly_budget:.2f}")
 self.remaining_budget_label.grid(row=3, column=0, sticky=tk.W, pady=2)
 Save Button
 ttk.Button(display_frame, text="Save Data", command=self.save_data).grid(row=4, column=0, pady=10)
 Configure grid
 self.root.columnconfigure(0, weight=1)
 self.root.rowconfigure(0, weight=1)
 main_frame.columnconfigure(1, weight=1)
 main_frame.rowconfigure(0, weight=1)
 display_frame.columnconfigure(0, weight=1)
 display_frame.rowconfigure(0, weight=1)

Comme vous pouvez le voir, l'interface utilisateur nécessite beaucoup de codage pour être correcte. Cependant, puisque vous prévoyez d'apprendre les bases de Python, comprendre la conception de l'UI fait partie de l'ensemble. Maintenant que nous avons l'UI opérationnelle, travaillons sur la fonctionnalité.

Définir des catégories et ajouter des dépenses

Pour donner un peu de "corps" à notre application, j'ai prédefini quelques catégories de dépenses. Je les ai incluses en tant qu'en-têtes avec le reste de nos définitions :

class ExpenseTrackerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Expense Tracker & Budget Planner")
        self.root.geometry("1100x500")
        self.expenses = []
        self.categories = ["Rent", "Food", "Entertainment", "Car", "Credit Cards"]
        
        self.monthly_budget = self.get_initial_budget()
        
        self.create_widgets()
    def get_initial_budget(self):
        while True:
            budget = simpledialog.askfloat("Monthly Budget", "Enter your monthly budget:", minvalue=0.01)
            if budget is not None:
                return budget
            else:
                if messagebox.askyesno("No Budget", "You haven't entered a budget. Do you want to exit?"):
                    self.root.quit()
                    return 0

Des définitions comme celles-ci devraient être en haut de notre programme. Dans ce but, ce fragment devrait venir avant le code de création du widget que nous avons déjà développé.

La fonction get_initial_budget(self) crée une fenêtre contextuelle au début de l'exécution de l'application pour collecter des données déterminant le montant d'argent que vous avez à votre disposition. Ne pas entrer de valeur entraîne la fermeture immédiate de l'application.

Nous avons également besoin de la fonctionnalité pour ajouter des dépenses et mettre à jour les catégories avec de nouvelles catégories personnalisées. Heureusement, cela est également simple à mettre en œuvre :

def add_expense(self):
    try:
        date = self.date_entry.get()
        category = self.category_combobox.get()
        amount = float(self.amount_entry.get())
        self.expenses.append({
            'date': date,
            'category': category,
            'amount': amount
        })
        self.update_expense_list()
        self.update_budget_info()
        self.clear_input_fields()
    except ValueError:
        messagebox.showerror("Error", "Invalid input. Please check your entries.")
def add_category(self):
    new_category = self.new_category_entry.get().strip()
    if new_category and new_category not in self.categories:
        self.categories.append(new_category)
        self.category_combobox['values'] = self.categories
        self.new_category_entry.delete(0, tk.END)
        messagebox.showinfo("Category Added", f"'{new_category}' has been added to the categories.")
    else:
        messagebox.showerror("Error", "Invalid category name or category already exists.")

Pour compléter notre fonctionnalité de base, nous aurons besoin de fonctions pour mettre à jour la liste des dépenses lorsque nous saisissons une nouvelle dépense. Nous aurons besoin d'une fonction auxiliaire pour effacer la liste déroulante après avoir saisi les données. Enfin, nous devrons calculer combien de notre budget nous avons encore disponible. Ce simple extrait de code nous permettra de faire exactement cela :

def update_expense_list(self):
    for item in self.expense_tree.get_children():
        self.expense_tree.delete(item)
    for expense in self.expenses:
        self.expense_tree.insert('', 'end', values=(
            expense['date'],
            expense['category'],
            f"${expense['amount']:.2f}"
        ))
def update_budget_info(self):
    total_expenses = sum(expense['amount'] for expense in self.expenses)
    remaining_budget = self.monthly_budget - total_expenses
    self.total_expenses_label.config(text=f"Total Expenses: ${total_expenses:.2f}")
    self.remaining_budget_label.config(text=f"Remaining Budget: ${remaining_budget:.2f}")
def clear_input_fields(self):
    self.date_entry.delete(0, tk.END)
    self.category_combobox.set('')
    self.amount_entry.delete(0, tk.END)

Alors, après avoir saisi toutes ces données, comment veillons-nous à ne pas les perdre ? J'ai implémenté une fonction de sauvegarde simple en utilisant le format JSON que nous avons importé plus tôt :

def save_data(self):
    data = {
        'budget': self.monthly_budget,
        'categories': self.categories,
        'expenses': self.expenses
    }
    
    Get the current date to use in the filename
    current_date = datetime.now().strftime("%Y-%m")
    default_filename = f"expense_data_{current_date}.json"
    
    file_path = filedialog.asksaveasfilename(
        defaultextension=".json",
        filetypes=[("JSON files", "*.json")],
        initialfile=default_filename
    )
    
    if file_path:
        with open(file_path, 'w') as f:
            json.dump(data, f, indent=2)
        messagebox.showinfo("Save Successful", f"Data saved to {file_path}")

Cette fonction sérialise les données et les enregistre dans un emplacement de fichier particulier avec la date actuelle ajoutée au nom du fichier. La dernière chose que nous devons faire est de rassembler la boucle de la fonction main() :

if __name__ == "__main__":
    root = tk.Tk()
    app = ExpenseTrackerApp(root)
    root.mainloop()

Cela devrait permettre à notre traqueur de dépenses de fonctionner sans aucun problème. Félicitations pour avoir terminé votre première application basée sur une interface graphique en Python !

Ajouts, Lacunes et Personnalisation

Après avoir complété cela, vous comprenez un peu comment Tkinter fonctionne et comment vous pouvez l'utiliser pour la création d'interfaces graphiques. Si vous connaissez un peu de CSS ou un peu de design graphique, vous devriez être en mesure de comprendre comment utiliser Tkinter rapidement. Si vous souhaitez relever quelques défis, vous pourriez essayer ces choses ensuite :

  • Créez ou utilisez un nouveau thème pour votre application.
  • Créez une fonction "charger" et un bouton pour cela. J'ai spécifiquement omis la fonction de chargement pour vous donner plus de place pour explorer.
  • Explorez comment vous pourriez faire une décomposition graphique de votre budget sous forme de graphique à secteurs.

L'application finale devrait actuellement ressembler à quelque chose comme ça :

Où aller à partir d'ici ?

Ce tutoriel de développement ne vous a donné qu'un aperçu général de Tkinter, mais si vous prévoyez de créer d'autres applications basées sur une interface graphique, vous devrez mieux comprendre ce que cela peut faire. RealPython peut vous donner une idée de la façon d'utiliser Tkinter ou l'un des autres frameworks ou outils GUI disponibles. Python est assez facile à comprendre à un niveau de base, mais il y a tant de choses que vous pouvez explorer en apprenant les bases de Python. La seule chose qui limite ce que vous pouvez créer est votre imagination.

Si vous avez des difficultés avec le code et que vous souhaitez comparer votre script Python au mien, il est disponible sur mon GitHub.