کل برنامه در ۳۰ دقیقه، تکه به تکه
- در VS Code یک فایل خالی
shop_manager.pyبساز. - «شروع» تایمر را بزن. هر قدم را خودت تایپ کن، کپی نکن؛ دستهایت باید یاد بگیرند.
- قبل از تایپ، «نکتهها»ی هر قدم را بخوان. همینها چیزی است که باید در ذهنت بماند.
- هر جا گفته شده «الان اجرا کن»، اجرا کن تا خطا را همانجا بگیری، نه در آخر.
- بعد از این اسپرینت، برای حفظ کردن برو سراغ درس نوشتن از حفظ.
۱ورودیها و مسیر فایل
۲ دقیقه · تا دقیقهی ۲چهار خط import و یک ثابت که مسیر فایل داده را نگه میدارد.
jsonبرای ذخیره،osبرای مسیر فایل،tkinterوttkوmessageboxبرای پنجره.FILEبا__file__ساخته میشود تا فایل داده همیشه کنار برنامه باشد.
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همه را خالی کن.
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('...')هستند.
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= تعداد × قیمت فروش.
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 = 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.
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هنوز ساخته نشده؛ اشکالی ندارد، تابع تا صدا زدن اجرا نمیشود.
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().
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()
اجرا کن. برنامهی کامل باید باز شود. یک کالا اضافه کن، رویش کلیک کن، بفروش، گزارش بگیر، برنامه را ببند و دوباره باز کن.
اگر برنامه کار کرد، فردا همین را بدون نگاه کردن تکرار کن. هر جا گیر کردی، فقط «نکتهها»ی همان قدم را نگاه کن. توضیح خط به خط هر تکه در مستندات پروژه است.