Python Learningاز صفر تا نوشتن پروژه از حفظ
پروژه‌محور · ۸ تکه

کل برنامه در ۳۰ دقیقه، تکه به تکه

۱۴۶ خط۸ قدمدر VS Code
چطور کار کنیم
  • در VS Code یک فایل خالی shop_manager.py بساز.
  • «شروع» تایمر را بزن. هر قدم را خودت تایپ کن، کپی نکن؛ دست‌هایت باید یاد بگیرند.
  • قبل از تایپ، «نکته‌ها»ی هر قدم را بخوان. همین‌ها چیزی است که باید در ذهنت بماند.
  • هر جا گفته شده «الان اجرا کن»، اجرا کن تا خطا را همان‌جا بگیری، نه در آخر.
  • بعد از این اسپرینت، برای حفظ کردن برو سراغ درس نوشتن از حفظ.
۳۰:۰۰ هنوز شروع نشده

۱ورودی‌ها و مسیر فایل

۲ دقیقه · تا دقیقه‌ی ۲

چهار خط import و یک ثابت که مسیر فایل داده را نگه می‌دارد.

  • json برای ذخیره، os برای مسیر فایل، tkinter و ttk و messagebox برای پنجره.
  • FILE با __file__ ساخته می‌شود تا فایل داده همیشه کنار برنامه باشد.
shop_manager.py — خط ۱ تا ۶ · این را تایپ کن
import json
import os
import tkinter as tk
from tkinter import ttk, messagebox

FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'shop_data.json')
الان اجرا کن

فایل را ذخیره کن و اجرا کن. نباید هیچ خطایی بدهد (و هنوز هیچ چیزی هم نشان نمی‌دهد).

۲کلاس Shop: ساختن، ذخیره، بارگذاری

۵ دقیقه · تا دقیقه‌ی ۷

مغز برنامه: دیکشنری کالاها، لیست فروش‌ها، و دو متد که آن‌ها را در فایل JSON می‌نویسند و می‌خوانند.

  • سازنده: اول {} و [] خالی، بعد self.load().
  • save: open(FILE, 'w', ...) و json.dump(..., ensure_ascii=False, indent=2).
  • load: اگر فایل هست، داخل try بخوان؛ با get و مقدار پیش‌فرض؛ در except همه را خالی کن.
shop_manager.py — خط ۸ تا ۲۶ · این را تایپ کن
class Shop:
    def __init__(self):
        self.products = {}
        self.sales = []
        self.load()

    def save(self):
        with open(FILE, 'w', encoding='utf-8') as f:
            json.dump({'products': self.products, 'sales': self.sales}, f, ensure_ascii=False, indent=2)

    def load(self):
        if os.path.exists(FILE):
            try:
                with open(FILE, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                self.products = data.get('products', {})
                self.sales = data.get('sales', [])
            except (OSError, json.JSONDecodeError, AttributeError):
                self.products, self.sales = {}, []
الان اجرا کن

اجرا کن؛ هنوز خطا نباید بدهد.

۳افزودن، ویرایش، حذف

۴ دقیقه · تا دقیقه‌ی ۱۱

سه متد با یک الگو: چک کن ← تغییر بده ← self.save().

  • add: نام خالی یا تکراری نباشد؛ هر سه عدد بین صفر و بی‌نهایت.
  • edit: مثل add، ولی کالا باید وجود داشته باشد.
  • delete: کالا باید باشد؛ بعد del.
  • همه‌ی خطاها raise ValueError('...') هستند.
shop_manager.py — خط ۲۸ تا ۴۸ · این را تایپ کن
    def add(self, name, buy, sell, qty):
        if not name or name in self.products:
            raise ValueError('نام خالی یا کالای تکراری است')
        if not all(0 <= x < float('inf') for x in (buy, sell, qty)):
            raise ValueError('عدد منفی یا نامعتبر مجاز نیست')
        self.products[name] = {'buy': buy, 'sell': sell, 'qty': qty}
        self.save()

    def edit(self, name, buy, sell, qty):
        if name not in self.products:
            raise ValueError('کالا پیدا نشد')
        if not all(0 <= x < float('inf') for x in (buy, sell, qty)):
            raise ValueError('عدد منفی یا نامعتبر مجاز نیست')
        self.products[name] = {'buy': buy, 'sell': sell, 'qty': qty}
        self.save()

    def delete(self, name):
        if name not in self.products:
            raise ValueError('کالا پیدا نشد')
        del self.products[name]
        self.save()

۴فروش و گزارش

۳ دقیقه · تا دقیقه‌ی ۱۴

sell موجودی را کم می‌کند و رکورد فروش می‌سازد؛ report دو جمع برمی‌گرداند.

  • سه چک: کالا هست؟ تعداد مثبت است؟ موجودی کافی است؟
  • product = self.products[name] ارجاع است؛ product['qty'] -= qty اصل را کم می‌کند.
  • total = تعداد × قیمت فروش.
shop_manager.py — خط ۵۰ تا ۶۳ · این را تایپ کن
    def sell(self, name, qty):
        if name not in self.products:
            raise ValueError('کالا پیدا نشد')
        if qty <= 0:
            raise ValueError('تعداد باید مثبت باشد')
        product = self.products[name]
        if qty > product['qty']:
            raise ValueError('موجودی کافی نیست')
        product['qty'] -= qty
        self.sales.append({'name': name, 'qty': qty, 'total': qty * product['sell']})
        self.save()

    def report(self):
        return sum(s['qty'] for s in self.sales), sum(s['total'] for s in self.sales)
الان اجرا کن

تست سریع: موقتاً این سه خط را ته فایل بگذار و اجرا کن، بعد پاکشان کن:

s = Shop() ، s.add('test', 10, 15, 3) ، print(s.products, s.report())

باید کالای test را ببینی و (0, 0). یک فایل shop_data.json هم کنار برنامه ساخته می‌شود؛ قبل از ادامه پاکش کن.

۵پنجره و فرم

۳ دقیقه · تا دقیقه‌ی ۱۷

شیء فروشگاه، پنجره‌ی اصلی، و چهار ردیف برچسب + جعبه که در fields ذخیره می‌شوند.

  • root = tk.Tk()، title، geometry('760x560').
  • حلقه: for row, (key, label) in enumerate([...]):
  • برچسب در یک خط (.grid(...) پشتش)، جعبه در دو خط، چون grid مقدار None برمی‌گرداند.
shop_manager.py — خط ۶۵ تا ۷۵ · این را تایپ کن
shop = Shop()
root = tk.Tk()
root.title('مدیریت فروشگاه - Python')
root.geometry('760x560')

fields = {}
for row, (key, label) in enumerate([('name', 'نام کالا'), ('buy', 'قیمت خرید'), ('sell', 'قیمت فروش'), ('qty', 'تعداد')]):
    ttk.Label(root, text=label).grid(row=row, column=0, padx=8, pady=5, sticky='e')
    entry = ttk.Entry(root, width=28)
    entry.grid(row=row, column=1, padx=8, pady=5)
    fields[key] = entry
الان اجرا کن

موقتاً root.mainloop() را ته فایل بگذار و اجرا کن: پنجره با چهار جعبه باید باز شود. بعد آن خط را بردار.

۶خواندن فرم، run و توابع دکمه‌ها

۶ دقیقه · تا دقیقه‌ی ۲۳

values متن جعبه‌ها را به عدد تبدیل می‌کند، run هر دکمه را امن اجرا می‌کند، و شش تابع یک‌خطی کار دکمه‌ها را انجام می‌دهند.

  • values: strip برای نام، float برای قیمت‌ها، int برای تعداد؛ return چهارتایی.
  • run(action): try ← action() ← if action is not search: refresh()؛ دو except با messagebox.showerror.
  • add/edit با shop.add(*values())؛ delete و sell فقط جعبه‌های لازم را می‌خوانند.
  • report: f'...{count}\n...{income:,.0f}' در showinfo.
shop_manager.py — خط ۷۷ تا ۱۱۱ · این را تایپ کن
def values():
    name = fields['name'].get().strip()
    buy = float(fields['buy'].get())
    sell = float(fields['sell'].get())
    qty = int(fields['qty'].get())
    return name, buy, sell, qty

def run(action):
    try:
        action()
        if action is not search: refresh()
    except (ValueError, TypeError) as exc:
        messagebox.showerror('خطا', str(exc))
    except OSError as exc:
        messagebox.showerror('خطای فایل', str(exc))

def add():
    shop.add(*values())

def edit():
    shop.edit(*values())

def delete():
    shop.delete(fields['name'].get().strip())

def sell():
    shop.sell(fields['name'].get().strip(), int(fields['qty'].get()))

def search():
    name = fields['name'].get().strip().lower()
    refresh(name)

def report():
    count, income = shop.report()
    messagebox.showinfo('گزارش فروش', f'تعداد فروخته‌شده: {count}\nدرآمد کل فروش: {income:,.0f}')

۷جدول: refresh و choose

۳ دقیقه · تا دقیقه‌ی ۲۶

refresh جدول را خالی و دوباره پر می‌کند؛ choose ردیف کلیک‌شده را به فرم می‌برد.

  • refresh(query=''): حلقه‌ی get_children + delete، بعد حلقه‌ی items() + insert('', 'end', values=...).
  • choose(event): selection()؛ اگر خالی بود return؛ item(..., 'values')؛ برای هر جعبه delete(0, tk.END) و insert(0, value).
  • table هنوز ساخته نشده؛ اشکالی ندارد، تابع تا صدا زدن اجرا نمی‌شود.
shop_manager.py — خط ۱۱۳ تا ۱۲۷ · این را تایپ کن
def refresh(query=''):
    for item in table.get_children():
        table.delete(item)
    for name, p in shop.products.items():
        if query in name.lower():
            table.insert('', 'end', values=(name, p['buy'], p['sell'], p['qty']))

def choose(event):
    selection = table.selection()
    if not selection:
        return
    name, buy, sell_price, qty = table.item(selection[0], 'values')
    for key, value in [('name', name), ('buy', buy), ('sell', sell_price), ('qty', qty)]:
        fields[key].delete(0, tk.END)
        fields[key].insert(0, value)

۸دکمه‌ها، جدول و اجرا

۴ دقیقه · تا دقیقه‌ی ۳۰

هفت دکمه در یک قاب، جدول چهارستونه، اتصال کلیک ردیف، کشیدگی پنجره، پر کردن اولیه و mainloop.

  • lambda a=action: run(a) و چیدمان row=i // 4, column=i % 4.
  • Treeview(root, columns=columns, show='headings', height=13) و حلقه‌ی heading + column.
  • table.bind('<<TreeviewSelect>>', choose)، وزن ردیف ۵ و ستون ۳، refresh() و در آخر root.mainloop().
shop_manager.py — خط ۱۲۹ تا ۱۴۶ · این را تایپ کن
buttons = [('افزودن کالا', add), ('ویرایش کالا', edit), ('حذف کالا', delete),
           ('ثبت فروش', sell), ('جست‌وجو', search), ('نمایش همه', refresh), ('گزارش فروش', report)]
frame = ttk.Frame(root)
frame.grid(row=4, column=0, columnspan=4, padx=10, pady=12)
for i, (label, action) in enumerate(buttons):
    ttk.Button(frame, text=label, command=lambda a=action: run(a)).grid(row=i // 4, column=i % 4, padx=4, pady=4)

columns = ('name', 'buy', 'sell', 'qty')
table = ttk.Treeview(root, columns=columns, show='headings', height=13)
for key, label in [('name', 'نام کالا'), ('buy', 'قیمت خرید'), ('sell', 'قیمت فروش'), ('qty', 'موجودی')]:
    table.heading(key, text=label)
    table.column(key, width=150, anchor='center')
table.grid(row=5, column=0, columnspan=4, padx=10, pady=10, sticky='nsew')
table.bind('<<TreeviewSelect>>', choose)
root.grid_rowconfigure(5, weight=1)
root.grid_columnconfigure(3, weight=1)
refresh()
root.mainloop()
الان اجرا کن

اجرا کن. برنامه‌ی کامل باید باز شود. یک کالا اضافه کن، رویش کلیک کن، بفروش، گزارش بگیر، برنامه را ببند و دوباره باز کن.

تمام شد!

اگر برنامه کار کرد، فردا همین را بدون نگاه کردن تکرار کن. هر جا گیر کردی، فقط «نکته‌ها»ی همان قدم را نگاه کن. توضیح خط به خط هر تکه در مستندات پروژه است.