9 اشتباه متداول بین برنامه نویسان پایتون - قسمت اول

سینتکس به ظاهر ساده پایتون می تواند برنامه نویسانی که از این زبان استفاده می کنند، علی الخصوص برنامه نویسان تازه کار، را دراستفاده مناسب از دستورات این زبان گیج کند.این گیجی می تواند باعث شود که از بعضی ریزه کاری های این زبان نادانسته رد شوند و از تمام قدرت این زبان استفاده نکنند.برای آشنایی با این ریزه کاری ها و اشکالات متداول در برنامه های نوشته شده در پایتون، در این نوشته به 9 تا از این اشتباهات که حتی ممکن است توسط افراد خبره تر هم انجام شود می پردازیم.اشتباه 1 – استفاده اشتباه از ورودی های پیش فرض توابعپایتون به شما این انتخاب را می دهد که مشخص کنید که ورودی های یک تابع اختیاری هستند. این کار با مشخص کردن یک مقدار پیش فرض برای آن ورودی امکان پذیر است. با اینکه داشتن این انتخاب ویژگی بسیار خوبی است، ولی اگر مقدار این ورودی قابل تغییر باشد، می تواند باعث سردرگمی برنامه نویس شود. برای مثال تعریف تابع زیر را در نظر داشته باشید :>>> def foo(bar=[]): bar.append(&quotbaz&quot) Return barخط اول ما در حال تعریف تابع و ورودی آن هستیم. bar نامی است که برای متغیر ورودی تابع در نظر گرفته ایم و مقدار پیش فرض آن (مقداری که اگر در هنگام استفاده از تابع به آن ورودی ندهیم به عنوان ورودی پیش فرض در نظر گرفته می شود) را برابر [](لیست خالی، تهی) در نظر گرفته ایم. خط دوم تابع کاری است که می خواهیم بر روی ورودی انجام دهیم و خط سوم برای برگرداندن خروجی تابع است.اشتباهی که در این حالت صورت می گیرد تصور این است هر دفعه تابع را بدون ورودی استفاده می کنیم، ورودی ما با مقدار پیش فرض برابر خواهد بود. مثلا اگر در ادامه کد بالا چندبار تابع ()foo را بدون دادن ورودی استفاده کنیم، از آنجایی که تصور این است که هردفعه که تابع را بدون ورودی استفاده می کنیم ورودی برابر مقدار پیش فرضش (یک لیست تهی) خواهد بود، پس انتظار می رود که در هر دفعه خروجی ما همواره لیستی شامل عبارت 'baz' باشد( چون ورودی ما با اسم bar یک لیست تهی است و در خط دوم کد به انتهای آن لیست عبارت 'baz' را اضافه می کنیم انتظار می رود خروجی ما لیستی که تنها شامل عبارت'baz' است، باشد).حال اگر این کار را در واقعیت انجام دهیم (تابع را چندبار بدون مقدار ورودی صدا بزنیم)، نتیجه به شکل زیر خواهد بود :>>> foo() [&quotbaz&quot] >>> foo() [&quotbaz&quot, &quotbaz&quot] >>> foo() [&quotbaz&quot, &quotbaz&quot, &quotbaz&quot]چی شد؟ چرا به جای اینکه هردفعه فقط یک لیست شامل عبارت 'baz' برگردونه، هردفعه عبارت 'baz' رو به یک لیست قدیمی اضافه می کنه؟جواب این سوال این هستش که مقدار پیش فرض ورودی یک تابع فقط یک بار در نظر گرفته می شود و این کار در زمانی که تابع تعریف می شود اتفاق می افتد. به همین علت مقدار پیش فرض ورودی ما فقط در هنگامی که تابع در کد تعریف می شود، برابر یک لیست تهی است و در استفاده اول از این لیست خالی استفاده خواهد شد. در دفعات بعدی دیگر یک لیست خالی ساخته نمی شود، بلکه از همان لیست قدیمی که دارای عضو است استفاده می شود. راه حل معمول این مشکل، نوشتن کد به شکل زیر است :>>> def foo(bar=None): ... if bar is None: ... bar = [] ... bar.append(&quotbaz&quot) ... return barکه باعث می شود در هردفعه صدا کردن این تابع، از یک لیست تهی به عنوان ورودی پیش فرض استفاده شود.اشتباه 2 – استفاده نامناسب از متغیر های کلاس (class variables)کد زیر رو در نظر بگیرید :>>> class A(object): ... x = 1 ... >>> class B(A): ... pass ... >>> class C(A): ... passحال اگر دستور زیر رو وارد کنیم :>>> print A.x, B.x, C.xخروجی ها به این شکل خواهند بود:1 1 1که کاملا مطابق انتظارات است و مشکلی ندارد.اگر در ادامه متغیر x یکی از دو کلاس B و یا C را تغییر دهیم و دوباره دستور بالا را وارد کنیم :>>> B.x = 2 >>> print A.x, B.x, C.x 1 2 1می بینیم که خروجی ما مشکلی ندارد و مطابق انتظار ماست.ولی اگر مقدار متغیر x برای کلاس A را تغییر دهیم و همین کار را تکرار کنیم می بینیم که نتیجه متفاوت خواهد بود.>>> A.x = 3 >>> print A.x, B.x, C.x 3 2 3چی شد؟ ما که تنها متغیر xبرای کلاس A را تغییر داده ایم، چرا مقدار C.xهم تغییر کرد؟در کد بالا از آنجایی که متغیر xبرای کلاس C تعریف نشده است، برای این ویژگی در کلاس های پایه ای آن جستجو خواهد شد (که در مثال بالا در کلاس A پیدا خواهد شد). به عبارت دیگر، متغیر xکلاس C یک متغیر وابسته به کلاس Aخواهد بود و درواقع فراخوانی متغیر C.x باعث فراخوانی A.x خواهد شد.این مشکل به این علت برای کلاس B در قسمت دوم کد بوجود نیامد که به آن در قسمت دوم دستورات به طور جداگانه و مستقیم مقداری اختصاص داده شد.اشتباه 3 – به اشتباه مشخص کردن پارامترهای یک دستور استثنا (exception) در کدکد زیر را در نظر بگیرید>>> try: ... l = [&quota&quot, &quotb&quot] ... int(l[2]) ... except ValueError, IndexError: ... passقسمت جلوی exceptبرای گرفتن دو ارورValueError وIndex Error نوشته شده است و مشکلی ندارد دیگر، نه؟وقتی کد را اجرا کنیم با پیام زیر روبه رو خواهیم شد :Traceback (most recent call last): File &quot<stdin>&quot, line 3, in <module> IndexError: list index out of rangeپس مشکل ما چیست؟مشکل این است که دستور except یک لیست از مشکلات را به این نحو دریافت نمی کند.برای این کار باید یک tuple معرفی کرده که در آن تمامی مشکلات مورد نظر موجود باشند. برای کمتر کردن مشکلات احتمالی و راحت تر کردن انتقال کد از یک as در انتهای کد نیز می توانید استفاده کنید، با این کار این دستور هم با پایتون 2 و هم پایتون 3 سازگار خواهد بود. بدین شکل :>>> try: ... l = [&quota&quot, &quotb&quot] ... int(l[2]) ... except (ValueError, IndexError) as e: ... passاشتباه 4 – تغییردادن یک لیست در حین خواندن آنکد زیر را در نظر داشته باشید :>>> odd = lambda x : bool(x % 2) >>> numbers = [n for n in range(10)] >>> for i in range(len(numbers)): ... if odd(numbers[i]): ... del numbers[i]اگر این کد را اجرا کنیم، با ارورهای زیر روبه رو خواهیم شد:Traceback (most recent call last): File &quot<stdin>&quot, line 2, in <module> IndexError: list index out of rangeمشکل این کد چیست؟مشکل این کد در خط 4 آن است. دستور آن خط جسمی که ما در حال خواندن از روی آن هستیم را حذف می کند که باعث خطا در اجرای کد می شود. این اشتباه بین افرادی که بیشتر با پایتون آشناتر هستند شناخته شده است، ولی گاهی ممکن است حتی آنان نیز به اشتباه این کار را انجام دهند.در پایتون راه های زیادی برای جلوگیری از این خطا داریم. یکی از راه هایی که برای جلوگیری از این مشکل داریم نوشتن کد بالا به نحوه زیر است :>>> odd = lambda x : bool(x % 2) >>> numbers = [n for n in range(10)] >>> numbers[:] = [n for n in numbers if not odd(n)]با نوشتن کد به این روش دیگر یک عضو یک لیست را در حین خواندن حذف نمی کنیم. بلکه در حین خواندن لیست یک لیست ثانویه از اعدادی که شرایط ما را شامل می شوند می سازیم و در نهایت، بعد از اتمام خواندن، لیست اولیه را تماما با لیست ثانویه جایگزین می کنیم (اعضای لیست ثانویه را در لیست اولیه میریزیم).اشتباه 5 – دقت نکردن به نحوه ای که پایتون با متغیرها برخورد می کندکد زیر را در نظر داشته باشید :>>> def create_multipliers(): ... return [lambda x : i * x for i in range(5)] >>> for multiplier in create_multipliers(): ... print multiplier(2)قاعدتا شما انتظار خروجی زیر را خواهید داشت :0 2 4 6 8ولی خروجی که شما دریافت خواهید کرد به شکل زیر خواهد بود :8 8 8 8 8عجیبه! چرا؟این اتفاق به دلیل نحوه برخورد پایتون با متغیر هاست. این نحوه برخورد باعث می شود مقدار متغیرهایی که در یک قسمت استفاده می شوند، تازه در هنگامی درخواست شوند که تابع شروع به کار میکند. پس در کد بالا، هردفعه که قسمت دوم نتیجه return را درخواست می کند مقدار متغیر در لحظه درخواست به آن تحویل داده می شود، که در نتیجه کامل شدن حلقه قسمت اول قبل از دومی برابر 4 خواهد بود.راه حل این مشکل نوشتن کد به صورت زیر خواهد بود :>>> def create_multipliers(): ... return [lambda x, i=i : i * x for i in range(5)] ... >>> for multiplier in create_multipliers(): ... print multiplier(2)که به ما خروجی موردنظر را خواهد داد.امیر هستم از ایران پایتونیرز، ممنونم از وقتی که برای خوندن قسمت اول این پست گذاشتید.در این قسمت به 5 تا از 9 اشتباه متداولی که برنامه نویسان پایتون انجام میدن پرداختیم. در قسمت دوم این پست باقی این اشتباهات رو برای شما می نویسیم.ما به تازگی استفاده از پلتفرم ویرگول رو شروع کردیم و به زودی مطالب بیشتری هم اینجا قرار داده میشه.برای دیدن ویدیو های آموزشی دیگری که دوستانم برای شما ساخته اند، می تونید به کانال ما در آپارات ، یوتیوب و یا تلگرام مراجعه کنید.منبع : www.toptal.com