9 اشتباه متداول بین برنامه نویسان پایتون - قسمت دوم
سینتکس به ظاهر ساده پایتون می تواند برنامه نویسانی که از این زبان استفاده می کنند، علی الخصوص برنامه نویسان تازه کار، را دراستفاده مناسب از دستورات این زبان گیج کند.این گیجی می تواند باعث شود که از بعضی ریزه کاری های این زبان نادانسته رد شوند و از تمام قدرت این زبان استفاده نکنند.
در قسمت گذشته به 5 تا از این اشتباهات احتمالی پرداختیم. در این پست به 4 اشتباه دیگر می پردازیم.
اشتباه 6 – ساختن یک حلقه بسته (closed loop) از وابستگی ها
فرض کنید که دو فایل a.py و b.py را داریم که در هرکدام دیگری را import می کنیم.
به گونه ای که در a.py داشته باشیم :
import b
def f():
return b.x
print f()
و در b.py داشته باشیم :
import a
x = 1
def g():
print a.f()
حالااگر a.py را import کنیم، خواهیم داشت :
>>> import a
1
شاید برای شما سوال باشد که در برنامه ما که وابستگی import به صورت یک حلقه بسته بود که از نظر عملی خطاست (a وابسته به b و b وابسته به a)، چرا اروری به ما داده نشد؟
جواب شما این است که حضور یک حلقه بسته از وابستگی import به تنهایی باعث مشکلی در پایتون نخواهد شد. ولی با توجه به محلی که هر ماژول سعی می کند به محتویات ماژول دیگر دسترسی پیدا کند، این نوع از وابستگی ها می تواند مشکل زا باشد (اگر در قسمتی از کد که اجرا نمی شود باشد ممکن است باعث مشکل نشود).
در مثال وقتی که ما a.py را import کردیم، پایتون هیچ مشکلی برای import کردن b.py نداشت، چون b.py نیازی به مشخص بودن هیچ مقداری از a.py در هنگام import شدن ندارد. تنها اشاره ای که به a.py در b.py شده است در قسمت ()g است که در لحظه ای که a را import می کنیم درخواست نمی شود، پس مشکلی در این زمینه نخواهیم داشت.
ولی اگر بخواهیم b.py را import کنیم (بدون آنکه ابتدا a.py را import کرده باشیم)، به مشکل برخواهیم خورد.
>>> import b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "b.py", line 1, in <module>
import a
File "a.py", line 6, in <module>
print f()
File "a.py", line 4, in f
return b.x
AttributeError: 'module' object has no attribute 'x'
مشکل اینجاست که در پروسه import کردن b.py، پایتون برای import کردن a.py نیز تلاش می کند. این اتفاق باعث درخواست شدن تابع()f است که در نتیجه باعث درخواست شدن مقدار b.x است، ولی این مقدار تاکنون تعریف نشده است، پس به ارور AttributeError بر می خوریم.
یک راه آسان برای حل کردن این مشکل این است که به جای اینکه در ابتدای b.py فایل a را import کنیم، درون تابع g() سعی بر import کنیم، پس b.py بدین شکل خواهد بود :
x = 1
def g():
import a
print a.f()
که باعث می شود خروجی ما بعد از درخواست کردن b.g() اینگونه باشد :
>>> import b
>>> b.g()
1
1
که عدد 1 اول توسط قسمت ()print f موجود در انتهای a.py (که در ()b.g فراخوانی شده است) چاپ شده است و عدد 1 دوم توسط دستور ()print a.f در انتهای ()b.g چاپ شده است.
اشتباه 7 – انتخاب کردن نام نامناسب برای ماژول ها
یکی از ویژگی های برجسته پایتون، وسعت و عظمت کتابخانه هایی است که پایتون به طور پیش فرض شامل آنان است. ولی بر اثر این وسعت، احتمال این وجود دارد که نام یک ماژول نوشته شده توسط برنامه نویس با یکی از ماژول های کتاب خانه ی استاندارد پایتون یکسان باشد (برای مثال ممکن است برنامه نویس یک ماژول برای دسترسی به ایمیل نوشته باشد که نام آن را email.py گذاشته باشد، این نام با نام یکی از ماژول های موجود در کتاب خانه ی پیش فرض پایتون یکسان است و باعث مشکل خواهد شد).
این اشتباه می تواند به مشکلات بسیار پیچیده ای منجر شود. پایتون نه تنها در هنگام استفاده از ماژول نوشته شده توسط برنامه نویس به مشکل خواهد خورد، بلکه ممکن است یک ماژول نامرتبط نیاز به استفاده از ماژولی در کتاب خانه ی استاندارد پایتون داشته باشد که هم نام ماژول دست ساز ماست. این اشتباه در نام گذاری ماژول باعث می شود که آن ماژول نامرتبط، ماژول اشتباه را برای کار خودش استفاده کند که منجر به ارور و کار نکردن برنامه می شود.
به همین علت، در هنگام نام گذاری باید حداکثر دقت را به کار برد که از اینگونه نام های موجود در کتاب خانه های استاندارد پایتون استفاده نشود. عوض کردن نام یک ماژول دست نویس توسط خودتان در برنامه خودتان بسیار راحت تر از پرکردن و ارسال یک درخواست تغییر نام ماژول پیش فرض پایتون (در یک فرم Python Enhancement Proposalیا PEP) خواهد بود.
اشتباه 8 – بی دقتی به تفاوت های موجود میان پایتون 2 و پایتون 3
کد زیر (foo.py) را در نظر داشته باشید :
import sys
def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
raise ValueError(2)
def bad():
e = None
try:
bar(int(sys.argv[1]))
except KeyError as e:
print('key error')
except ValueError as e:
print('value error')
print(e)
bad()
این کد در پایتون 2 بدون هیچ مشکلی اجرا خواهد شد :
$ python foo.py 1
key error
1
$ python foo.py 2
value error
2
ولی اگر همین برنامه را در پایتون 3 اجرا کنیم به مشکل برخواهیم خورد :
$ python3 foo.py 1
key error
Traceback (most recent call last):
File "foo.py", line 19, in <module>
bad()
File "foo.py", line 17, in bad
print(e)
UnboundLocalError: local variable 'e' referenced before assignment
چه شد؟ چرا به مشکل برخوردیم؟
مشکل این است که در پایتون 3، جسم exception خارج از حدود قسمت except در کد قابل دسترسی نخواهد بود.
یک راه برای حل این مشکل این است که یک اشاره خارج از قسمت except در کد به جسم exception داشته باشیم تا بتوان به آن دسترسی داشت. کد زیر از این روش استفاده می کند و به این علت با پایتون 2 و 3 قابل اجراست و خروجی مورد نظر ما را خواهد داشت :
import sys
def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
raise ValueError(2)
def good():
exception = None
try:
bar(int(sys.argv[1]))
except KeyError as e:
exception = e
print('key error')
except ValueError as e:
exception = e
print('value error')
print(exception)
good()
اگر این کد را در پایتون 3 اجرا کنیم می بینیم که بدون مشکلی اجرا خواهد شد :
$ python3 foo.py 1
key error
1
$ python3 foo.py 2
value error
2
اشتباه 9 – استفاده اشتباه از __del__
سطح این اشتباه کمی بالاتر از اشتباهات قبلیست.
اگر کد زیر را در فایلی به نام mod.py داشته باشیم :
import foo
class Bar(object):
...
def __del__(self):
foo.cleanup(self.myhandle)
و بخواهیم از یک کد دیگر مثل کد زیر به آن دسترسی پیدا کنیم :
import mod
mybar = mod.Bar()
می بینیم که به ارور AttributeError بر می خوریم.
چرا؟ مشکل این است که وقتی کار مفسر زبان پایتون به اتمام می رسد و خاموش می شود، مقدار متغیرهای عمومی ماژول ما برابر با None خواهد شد. در نتیجه این رفتار، وقتی به مرحله ای میرسیم که دستور __del__
اجرا می شود، مقدار متغیر foo قبلا برابر None شده است.
یکی از راه حل های موجود برای این مشکل می تواند استفاده از ()atexit.register
باشد. با استفاده از این دستور، وقتی اجرای برنامه شما به اتمام می رسد، هندلرهای برنامه قبل از خاموش شدن مفسر از بین می روند.
برای اجرای این راه حل، شکل کد ما اینگونه خواهد بود:
import foo
import atexit
def cleanup(handle):
foo.cleanup(handle)
class Bar(object):
def __init__(self):
...
atexit.register(cleanup, self.myhandle)
این روش استفاده از این دستور، راه مناسب و قابل اعتمادی برای اجرای پاکسازی های موردنیاز در هنگام اتمام اجرای برنامه است.
در این پست دو قسمتی سعی شده به برخی از اشتباهاتی که ممکن است در هنگام استفاده از پایتون توسط برنامه نویسان انجام شود بپردازیم. امیدواریم که این نوشته مفید باشه براتون.
امیر هستم از ایران پایتونیرز، ممنونم از وقتی که برای خوندن قسمت دوم این پست گذاشتید.
ما به تازگی استفاده از پلتفرم ویرگول رو شروع کردیم و به زودی مطالب بیشتری هم اینجا قرار داده میشه.
برای دیدن ویدیو های آموزشی دیگری که دوستانم برای شما ساخته اند، می تونید به کانال ما در آپارات ، یوتیوب ، تلگرام یا اینستاگرام مراجعه کنید.
منبع : www.toptal.com
مطلبی دیگر از این انتشارات
چقدر با ماژول OS پایتون آشنایی دارید؟ 10 تابع پرکاربرد این ماژول همراه با مثال
مطلبی دیگر از این انتشارات
9 تابع پیش فرض در پایتون که باید با آنها آشنا باشید - قسمت دوم
مطلبی دیگر از این انتشارات
9 اشتباه متداول بین برنامه نویسان پایتون - قسمت اول