7 اشتباه برنامه نویسان پایتون

امیرحسین بیگدلو 12 ماه قبل

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

 

با در نظر گرفتن این موضوع، این مقاله 10 اشتباهی که ممکن است برنامه نویسان پایتون مرتکب شوند را مرور میکند.

 

دوره پیشنهادی: دوره آموزش پایتون (python)

 

 1  استفاده نادرست از عبارات پیشفرض برای آرگومان های توابع

پایتون به شما اجازه می دهد تا با ارائه یک مقدار پیش فرض برای آرگومان تابع، مشخص کنید که یک آرگومان اختیاری است. در حالی که این یک ویژگی عالی زبان است، زمانی که مقدار پیش‌فرض قابل تغییر(mutable) باشد، می‌تواند منجر به سردرگمی شود. به عنوان مثال، این تابع پایتون را در نظر بگیرید:

>>> def foo(bar=[]):        # bar is optional and defaults to [] if not specified
...    bar.append("baz")    # but this line could be problematic, as we'll see...
...    return bar

 

یک اشتباه رایج این است که فکر می کنیم هر بار که تابع بدون ارائه مقداری برای آرگومان اختیاری فراخوانی می شود، آرگومان اختیاری روی عبارت پیش فرض مشخص شده دوباره تنظیم می شود. برای مثال، در کد بالا، ممکن است انتظار داشته باشیم که فراخوانی مکرر foo() (یعنی بدون تعیین آرگومان bar) همیشه "baz" را برگرداند، زیرا فرض این است که هر بار foo() فراخوانی شود (بدون bar آرگومان مشخص شده) bar روی [] تنظیم شده است (یعنی یک لیست خالی جدید).

 

اما بیایید ببینیم وقتی این کار را انجام می دهید واقعاً چه اتفاقی می افتد:

>>> foo()
["baz"]
>>> foo()
["baz", "baz"]
>>> foo()
["baz", "baz", "baz"]

 

چرا با هر بار فراخوانی foo() مقدار پیش‌فرض "baz" را به لیست موجود اضافه می‌کند، به جای اینکه هر بار یک لیست جدید ایجاد کند؟

 

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

 

برای حل این مشکل میتوانید به شکل زیر کار کنید:

>>> def foo(bar=None):
...    if bar is None:		# or if not bar:
...        bar = []
...    bar.append("baz")
...    return bar
...
>>> foo()
["baz"]
>>> foo()
["baz"]
>>> foo()
["baz"]

 

ویدیو پیشنهادی: آموزش class و instance variable در پایتون

 

 2  استفاده نادرست متغیرهای کلاسی

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

>>> class A(object):
...     x = 1
...
>>> class B(A):
...     pass
...
>>> class C(A):
...     pass
...
>>> print A.x, B.x, C.x
1 1 1

 

تا اینجا مشکل نیست. حالا مقدار زیر را تغییر میدهیم:

>>> B.x = 2
>>> print A.x, B.x, C.x
1 2 1

 

هنوز هم مشکلی نیست. حالا کلاس A را تغییر میدهیم:

>>> A.x = 3
>>> print A.x, B.x, C.x
3 2 3

 

چی شد؟ ما فقط مقدار A.x را تغییر دادیم. چرا مقدار C.x تغییر کرد؟

 

در پایتون، متغیرهای کلاس(class variables) به شکل دیکشنری مدیریت می‌شوند و از قانون MRO پیروی می‌کنند. بنابراین در کد بالا، از آنجایی که ویژگی x در کلاس C یافت نمی‌شود، در کلاس‌های پایه آن جستجو می‌شود (در مثال بالا A است). به عبارت دیگر، C ویژگی x خود را ندارد. بنابراین، ارجاعات به C.x در واقع ارجاعاتی به A.x هستند. این باعث ایجاد مشکل پایتون می شود.

 

ویدیو پیشنهادی: توضیح مدیریت خطاها در پایتون

 

 3  استفاده نادرست پارامترهای بلاک except

فرض کنید که کد زیر را دارید:

>>> try:
...     l = ["a", "b"]
...     int(l[2])
... except ValueError, IndexError:  # To catch both exceptions, right?
...     pass
...
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
IndexError: list index out of range

 

مشکل این کد اینجاست که بلاک except خطاهای مشخص شده را به این روش قبول نمیکند. در پایتون 2, روش except Exception, e برای گره زدن یک استثنا به یک متغیر استفاده میشود. در کد بالا خطای IndexError هندل نمیشود. در واقع خطای ValueError در IndexError ذخیره میشود.

 

روش بهتر این است که خطای موجود را در یک tuple مشخص کنید و برای گره زدن خطاها به متغیر از کلمه as استفاده کنید:

>>> try:
...     l = ["a", "b"]
...     int(l[2])
... except (ValueError, IndexError) as e:  
...     pass
...
>>>

 

 

 4  درک نادرست از قوانین scope پایتون

پایتون از قانونی به نام LEGB استفاده میکند تا محدوده دسترسی به آبجکت ها را مشخص کند. برای درک کامل این موضوع حتما پیشنهاد میکنیم ویدیو آموزش namespace در پایتون را مشاهده کنید. به کد زیر دقت کنید:

>>> x = 10
>>> def foo():
...     x += 1
...     print x
...
>>> foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in foo
UnboundLocalError: local variable 'x' referenced before assignment

 

مشکل کجاست؟

 

خطای بالا به این دلیل رخ می‌دهد که وقتی متغیری ایجاد میکنید پایتون محدوده ای را برای آن مشخص میکند. در کد بالا متغیر x را در محدوده global مقداردهی کردید و نمیتوانید در داخل یک فانکشن به آن متغیر دسترسی پیدا کنید.

 

مقاله پیشنهادی: ساخت میکروسرویس با nameko در پایتون

 

 5  تغییر یک لیست در حین پیمایش در آن

مشکل کد زیر باید کاملا واضح باشد:

>>> 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]  # BAD: Deleting item from a list while iterating over it
...
Traceback (most recent call last):
  	  File "<stdin>", line 2, in <module>
IndexError: list index out of range

 

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

 

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

>>> odd = lambda x : bool(x % 2)
>>> numbers = [n for n in range(10)]
>>> numbers[:] = [n for n in numbers if not odd(n)]  # ahh, the beauty of it all
>>> numbers
[0, 2, 4, 6, 8]

 

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

 

 6  تداخل نام با کتابخانه استاندارد پایتون

یکی از زیبایی‌های پایتون، ماژول‌های کتابخانه‌ای فراوانی است که به همراه آن ارائه می‌شود. اگر شناخت درستی از کتابخانه استاندارد پایتون نداشته باشید ممکن است نام ماژول هایتان را با یکی از ماژول های پایتون یکسان انتخاب کنید.برای مثال ، ممکن است ماژولی به نام email.py در کد خود داشته باشید که با ماژول استاندارد کتابخانه به همین نام در تضاد باشد.

 

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

 

 

 7  استفاده نادرست از __del__

فرض کنید که این کد را در فایلی به نام mod.py دارید:

import foo

class Bar(object):
   	    ...
    def __del__(self):
        foo.cleanup(self.myhandle)

 

و سعی میکنید در فایل دیگری به نام another_mod.py اینکار را انجام دهید:

import mod
mybar = mod.Bar()

 

این کد باعث ایجاد یک خطای AttributeError خواهد شد.

 

چرا؟ همانطور که در اینجا توضیح داده شده، زمانی که مفسر پایتون خاموش میشود متغیرهای خاص ماژول هم برابر None خواهند شد. در نتیجه زمانی که __del__ فراخوانی می شود، نام foo قبلاً روی None تنظیم شده است

 

یک راه حل برای این مشکل برنامه نویسی پایتون تا حدودی پیشرفته تر، استفاده از atexit.register است. به این ترتیب، هنگامی که اجرای برنامه شما به پایان می رسد (هنگامی که به طور معمول از آن خارج می شوید)، کنترل کننده های ثبت شده شما قبل از اینکه مترجم خاموش شود، فعال می شوند.

 

با این درک، اصلاح کد mod.py بالا ممکن است چیزی شبیه به این باشد:

import foo
import atexit

def cleanup(handle):
    foo.cleanup(handle)


class Bar(object):
    def __init__(self):
        ...
        atexit.register(cleanup, self.myhandle)

 

این پیاده سازی روشی تمیز و قابل اعتماد برای فراخوانی هر گونه عملکرد پاکسازی مورد نیاز پس از پایان برنامه عادی ارائه می دهد. بدیهی است که تصمیم با foo.cleanup است که با شی محدود شده به نام self.myhandle چه کاری انجام دهد، اما شما این ایده را دریافت می کنید.

مطالب مشابه



مونگارد