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

خط ۲۸ تا ۴۸

سه متد بعدی کلاس، دیکشنری کالاها را تغییر می‌دهند. الگوی هر سه یکی است: اول ورودی بررسی می‌شود و اگر ایرادی داشت با raise ValueError متوقف می‌شود، بعد تغییر انجام می‌شود و در آخر save همه‌چیز را در فایل می‌نویسد.

کد این بخش

shop_manager.py, lines 28–48
28    def add(self, name, buy, sell, qty):29        if not name or name in self.products:30            raise ValueError('نام خالی یا کالای تکراری است')31        if not all(0 <= x < float('inf') for x in (buy, sell, qty)):32            raise ValueError('عدد منفی یا نامعتبر مجاز نیست')33        self.products[name] = {'buy': buy, 'sell': sell, 'qty': qty}34        self.save()35 36    def edit(self, name, buy, sell, qty):37        if name not in self.products:38            raise ValueError('کالا پیدا نشد')39        if not all(0 <= x < float('inf') for x in (buy, sell, qty)):40            raise ValueError('عدد منفی یا نامعتبر مجاز نیست')41        self.products[name] = {'buy': buy, 'sell': sell, 'qty': qty}42        self.save()43 44    def delete(self, name):45        if name not in self.products:46            raise ValueError('کالا پیدا نشد')47        del self.products[name]48        self.save()

روی شماره‌ی هر خط بزن تا به توضیحش بروی؛ روی «خط …» در توضیح بزن تا همان خط در کد روشن شود.

توضیح خط به خط

خط ۲۸def add(self, name, buy, sell, qty):
متد add با چهار ورودی: نام، قیمت خرید، قیمت فروش و تعداد. این متد از تابع add بیرون کلاس (خط ۹۴) صدا زده می‌شود.
خط ۲۹if not name or name in self.products:
دو شرط با or: نام خالی است، یا این نام از قبل در دیکشنری هست. رشته‌ی خالی '' در پایتون «نادرست» حساب می‌شود، پس not name برای نام خالی True است.
خط ۳۰raise ValueError('نام خالی یا کالای تکراری است')
یک خطای ValueError با پیام فارسی «پرتاب» می‌کند. با raise اجرای متد همین‌جا تمام می‌شود و هیچ کالایی اضافه نمی‌شود. این خطا در تابع run (خط ۸۸) گرفته و به کاربر نشان داده می‌شود.
خط ۳۱if not all(0 <= x < float('inf') for x in (buy, sell, qty)):
all وقتی True است که شرط برای همه‌ی اعضا درست باشد. اینجا هر سه عدد باید بین صفر و بی‌نهایت باشند: 0 <= x < float('inf'). این شرط سه چیز را رد می‌کند: عدد منفی، بی‌نهایت (inf) و «نه‌عدد» (nan). float متن‌های nan و inf را بدون خطا به عدد تبدیل می‌کند و هر مقایسه‌ای با nan نادرست است، پس 0 <= nan هم نادرست است و رد می‌شود. not نتیجه را برعکس می‌کند: اگر یکی نامعتبر بود، خطا بده.
خط ۳۲raise ValueError('عدد منفی یا نامعتبر مجاز نیست')
خطای «عدد منفی یا نامعتبر مجاز نیست».
خط ۳۳self.products[name] = {'buy': buy, 'sell': sell, 'qty': qty}
اگر همه‌چیز درست بود، کالا با نامش به‌عنوان کلید و یک دیکشنری سه‌تایی به‌عنوان مقدار ثبت می‌شود.
خط ۳۴self.save()
تغییر را بلافاصله در فایل ذخیره می‌کند (خط ۱۴).
خط ۳۵
خط خالی.
خط ۳۶def edit(self, name, buy, sell, qty):
متد edit همان چهار ورودی را دارد و از تابع edit بیرون کلاس (خط ۹۷) صدا زده می‌شود.
خط ۳۷if name not in self.products:
برعکس add: اینجا کالا باید از قبل وجود داشته باشد.
خط ۳۸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):
متد delete فقط نام را می‌گیرد. از تابع delete بیرون کلاس (خط ۱۰۰) صدا زده می‌شود.
خط ۴۵if name not in self.products:
اگر کالا وجود ندارد...
خط ۴۶raise ValueError('کالا پیدا نشد')
...خطای «کالا پیدا نشد».
خط ۴۷del self.products[name]
del کلید را همراه مقدارش از دیکشنری پاک می‌کند. فروش‌های ثبت‌شده‌ی این کالا در self.sales دست نمی‌خورند، پس گزارش فروش بعد از حذف تغییری نمی‌کند.
خط ۴۸self.save()
ذخیره در فایل.

یادداشت

add و edit تقریباً یکسان‌اند؛ تنها فرقشان شرط اول است (کالا نباید باشد / باید باشد). بررسی عدد (خط ۳۱ و خط ۳۹) تکرار شده و می‌شد آن را در یک متد کمکی گذاشت. در نسخه‌ی قبلی این خط if min(buy, sell, qty) < 0: بود که nan و inf را می‌پذیرفت؛ با نوشتن nan در قیمت، کالا ثبت می‌شد و NaN در فایل ذخیره می‌شد که JSON استاندارد نیست.

ارتباط با بقیه‌ی کد

همچنین ببینید

Shop.add
با دکمه‌ی «افزودن کالا»: دکمه خط ۱۳۴ ← run خط ۸۴ ← تابع add خط ۹۳ ← shop.add(*values()) خط ۹۴.
Shop.edit
با دکمه‌ی «ویرایش کالا»: تابع edit خط ۹۶ ← خط ۹۷.
Shop.delete
با دکمه‌ی «حذف کالا»: تابع delete خط ۹۹ ← خط ۱۰۰. با table.delete در خط ۱۱۵ که فقط یک ردیف جدول را پاک می‌کند اشتباه نشود.
ValueError
همه‌ی raiseهای این بخش در خط ۸۸ گرفته می‌شوند و با messagebox.showerror در خط ۸۹ نمایش داده می‌شوند.
values()
چهار مقدار name, buy, sell, qty را دقیقاً به همان ترتیب پارامترهای add و edit برمی‌گرداند (خط ۸۲).

سؤال‌های تمرینی

اول خودت جواب بده، بعد جواب را باز کن.

  1. چرا not name در خط ۲۹ نام خالی را تشخیص می‌دهد؟

    نمایش جواب

    رشته‌ی خالی «نادرست» حساب می‌شود، پس not '' برابر True است.

  2. کاربر در قیمت خرید nan تایپ می‌کند. کدام خط جلویش را می‌گیرد و چرا؟

    نمایش جواب

    خط ۳۱: هر مقایسه‌ای با nan نادرست است، پس 0 <= nan نادرست، all(...) نادرست و not all(...) درست می‌شود و خط ۳۲ خطا می‌دهد.

  3. آیا با edit می‌شود نام کالا را عوض کرد؟

    نمایش جواب

    نه. نام کلید دیکشنری است؛ نام جدید در خط ۳۷ پیدا نمی‌شود و edit خطای «کالا پیدا نشد» می‌دهد.

  4. بعد از حذف یک کالا، گزارش فروش تغییر می‌کند؟

    نمایش جواب

    نه. خط ۴۷ فقط آن را از self.products پاک می‌کند و فروش‌هایش در self.sales می‌مانند.