آموزشنرم افزار

بررسی اشتباهات رایج در کدنویسی به زبان پایتون

بررسی 10 اشتباه کدنویسی در پایتون و نحوه اجتناب از آنها

زبان پایتون (Python) انعطاف‌پذیر، سرگرم‌کننده و آسان برای یادگیری است، اما مانند هر زبان برنامه‌نویسی دیگری، برخی از خطاهای رایج وجود دارد که اگر خصلت‌های پایتون را درک نکنید، ممکن است باعث سردرگمی شوید. در این مقاله از وبلاگ آذردیتا با معرفی ۱۰ مورد از رایج ترین ارور های python در کدنویسی به زبان پایتون که به راحتی می توانید از آنها اجتناب کنید، به شما کمک می کنیم در زمان خود صرفه جویی کنید و کد های بهینه پایتون را بنویسید؛ پس تا انتهای این مقاله با آذردیتا همراه باشید.

اسکریپت های بدون Main

اولین اشتباه از اشتباهات رایج در کدنویسی که منجر به ارور های python میشود  به زبان پایتون اسکریپت های بدون Main است.

بیایید با اشتباه یک مبتدی شروع کنیم که اگر به سرعت تشخیص داده نشود، می تواند مشکلاتی را برای شما ایجاد کند. از آنجایی که پایتون یک زبان برنامه نویسی است، می توانید توابعی را تعریف کنید که می توانید آنها را در حالت REPL فراخوانی کنید. مثلا:

def super_fun():

   print("Hello, I will give you super powers")

super_fun()

این کد ساده وقتی تابع super_fun را از CLI python no_main_func.py فراخوانی می‌کند، اجرا میشود. اما چه اتفاقی می افتد که مثلاً بخواهید از کد به عنوان یک ماژول در یک نوت بوک مجدد استفاده کنید؟

top 10 coding mistakes in python how to avoid them 01 - بررسی اشتباهات رایج در کدنویسی به زبان پایتونهمانطور که می بینید، زمانی که اسکریپت خود را وارد می کنید، super_fun به طور خودکار اجرا می شود. این یک مثال بی ضرر است، اما تصور کنید که تابع شما برخی از عملیات محاسباتی سنگین را انجام می دهد یا فرآیندی را فراخوانی می کند که چندین رشته ایجاد می کند. شما نمی خواهید این موارد را به طور خودکار در هنگام ایمپورت اجرا کنید، بنابراین چگونه می توانید از این اتفاق جلوگیری کنید؟

یکی از راه‌ها استفاده از یک نسخه اصلاح‌شده از محدوده استاندارد ساده اجرای اسکریپت __main__ به شرح زیر است:

def super_fun():

   print("Hello, I will give you super powers")


if __name__ == "__main__":

   # execute only if run as a script

   super_fun()

با استفاده از این کد، همان نتایجی را دریافت می‌کنید که قبلاً هنگام فراخوانی فایل اسکریپت از CLI دریافت می‌کردید، اما این بار وقتی آن را به عنوان یک ماژول وارد می‌کنید، رفتارهای غیرمنتظره‌ای دریافت نمی‌کنید.

انواع داده شناور

یکی دیگر از اشتباهات رایج در کدنویسی به زبان پایتون استفاده نادرست انواع داده است.

یکی دیگر از مشکلات رایج برای مبتدیان مدیریت نقطه شناور در پایتون است. تازه کارها اغلب به اشتباه Float را یک نوع ساده می دانند. به عنوان مثال، کد زیر تفاوت بین مقایسه شناسه انواع ساده (مانند اعداد int) با انواع شناور را نشان می دهد:

>>> a = 10

>>> b = 10

>>>

>>> id(a) == id(b)

True

>>> c = 10.0

>>> d = 10.0

>>>

>>> id(c) == id(d)

False

>>> print(id(a), id(b))

۹۷۸۸۸۹۶ ۹۷۸۸۸۹۶

>>> print(id(c), id(d))

۱۴۰۵۳۸۴۱۱۱۵۷۵۸۴ ۱۴۰۵۳۸۴۱۰۵۵۹۷۲۸

انواع ممیز شناور نیز یک ویژگی ظریف اما مهم دارند: نمایش داخلی آنها کد زیر از یک عملیات حسابی ساده استفاده می کند که حل آن باید ساده باشد، اما عملگر مقایسه == نتایج غیرمنتظره ای به شما می دهد:

>>> a = (0.3 * 3) + 0.1

>>> b = 1.0

>>> a == b

False

دلیل نتیجه غیرمنتظره این است که عملیات ممیز شناور به دلیل نمایش داخلی خود می توانند تفاوت های جزئی (یا حتی قابل توجهی) داشته باشند. تابع زیر به شما امکان می دهد شناورها را با استفاده از قدر مطلق تفاوت بین آنها مقایسه کنید:

def super_fun(a:float, b:float):

   return True if abs(a-b) < 1e-9 else False


if __name__ == "__main__":

   # execute only if run as a script

   print(super_fun((0.3*3 + 0.1),1.0))

سردرگمی بولی :(

یکی دیگر از اشتباهات رایج در کدنویسی به زبان پایتون استفاده نادرست از بولی ها است.

تعریف چیزی که باید به عنوان مقدار “واقعی” بولی در نظر گرفته شود، منبع اصلی سردرگمی در چندین زبان برنامه نویسی است و پایتون نیز از این قاعده مستثنی نیست. مقایسه های زیر را در نظر بگیرید:

>>> 0 == False

True

>>> 0.0 == False

True

>>> [] == False

False

>>> {} == False

False

>>> set() == False

False

>>> bool(None) == False

True

>>> None == False

False

>>> None == True

False

همانطور که می بینید، مقدار صفر برای هر نوع داده عددی “نادرست” در نظر گرفته می شود، اما مجموعه های خالی مانند لیست ها، مجموعه ها یا دیکشنری ها اینطور نیستند. به خاطر داشته باشید که “هیچ” با “درست” و “نادرست” متفاوت است. این می تواند مشکل ساز باشد، زیرا یک متغیر می تواند تعریف نشده باشد، اما بعداً در مقایسه ای استفاده می شود که نتایج غیرمنتظره ای ایجاد می کند.

اسکریپت های غیر قابل توقف

زمانی که مبتدیان می خواهند حلقه های بی نهایت را در داخل اسکریپت های خود اجرا کنند، در حالی که توانایی خود را برای متوقف کردن آنها حفظ می کنند، نوع کمی متفاوت از مشکل رخ می دهد. به عنوان مثال، حلقه زیر را بررسی کنید:

while True:

   try:

       print("Run Forest, run")

       time.sleep(5)

   except:

       print("Forrest cannot stop")


Run Forest, run

^CStop Forrest

Run Forest, run

^CStop Forrest

Run Forest, run

Run Forest, run

همانطور که ممکن است متوجه شده باشید، حتی قدرت ^C برای متوقف کردن این حلقه کافی نیست. اما چرا؟ این به این دلیل است که بلوک try/except حتی KeyboardInterrupt را می گیرد. اگر می خواهید فقط استثناها را بگیرید، باید در مورد آن صریح باشید:

while True:

   try:

       print("Run Forest, run")

       time.sleep(5)

   except Exception:

       print("Forrest cannot stop")


Run Forest, run

Run Forest, run

Run Forest, run

^CTraceback (most recent call last):

 File "with_main_func.py", line 6, in <module>

   time.sleep(5)

KeyboardInterrupt

اما KeyboardInterrupt نیز از BaseException به ارث می‌برد، بنابراین می‌توانید به راحتی آن را بگیرید و مدیریت کنید:

while True:

   try:

       print("Run Forest, run")

       time.sleep(5)

   except Exception:

       print("Forrest cannot stop")

   except KeyboardInterrupt:

       print("Ok, Forrest will stop now")

       exit(0)


Run Forest, run

Run Forest, run

^COk, Forrest will stop now

ماژول کلش

یکی از سوالاتی که هر از گاهی در تابلوهای بحث سایت های برنامه نویسی معروف مطرح می شود، مربوط به اشتباهات و ارور های python مربوط به نام ماژول های پایتون است. به عنوان مثال، بیایید تصور کنیم که باید یک مسئله پیچیده ریاضی را حل کنید، بنابراین یک اسکریپت math.py با کد زیر ایجاد کنید:

from math import abs

def complicated_calculation(a,b):

   return abs(a - b) > 0


if __name__ == "__main__":

  # execute only if run as a script

  complicated_calculation()

اگر این اسکریپت را اجرا کنید، یک stack trace مانند زیر به شما می دهد:

$ python3 math.py

Traceback (most recent call last):

 File "math.py", line 1, in <module>

   from math import abs

ImportError: cannot import name 'abs' from 'math' (unknown location)

باید واضح باشد که چه اتفاقی می افتد: شما ماژول خود را به گونه ای نام گذاری کردید که با یکی از کتابخانه های استاندارد در تضاد باشد. گاهی اوقات به دلیل درخت وابستگی که معمولاً برای پروژه‌های غیر ضروری ساخته می‌شود، می‌تواند حتی پیچیده‌تر باشد.

ارور های python : آرگومان های تابع قابل تغییر

گاهی اوقات استفاده نادرست از انواع قابل تغییر می تواند منجر به رفتارهای غیرمنتظره شود. به عنوان مثال، قطعه زیر را در نظر بگیرید:

def list_init(alist=[]):

   alist.append('Initialize with a value')

   return alist


if __name__ == "__main__":

  # execute only if run as a script

  a = list_init()

  print(id(a), a)

  b = list_init()

  print(id(b), b)

  c = list_init()

  print(id(c), c)

ممکن است فکر کنید که هدف از تابع list_init اضافه کردن یک مقدار پیش‌فرض جدید است، بنابراین فراخوانی آن بدون آرگومان باید فهرستی به طول ۱ را برگرداند. با این حال، نتایج شما را شگفت‌زده خواهد کرد:

$ python3 mutable_args.py

۱۴۰۰۸۲۳۶۹۲۱۳۵۶۸ ['Initialize with a value']

۱۴۰۰۸۲۳۶۹۲۱۳۵۶۸ ['Initialize with a value', 'Initialize with a value']

۱۴۰۰۸۲۳۶۹۲۱۳۵۶۸ ['Initialize with a value', 'Initialize with a value', 'Initialize with a value']

چه خطایی رخ داده است؟ نوع لیست قابل تغییر است و با توجه به اینکه مقدار پیش فرض آرگومان های یک تابع تنها در زمانی که تابع در پایتون تعریف می شود ارزیابی می شود، لیست خالی در فراخوانی های بعدی ارجاع داده می شود.

خوشبختانه، یک تغییر ساده می تواند رفتار دلخواه را به شما بدهد:

def list_init(alist=None):

   if not alist:

       alist = []


   alist.append('Initialize with a value')

   return alist


if __name__ == "__main__":

  # execute only if run as a script

  a = list_init()

  print(id(a), a)

  b = list_init()

  print(id(b), b)

  c = list_init()

  print(id(c), c)

فهرست جهش در داخل تکرار

بسیاری از توسعه دهندگان می خواهند یک مجموعه را در حین تکرار آن جهش دهند یا iterate کنند. به عنوان مثال، فیلتر کردن یک لیست به صورت دستی را به صورت زیر تصور کنید:

list_nums = list(range(16))

for idx in range(len(list_nums)):

   n = list_nums[idx]

   if n % 3 == 0:

       del list_nums[idx]

print(list_nums)

این کد خروجی شبیه به زیر را دارد:

$ python3 list_mutation.py

Traceback (most recent call last):

 File "math.py", line 3, in <module>

   n = list_nums[idx]

IndexError: list index out of range

خوشبختانه، پایتون چندین تکنیک را برای انجام چنین چیزی بدون نوشتن کد زیاد به شما ارائه می دهد. به عنوان مثال، می توانید یک لیست را به این ترتیب فیلتر کنید:

list_nums = list(range(16))

list_nums = [n for n in list_nums if n % 3 !=0]

print(list_nums)

مراجع، کپی ها و کپی های عمیق

بسیاری از برنامه نویسان با اصلاح متغیرهایی که قرار است کپی مستقلی از متغیرهای دیگر باشند، دست و پنجه نرم می کنند. این زمانی اتفاق می افتد که فکر می کنید یک کپی مستقل از یک متغیر ایجاد کرده اید، اما در واقع فقط یک اشاره گر برای آن ایجاد کرده اید.

به عنوان مثال، هنگامی که از عملگر انتساب = به روش زیر استفاده می کنید، می توانید یک مرجع به یک متغیر دریافت کنید:

Python 3.8.10 (default, Jun  ۲ ۲۰۲۱, ۱۰:۴۹:۱۵)

[GCC 9.4.0] on linux

Type "help", "copyright", "credits" or "license" for more information.

>>> d1 = {'k':[1,2,3]}

>>> d2 = d1

>>> print('d1 and d2 points to the same object',id(d1), id(d2))

d1 and d2 points to the same object 140265839379264 140265839379264

همانطور که می بینید، دیکشنری های d1 و d2 دارای شناسه یکسان هستند.

حال بیایید سعی کنیم یک کپی مستقل از یک متغیر را با استفاده از متد copy() دریافت کنیم:

>>> d2['k'].append(99)

>>> d3 = d1.copy()

>>> print('d3 is a different object but its contents points to d1 contents',id(d3), d1, d2, d3)

d3 is a different object but its contents points to d1 contents 140674662142848 {'k': [1, 2, 3, 99]} {'k': [1, 2, 3, 99]} {'k': [1, 2, 3, 99]}

در این مورد، ممکن است فکر کنید که تغییر d2 روی d1 تأثیر نمی گذارد، اما در واقع هر دوی آنها اصلاح می شوند زیرا آنها مرجع مشابهی دارند. به اندازه کافی عجیب، d3 نیز اصلاح می شود، حتی اگر مرجع متفاوتی باشد. به این دلیل که d3 یک کپی کم عمق از d1 است، به این معنی که محتوای آن به موارد مشابه اشاره دارد.

برای به دست آوردن یک نمونه کاملا متفاوت از یک متغیر با محتویات خاص خود که می توانید آن را تغییر دهید، باید از یک کپی عمیق استفاده کنید:

>>> import copy

>>> d4 = copy.deepcopy(d1)

>>> d3['k'].append(199)

>>> print('d4 is a different object AND its contents is also independent',id(d4), d1, d2, d3, d4)

d4 is a different object AND its contents is also independent 139809259201088 {'k': [1, 2, 3, 99, 199]} {'k': [1, 2, 3, 99, 199]} {'k': [1, 2, 3, 99, 199]} {'k': [1, 2, 3, 99]}

توجه داشته باشید که محتویات d4 با دیکشنری های d1، d2 و d3 یکسان نیست.

متغیرهای کلاس

هدف برنامه نویسی شی گرا ساختاربندی مسائل به گونه ای است که دنیای واقعی را تقلید کند، اما برای برنامه نویسان بی تجربه می تواند دست و پا گیر به نظر برسد. یکی از رایج ترین مشکلات درک تفاوت بین متغیرهای “class” و “instance” است.

برای مثال کد زیر را در نظر بگیرید:

class Local:

   motto = 'Think globally'

   actions = []


instance1 = Local()

instance2 = Local()

instance2.motto = 'Act locally'

instance2.actions.append('Reuse')

print( instance1.motto, instance2.motto )

print( instance1.actions, instance2.actions )

نتیجه ممکن است غیرمنتظره باشد:

$ python3 class_vars.py

Think globally Act locally

['Reuse'] ['Reuse']

ممکن است انتظار داشته باشید که مشخصه جهانی برای همه موارد تغییر کند، زیرا این همان چیزی است که با اقدامات لیست اتفاق می افتد، اما در حالی که اقدامات برای همه نمونه ها به یک مرجع اشاره می کند، رشته به عنوان یک ویژگی نمونه کپی می شود.

به طور کلی، متغیرهای کلاس معمولاً غیر ضروری هستند.

ارور های python : آرگومان های تابع بر اساس مرجع و ارزش

آخرین اشتباه از اشتباهات رایج در کدنویسی به زبان پایتون استفاده نادرست از آرگومان های تابع بر اساس مرجع و ارزش است.

پایتون روشی خاص برای استفاده از آرگومان ها در توابع و متدها دارد. برنامه نویسانی که از زبان هایی مانند جاوا یا C++ به پایتون تغییر زبان می کنند ممکن است در درک نحوه کار مفسر با آرگومان ها دچار مشکلاتی شوند.

برای مثال به مثال زیر توجه کنید:

def mutate_args(a:str, b:int, c:float, d:list):

   a += " mutated"

   b += 1

   c += 1.0

   d.append('mutated')

   print('After mutation, inside func')

   print(id(a), id(b), id(c), id(d) )

a = "String"

b = 0

c = 0.0

d = ['String']


print( id(a), id(b), id(c), id(d) )

mutate_args(a,b,c,d)

print( a,b,c,d )

print( id(a), id(b), id(c), id(d) )

خروجی شناسه های هر متغیر را نشان می دهد. توجه داشته باشید که متغیرهای داخل تابع با متغیرهای اصلی به جز اشاره به لیست تفاوت دارند.

در مثال زیر، متغیر ارجاع شده همانطور که انتظار می رود جهش یافته و مرجع در داخل تابع نگهداری می شود:

$ python3 args.py

۱۳۹۷۳۳۸۹۰۱۰۶۷۳۶ ۹۷۸۸۵۷۶ ۱۳۹۷۳۳۸۹۰۰۶۴۴۶۴ ۱۳۹۷۳۳۸۹۰۰۰۴۱۶۰

After mutation, inside func

۱۳۹۷۳۳۸۹۰۱۰۷۳۷۶ ۹۷۸۸۶۰۸ ۱۳۹۷۳۳۸۹۱۵۴۱۰۷۲ ۱۳۹۷۳۳۸۹۰۰۰۴۱۶۰

String 0 0.0 ['String', 'mutated']

۱۳۹۷۳۳۸۹۰۱۰۶۷۳۶ ۹۷۸۸۵۷۶ ۱۳۹۷۳۳۸۹۰۰۶۴۴۶۴ ۱۳۹۷۳۳۸۹۰۰۰۴۱۶۰

شما باید مراقب باشید که بهترین روش‌ها را دنبال کنید، مانند بازگرداندن چندین مقدار یا استفاده از ویژگی‌های شی به منظور جلوگیری از سردرگمی هنگام تغییر آرگومان‌های تابع.

نتیجه گیری

پایتون یک زبان سطح بالا است که وقتی برخی از ویژگی های آن را فهمیدید، کار کردن با آن سرگرم کننده است. و ارزش آن را دارد که سر خود را در اطراف این ویژگی‌ها بپیچید، زیرا پایتون به طرز شگفت‌انگیزی برای حل سریع و آسان بسیاری از سناریوهای برنامه‌نویسی روزانه مناسب است.

اما برای جلوگیری از سردرد اشکال زدایی کد که در نگاه اول مشکلی به نظر می رسد، باید عناصر طراحی پایتون را درک کنید. این مقاله مقدمه‌ای بر ده خطای برتری که مبتدیان اغلب مرتکب می‌شوند، ارائه می‌کند و نکاتی در مورد نحوه اجتناب از آنها به شما ارائه می‌دهد. اما منابع زیادی وجود دارد که می تواند به شما در شروع کار کمک کند.

یکی دیگر از مشکلات پایتون که در طول این سال‌ها همچنان نامرتب‌تر شده است، مدیریت بسته یا package management است. مدیریت بسته به خودی خود یک مشکل سخت است و به مدیریت محیط نیز گسترش یافته است. با وجود پیچیدگی، انجام درست آن ضروری است.

متأسفانه، با پایتون، از نظر تاریخی اشتباه گرفتن آن بسیار آسان بوده است. این یکی از دلایل کلیدی است که پایتون دارای بسیاری از مدیریت بسته های مختلف است.

در دنیای فناوری مدرن، راه امن برای کدنویسی باید ساده ترین راه برای کدنویسی باشد. اما معرفی یک وابستگی به پروژه شما ناگزیر وابستگی های دیگری را به همراه دارد و یک اثر دومینو ایجاد می کند که می تواند شناسایی آسیب پذیری های امنیتی را چالش برانگیز کند.

حتی زمانی که آن‌ها را پیدا می‌کنید، مگر اینکه آسیب‌پذیری‌های مهمی باشند، زمان و تلاش برای رفع آن‌ها به این معنی است که به ندرت به آن‌ها رسیدگی می‌شود و محیط‌های توسعه و آزمایش شما را در معرض حملات سایبری قرار می‌دهد.

شاید این مقاله نیز برای شما کاربردی باشد : آموزش متغیرها در پایتون – راهنمای کامل مبتدیان

یکی دیگر از مجموعه آموزش های نرم افزار به اتمام رسید. امیدواریم این آموزش به جهت یادگیری و شناخت بررسی ارور های python و اشتباهات رایج در کدنویسی به زبان پایتون برای شما عزیزان مفید واقع شده باشد. اگر شما نیز مورد خاصی درمورد اشتباه کدنویسی در پایتون و نحوه اجتناب از آنها ، میدانید. میتوانید آن را در بخش کامنت ها مطرح کنید تا با نام شما این مقاله بروزرسانی گردد؛ همچنین شاید به مقاله نحوه نوشتن و اجرای کدهای C و C++ در Visual Studio Code نیز علاقه مند باشید.

برای امتیاز به این نوشته کلیک کنید!
[کل: ۱ میانگین: ۵]

سعید زارعین

سعید هستم 27 ساله، یک عدد تولید محتوا(ئر) خلاق :)))

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا