ESC را فشار دهید تا بسته شود

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

در این مقاله به 10 اشتباه برنامه نویسی در پایتون میپردازیم که اغلب برنامه نویسان تازه کار مرتکب می‌شوند.. این اشتباهات به نظر ساده می‌رسند، اما اگر از آن‌ها خلاص شوید، کد شما بهتر خواهد بود! بیایید ببینیم چگونه از این اشتباهات مبتدی خلاص شویم و به شیوه‌ای پایتونی عمل کنیم.


استفاده از عملگر + برای درج متغیر‌ها بین رشته‌ها یا ترکیب آنها

name = "Bobby"
city = "Munich"
year_of_birth = 1991

nooby_str_formatting = "My name is " + name + ", I live in " + city + ". I was born in " + str(year_of_birth) + "."
pythonic_str_formatting = f"My name is {name}, I live in {city}. I was born in {year_of_birth}."

معمولا اکثر برنامه نویسان تازه کار، برای فرمت‌بندی رشته‌ها از عملگر + استفاده کنند، مانند روش nooby_str_formatting در کد بالا. اما این روش بهینه و استاندارد نیست. چرا؟ چون این روش خوانایی کد را کاهش می‌دهد و ممکن است منجر به کدی شود که سخت‌ نگهداری و به‌روزرسانی شود. علاوه بر این، اگر هر یک از ورودی‌ها به درستی به رشته تبدیل نشود، این کد باعث بروز خطای نوع داده (TypeError) خواهد شد. فقط سعی کنید از year_of_birth به جای str(year_of_birth) در nooby_str_formatting استفاده کنید و خواهید دید که چه اتفاقی می‌افتد.

روش Pythonic و بهینه برای انجام این عمل استفاده از f-string است. با استفاده از f-string، کد خواناتر خواهد بود و احتمال بروز خطا کمتر خواهد بود.

 

استفاده از * import

احتمالا شما هم فکر میکنید، استفاده از "import *" یا "from xyz import *" واقعاً خوب است، درست است؟ با این روش، می‌توانیم همه چیز را از ماژول xyz به برنامه اضافه کنیم و استفاده کنیم. اما یک واقعیتی وجود دارد و آن این است که این روش مناسب نیست و باعث ایجاد مشکلاتی می‌شود. در ادامه این مشکلات توضیح داده شده است:

تداخل در فضای نام یا Namespace: برخی از ماژول‌ها ممکن است توابع یا متغیرهایی با نام یکسان داشته باشند. این می‌تواند منجر به تداخل یا ابهام شود.

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

کاهش سرعت برنامه: با استفاده از "import *"، برنامه تمام توابع و متغیرهای ماژول را ایمپورت می‌کند، حتی اگر استفاده نشوند. این باعث کاهش سرعت اجرای برنامه می‌شود زیرا استفاده از حافظه افزایش می‌یابد. توصیه می‌شود که فقط آنچه که نیاز است را از یک ماژول وارد کنید. به قطعه کد زیر دقت کنید:

# Nooby version
from math import *

result = sin(0.1) + cos(0.1)

# Good version
from math import sin, cos

result = sin(0.1) + cos(0.1)

 

except یا exceptException

استفاده از "except" بدون مشخص کردن نوع خطا یا استفاده از "except Exception" بر اساس راهنمایی‌های PEP 8 توصیه نمی‌شود و دلیل خوبی برای این توصیه وجود دارد. با این دو روش، برنامه همه خطاها را می‌گیرد، حتی خطاهایی که ممکن است پیش بینی نشده باشند و باعث رفتار غیرمنتظره برنامه شوند. مهمترین نکته این است که گرفتن همه خطاها به این شکل، در هنگام اشکال‌زدایی، منجر به استفاده از عبارات ناسزا خواهد شد.

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

dictionary = {'a': 'one', 'b': 'two', 'c': 'three'}

# Nooby version
try:
    print(int(dictionary['d']))
except:
    print("Can you guess the exception?")

# Nooby version 2
try:
    print(int(dictionary['d']))
except Exception:
    print("Can you guess the exception?")

# Good
try:
    print(int(dictionary['d']))
except KeyError:
    print("We get KeyError")
except ValueError:
    print("We get ValueError")

 

پیشنهاد برای مطالعه: راهنمای کامل کاربردهای برنامه نویسی پایتون | از بازی سازی تا توسعه وب

بستن یک File به صورت دستی

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

f = open('file_name.txt', 'w')
f.write('new_data')
f.close()

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

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

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

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

بهترین شیوه استفاده از عبارت "with" است، که به صورت خودکار فایل را می‌بندد (close) می‌کند حتی در صورت بروز استثنا (Exception ) در عملیات ورود و خروج فایل، این کار باعث بهبود خوانایی و نگهداری کد می‌شود و همچنین احتمال بروز خطا در کد کمتر می‌شود. کد زیر یک نمونه استاندارد از این بخش است:

with open('file_name.txt', 'w') as f:
    f.write('new_data')

 

استفاده از == به جای ()isinstance

یک سوال، چطور میتوانیم نوع یک متغیر را بررسی کنیم؟ اگر در ابتدا استفاده از عملگر == به ذهن‌تان آمد باید بگویم که این کار درست نیست. دلیل این حرف‌ این است که عملگر == برای بررسی نوع، زیر کلاس‌های آن نوع را شناسایی نمی‌کند.

from collections import namedtuple

Point = namedtuple("Point", "x y")
p = Point(3, 4)

print(type(p) == tuple)  # Output: False
print(isinstance(p, tuple))  # Output: True

در این مثال، namedtuple زیرکلاسی از tuple است و == قادر به شناسایی آن نیست، اما isinstance() می‌تواند این کار را انجام دهد.. در اکثر موارد، بهتر است از isinstance() استفاده شود.

پیشنهاد مطالعه: 7 کتابخانه قدرتمند پایتون برای هک که همین حالا باید امتحانش کنید

استفاده زیاد از Comprehensions

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

def overuse_comprehension(input_list: list) -> list:
    return [item
           for sublist in input_list
           for item in sublist
           if item > 0]

def use_loop_instead(input_list: list) -> list:
    result = []
    for rows in list:
        for sublist in rows:
            if sublist > 0:
                result.append(sublist)
    return result

input_list = [[1, 2, 3],[-1,-5,6]]

print(overuse_comprehension(input_list))  # Output: [1, 2, 3, 6]
print(use_loop_instead(input_list))  # Output: [1, 2, 3, 6]

ممکن است این یک مسئله سلیقه شخصی باشد، اما من معتقدم که در مثال بالا، استفاده از use_loop_instead خواناتر از overuse_comprehension است.

 

استفاده نکردن از Comprehensions

هر چه قدر که استفاده بیش از حد از comprehension خوب نیست، استفاده نکردن از comprehension هم اصلا توصیه نمی‌شود. یک برنامه نویس حرفه‌ای پایتون باید بداند که چه زمانی از این قابلیت قدرتمند پایتون استفاده کند. کد زیر را بررسی کنید:

def use_loop(max_value: int) -> list:
    result = []
    for value in range(max_value):
        if value % 2 == 0:
            result.append(value)
    return result

def use_comprehension(max_value: int) -> list:
    return [x for x in range(max_value) if x % 2 == 0]

print(use_loop(10))  # Output: [0, 2, 4, 6, 8]
print(use_comprehension(10))  # Output: [0, 2, 4, 6, 8]

 

در این مثال، استفاده از یک خط در use_comprehension کد را مختصر و قابل خواندن‌تر می‌کند. نکته مهم این است که در پایتون، می‌توان از Comprehensions نه تنها برای لیست‌ها، بلکه برای دیکشنری‌ها و تاپل‌ها نیز استفاده کرد.

 

 

استفاده از آرگومان های پیش فرض تغییر پذیر

استفاده از یک چیزی که می‌تواند تغییر کند، مثل یک لیست یا دیکشنری، به عنوان یک آرگومان پیش‌فرض دارای مشکلاتی است. مشکل این است که هر بار که تابع را بدون دادن آرگومانی برای آن فراخوانی می‌کنیم، همان چیزی که به عنوان پیش‌فرض تعریف شده استفاده می‌شود. این مسئله خطرناک است زیرا اگر این چیز در داخل تابع تغییر کند، می‌تواند رفتاری کاملاً غیرمنتظره داشته باشد.

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

به همین دلیل بهتر است از یک مقدار خاص، مثلاً None، به عنوان آرگومان پیش‌فرض استفاده کنیم و اگر هیچ لیستی به عنوان آرگومان داده نشده باشد، در داخل تابع یک لیست جدید ایجاد کنیم.

 

def add_item(n, l=[]):
    l.append(n)
    return l

print(add_item(1))  # Expected Output: [1] - Actual Output: [1]
print(add_item(2))  # Expected Output: [2] - Actual Output: [1, 2]
print(add_item(3))  # Expected Output: [3] - Actual Output: [1, 2, 3]

 

دلیل اینکه رفتار ناخواسته اتفاق می‌افتد این است که وقتی تابع را تعریف می‌کنیم، یک شیء پیش‌فرض برای لیست ایجاد می‌شود و همین شیء در هر باری که تابع را بدون دادن هیچ آرگومانی برای لیست فراخوانی می‌کنیم، استفاده می‌شود. مثلاً وقتی تابع را با استفاده از add_item(1) فراخوانی می‌کنیم، یک لیست خالی ساخته می‌شود و ۱ به آن اضافه می‌شود. در آن لحظه، ما لیستی که [۱] است را داریم. وقتی تابع را با استفاده از add_item(2) فراخوانی می‌کنیم، همان لیست قبلی [۱] استفاده می‌شود و ۲ به آن اضافه می‌شود. وقتی تابع را با استفاده از add_item(۳) فراخوانی می‌کنیم نیز همین موضوع صادق است.

برای جلوگیری از این مشکل، به جای استفاده از یک شیء پیش‌فرض برای لیست، بهتر است از مقدار None استفاده کنیم و در داخل تابع یک لیست جدید ایجاد کنیم اگر آرگومان لیست مقدار None باشد.

def add_item(n, l=None):
    if l is None:
        l = []
    l.append(n)
    return l

print(add_item(1))  # Expected Output: [1] - Actual Output: [1]
print(add_item(2))  # Expected Output: [2] - Actual Output: [2]
print(add_item(3))  # Expected Output: [3] - Actual Output: [3]

 

استفاده از عبارت‌ یا دستور print به جای استفاده از ماژول Logging

یکی از عادت‌های متداولی که بسیاری از مبتدیان دارند، استفاده از دستور print به عنوان روشی برای چاپ کردن لاگ ها و رفع اشکال است. استفاده از این روش محدودیت‌هایی دارد:

  • پیدا کردن مکانی که پیام‌ها از آنجا نشات می‌گیرند، به ویژه در برنامه‌های بزرگ، دشوار است.
  • قابلیت کنترل بر روی مکان ذخیره سازی پیام‌ها وجود ندارد. این باعث می‌شود که فیلتر کردن و مدیریت پیام‌ها سخت‌تر باشد.
  • استفاده از دستور print می‌تواند عملکرد برنامه را کاهش دهد، به ویژه در برنامه‌های بزرگ یا با مدت زمان اجرای طولانی.

 

در زیر مزیت‌های استفاده از ماژول logging به جای عبارت print توضیح داده شده است:

  • این ماژول امکانات بیشتری را فراهم می‌کند که می‌توان از آن برای ردیابی مشکلات استفاده کرد.
  • با استفاده از ماژول logging، می‌توان سطح پیام‌های ثبت شده را کنترل کرد و این کار راحت‌تر می‌کند.
  • با استفاده از ماژول logging، می‌توان پیام‌ها را در یک فایل، پایگاه داده یا هر خروجی دیگری ذخیره کرد.

 

پیشنهاد مطالعه: استفاده های پیشرفته از پایتون با 7 تابع و کتابخانه حرفه‌ای پایتون

عدم رعایت PEP8

PEP 8 یک راهنمایی برای نوشتن کد پایتون است که برای ما قواعدی برای ساختاردهی و قالب‌بندی کد فراهم می‌کند. این مهم است که بدانید این راهنما تأثیری بر روی کد در هنگام اجرا ندارد. اما ما به دلایل زیر باید به PEP 8 اهمیت بدهیم:

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

همکاری: زمانی که در یک پروژه با چند برنامه‌نویس دیگر کار می‌کنید، پیروی از یک استایل کدنویسی یکسان باعث می‌شود تا برنامه بهتر و سریع‌تر توسعه پیدا کند و همکاری بهتری بین برنامه‌نویسان صورت بگیرد. خوشبختانه، برخی از نرم‌افزارهای توسعه یکپارچه (IDE) مانند پایچرم، پیشنهاداتی برای رعایت PEP8 را به شما نشان می‌دهند.

 

به طور خلاصه، اجتناب از ۱۰ اشتباه کدنویسی مبتدی در پایتون که در این پست مطرح شد، می‌تواند به شما کمک کند تا یک برنامه‌نویس بهتر و موثرتر شوید. اگر موردی یا پیشنهادی برای این مقاله دارید در قسمت دیدگاه با ما و سایر خوانندگان به اشتراک بگذارید.

منبع: levelup.gitconnected.com

نظرات (0)

wave

هیج نظری ثبت نشده است

ارسال نظر

wave

برای درج نظر می بایست وارد حساب کاربری خود شوید