
10 اشتباه رایج در کدنویسی پایتون و روش های مقابله با آن
سینتکس ساده و یادگیری راحت پایتون ممکنه باعث بشه برنامه نویسان پایتون، مخصوصا افرادی که تازه با این زبان آشنا شدن، بعضی از ظرافت های اون رو از دست بدن و قدرت این زبان رو کمتر از چیزی که هست ارزیابی کنن.
- 10 اشتباه رایج در کدنویسی پایتون و روش های مقابله با آن
- درباره پایتون
- درباره این مقاله
- اشتباه رایج شماره 1: استفاده نادرست از عبارات به عنوان پیش فرض برای آرگومان های تابع
- اشتباه رایج شماره 2: استفاده نادرست از متغیرهای کلاسی
- اشتباه رایج شماره 3: مشخص کردن نادرست پارامترها برای یک بلاک استثناء
- اشتباه رایج شماره 4: درک نادرست قوانین محدوده (Scope) در پایتون
- اشتباه رایج شماره 5: تغییر دادن یک لیست در حین پیمایش (Iteration) روی اون
- اشتباه رایج شماره 6: اشتباه گرفتن نحوه بایند (Bind) شدن متغیرها در کلوزرها (Closures) در پایتون
- اشتباه رایج شماره 7: ایجاد وابستگی های مدولی دایره ای (Circular Module Dependencies)
- اشتباه رایج شماره 8: تداخل نام (Name Clashing) با ماژول های استاندارد پایتون
- اشتباه رایج شماره 9: در نظر نگرفتن تفاوت های بین پایتون 2 و پایتون 3
- اشتباه رایج شماره 10: استفاده نادرست از متد __del__
در این مقاله، مارتین چی کیلیان از Toptal یک لیست از 10 اشتباه رایج و دشوار برای شناسایی رو معرفی میکنه که حتی پیشرفته ترین برنامه نویسان پایتون هم ممکنه درگیر اونها بشن.
درباره پایتون
پایتون یک زبان برنامه نویسی تفسیر شده، شی گرا و سطح بالا با معنای دینامیک هست. ساختارهای داده پیشرفته ای که به طور داخلی در پایتون وجود دارن، همراه با تایپ و بایندینگ دینامیک، این زبان رو برای توسعه سریع برنامه ها و همچنین استفاده به عنوان یک زبان اسکریپت نویسی یا اتصال دهنده (Glue Language) برای ارتباط با اجزای موجود یا سرویس ها، بسیار جذاب میکنه.

پایتون از ماژول ها و پکیج ها پشتیبانی میکنه که این باعث تشویق به مدولار بودن برنامه ها و بازاستفاده از کد میشه.
درباره این مقاله
سینتکس ساده و یادگیری راحت پایتون ممکنه باعث بشه برنامه نویسان پایتون، به ویژه کسانی که تازه با این زبان آشنا شدن – برخی از ظرافت های اون رو از دست بدن و قدرت زبان پایتون رو کمتر از چیزی که هست ارزیابی کنن.
با توجه به این نکته، این مقاله یک لیست از 10 اشتباه رایج، دشوار برای شناسایی و در عین حال زیاد اتفاق افتاده رو ارائه میده که حتی پیشرفته ترین برنامه نویسان پایتون هم ممکنه درگیر اونها بشن.
توجه: این مقاله برای مخاطبان پیشرفته تر نوشته شده است و با مقاله اشتباهات رایج برنامه نویسان پایتون که بیشتر برای کسانی که تازه با زبان پایتون آشنا شدن و ممکنه با خطاهای رایج پایتون آشنایی کمتری داشته باشن، تفاوت داره.
اشتباه رایج شماره 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” به لیست موجود اضافه میشد، به جای اینکه هر بار یک لیست جدید ایجاد بشه؟
جواب پیشرفته تر برنامه نویسی پایتون اینه که مقدار پیش فرض برای آرگومان تابع فقط یکبار و در زمان تعریف تابع ارزیابی میشه. بنابراین، آرگومان bar تنها زمانی به مقدار پیش فرض خود (یعنی یک لیست خالی) تنظیم میشه که ()foo برای اولین بار تعریف بشه، اما بعد از اون فراخوانی های تابع ()foo (یعنی بدون مشخص کردن آرگومان bar) از همون لیستی استفاده میکنه که ابتدا bar به اون مقداردهی شده بود.
برای اطلاع شما، یک روش رایج برای حل این مشکل به شکل زیر هست:
>>> def foo(bar=None):
... if bar is None: # or if not bar:
... bar = []
... bar.append("baz")
... return bar
...
>>> foo()
["baz"]
>>> foo()
["baz"]
>>> foo()
["baz"]
اشتباه رایج شماره 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.x = 3
>>> print A.x, B.x, C.x
3 2 3
ما فقط A.x رو تغییر دادیم. چرا C.x هم تغییر کرد؟
در پایتون، متغیرهای کلاسی به صورت داخلی به عنوان دیکشنری مدیریت میشن و از چیزی که به عنوان ترتیب حل متدها (MRO) شناخته میشه پیروی میکنن.
بنابراین در کد بالا، از اونجایی که ویژگی x در کلاس C پیدا نمیشه، به دنبال اون در کلاس های پایه جستجو میشه (که در این مثال فقط A هست، هرچند که پایتون از ارث بری چندگانه پشتیبانی میکنه).
به عبارت دیگه، C ویژگی x خودش رو به طور مستقل از A نداره. بنابراین، ارجاع ها به C.x در واقع ارجاع به A.x هستن. این موضوع میتونه در پایتون مشکل ایجاد کنه مگر اینکه به درستی مدیریت بشه. برای یادگیری بیشتر در مورد ویژگی های کلاسی در پایتون، بهتره به مستندات پایتون مراجعه کنید.
اشتباه رایج شماره 3: مشخص کردن نادرست پارامترها برای یک بلاک استثناء
فرض کنید کد زیر رو دارید:
>>> 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.x، سینتکس except Exception, e برای بایند (bind) کردن استثناء به پارامتر دوم اختیاری مشخص شده (که در اینجا e هست) استفاده میشه تا برای بررسی های بیشتر در دسترس باشه. در نتیجه، در کد بالا، استثناء IndexError توسط دستور except گرفته نمیشه، بلکه این استثناء به جای اون، به یک پارامتر به نام IndexError بایند میشه. این نوع اشتباهات در کدنویسی پایتون رایج هستن.
روش درست برای گرفتن چندین استثناء در یک بلاک except اینه که پارامتر اول رو به عنوان یک تاپل (tuple) شامل تمام استثناء های مورد نظر مشخص کنیم. همچنین، برای حداکثر سازگاری، از کلیدواژه as استفاده کنید، چون این سینتکس هم در پایتون 2 و هم در پایتون 3 پشتیبانی میشه:
>>> try:
... l = ["a", "b"]
... int(l[2])
... except (ValueError, IndexError) as e:
... pass
...
>>>
اشتباه رایج شماره 4: درک نادرست قوانین محدوده (Scope) در پایتون
تعیین محدوده (Scope Resolution) در پایتون بر اساس چیزی انجام میشه که به عنوان قانون LEGB شناخته میشه، که مخفف Local, Enclosing, Global, Built-in هست. به نظر میاد که این مفهوم کاملا سرراست باشه، درسته؟ اما در واقع، جزئیات ظریفی در نحوه کار این سیستم در پایتون وجود داره که ما رو به یکی از مشکلات رایج و پیشرفته تر در برنامه نویسی پایتون میرسونه.
به مثال زیر دقت کنید:
>>> 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
مشکل چیه؟
خطای بالا به این دلیل رخ میده که وقتی داخل یک محدوده (Scope) به یک متغیر مقداردهی می کنید، پایتون اون متغیر رو به طور خودکار محلی (Local) در نظر میگیره و هر متغیری با همین نام در محدوده های بیرونی رو سایه میندازه (Shadow).
به همین دلیل، خیلی ها وقتی که در یک کد که قبلا درست کار میکرد، یک دستور مقداردهی به متغیر اضافه میکنن، ناگهان با خطای UnboundLocalError مواجه میشن. (میتونید برای اطلاعات بیشتر درباره این موضوع تحقیق کنید.)
این مشکل به خصوص هنگام کار با لیست ها (Lists) خیلی رایج هست.
به مثال زیر دقت کنید:
>>> lst = [1, 2, 3]
>>> def foo1():
... lst.append(5) # This works ok...
...
>>> foo1()
>>> lst
[1, 2, 3, 5]
>>> lst = [1, 2, 3]
>>> def foo2():
... lst += [5] # ... but this bombs!
...
>>> foo2()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in foo
UnboundLocalError: local variable 'lst' referenced before assignment
چرا foo2 خراب شد در حالی که foo1 بدون مشکل اجرا شد؟
جواب دقیقا همون چیزیه که در مثال قبلی هم گفتیم، اما اینجا کمی ظریف تره. تابع foo1 هیچ مقداردهی مستقیمی به lst انجام نمیده، ولی foo2 این کارو میکنه.
یادتون باشه که عبارت lst += [5] در واقع یک شکل کوتاه شده از lst = lst + [5] هست. یعنی ما داریم به lst مقدار جدیدی اختصاص میدیم، که باعث میشه پایتون اون رو یک متغیر محلی (Local) در نظر بگیره. اما مقدار جدیدی که میخوایم به lst اختصاص بدیم، خودش بر اساس lst هست که پایتون فکر میکنه هنوز در محدوده محلی تعریف نشده. نتیجه؟ خطا.
اشتباه رایج شماره 5: تغییر دادن یک لیست در حین پیمایش (Iteration) روی اون
مشکل در کد زیر باید کاملا واضح باشه:
>>> 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
حذف یک آیتم از یک لیست یا آرایه در حین پیمایش (Iteration) روی اون، یکی از مشکلات شناخته شده پایتون هست که هر توسعه دهنده باتجربهای باهاش آشناست. اما در حالی که مثال بالا ممکنه کاملا واضح به نظر برسه، حتی برنامه نویس های حرفه ای هم ممکنه ناخواسته در کدهای پیچیده تر دچار این مشکل بشن.
خوشبختانه، پایتون شامل تعدادی الگوی برنامه نویسی (Programming Paradigm) زیبا و کارآمد هست که اگه درست ازشون استفاده بشه، میتونه باعث بشه کدها خیلی ساده تر و تمیزتر بشن. یکی از مزیت های جانبی این روش اینه که هرچقدر کد ساده تر باشه، احتمال بروز این مشکل هم کمتر میشه.
یکی از این الگوهای مفید، لیست کامپرهنشن (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]
اشتباه رایج شماره 6: اشتباه گرفتن نحوه بایند (Bind) شدن متغیرها در کلوزرها (Closures) در پایتون
به مثال زیر دقت کنید:
>>> 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
سورپرایز
این اتفاق به خاطر رفتار بایندینگ دیرهنگام (Late Binding) در پایتون رخ میده. طبق این رفتار، مقدار متغیرهایی که در کلوزرها (Closures) استفاده میشن، در زمان اجرای تابع داخلی بررسی میشه، نه در زمان تعریف اون.
پس در کد بالا، هر بار که یکی از توابع بازگشتی صدا زده بشه، مقدار i در محدودهی بیرونی در همون لحظهی اجرا بررسی میشه و چون حلقه تا اون زمان تکمیل شده، مقدار i روی مقدار نهایی خودش، یعنی 4 قرار گرفته.
راه حل این مشکل رایج در پایتون یه جورایی یه ترفند محسوب میشه:
>>> def create_multipliers():
... return [lambda x, i=i : i * x for i in range(5)]
...
>>> for multiplier in create_multipliers():
... print multiplier(2)
...
0
2
4
6
8
و تمام!
اینجا ما از آرگومان های پیش فرض (Default Arguments) استفاده کردیم تا توابع ناشناس (Anonymous Functions) تولید کنیم و رفتار مورد نظر رو به دست بیاریم. بعضی ها این روش رو هوشمندانه و شیک میدونن. بعضی ها میگن که خیلی ظریف و پیچیدست و بعضی ها اصلا ازش متنفرن اما اگه یه توسعه دهنده پایتون هستید، مهمه که این رفتار رو کاملا درک کنید.
اشتباه رایج شماره 7: ایجاد وابستگی های مدولی دایره ای (Circular Module Dependencies)
فرض کنید دو فایل دارید، 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 a
1
همه چی به خوبی کار کرد. شاید این براتون عجیب باشه. بالاخره، ما اینجا یه ایمپورت دایره ای (Circular Import) داریم که منطقا باید مشکلساز بشه، درسته؟
جواب اینه که وجود یه ایمپورت دایره ای به خودی خود در پایتون مشکلساز نیست. اگه یه ماژول قبلا ایمپورت شده باشه، پایتون به اندازه کافی هوشمنده که دوباره سعی نکنه اون رو ایمپورت کنه. اما بسته به این که هر ماژول در چه مرحلهای داره به متغیرها یا توابعی که در ماژول دیگه تعریف شدن دسترسی پیدا میکنه، ممکنه به مشکل بخورید.
برگردیم به مثال خودمون:
وقتی a.py رو ایمپورت کردیم، مشکلی پیش نیومد، چون b.py هم بدون مشکل ایمپورت شد. دلیلش اینه که در لحظهای که b.py داره ایمپورت میشه، هنوز به چیزی از a.py نیاز نداره. تنها ارجاع b.py به a، فراخوانی () a.f درون تابع () g هست. اما هیچ کدی در a.py یا b.py تابع () g رو صدا نمیزنه، پس مشکلی پیش نمیاد.
اما حالا ببینیم چی میشه اگه بخوایم b.py رو مستقیما ایمپورت کنیم بدون این که قبلا a.py رو ایمپورت کرده باشیم:
>>> 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'
اوه این خوب نیست!
مشکل اینجاست که در حین ایمپورت کردن b.py، اون سعی میکنه a.py رو ایمپورت کنه، که در ادامه () f رو صدا میزنه، و () f هم میخواد به b.x دسترسی پیدا کنه. اما مشکل اینه که b.x هنوز تعریف نشده! بنابراین، ما با استثناء AttributeError مواجه میشیم.
حداقل یکی از راهحلهای این مشکل خیلی سادهست:
کافیه a.py رو مستقیما داخل تابع () g در b.py ایمپورت کنیم:
x = 1
def g():
import a # This will be evaluated only when g() is called
print a.f()
حالا وقتی که اون رو ایمپورت میکنیم، همه چی خوبه:
>>> import b
>>> b.g()
1 # Printed a first time since module 'a' calls 'print f()' at the end
1 # Printed a second time, this one is our call to 'g'
اشتباه رایج شماره 8: تداخل نام (Name Clashing) با ماژول های استاندارد پایتون
یکی از زیبایی های پایتون، تعداد زیاد ماژول های استانداردیه که به صورت پیش فرض همراهش هستن. اما به همین دلیل، اگه حواستون نباشه، خیلی راحت ممکنه نام یکی از ماژول های خودتون با یه ماژول استاندارد پایتون تداخل پیدا کنه.
مثلا فرض کنید یه فایل email.py توی پروژهتون داشته باشید. این با ماژول استاندارد email در پایتون تداخل پیدا میکنه.
این موضوع میتونه باعث مشکلات عجیبی بشه، مثلا:
- یه کتابخونه دیگه رو ایمپورت میکنید
- اون کتابخونه سعی میکنه نسخه استاندارد ماژول email رو از پایتون لود کنه
- اما چون شما یه email.py توی پروژه خودتون دارید، به جای نسخه استاندارد، پایتون نسخه شما رو لود میکنه
- و بعدش کلی خطای عجیب و غریب دریافت میکنید
پس همیشه مراقب باشید که نام ماژول های شما با ماژول های استاندارد پایتون یکی نباشه.
تغییر دادن نام یه ماژول توی پروژهی خودتون خیلی راحت تر از اینه که بخواید یه پیشنهاد تغییر رسمی (PEP) به پایتون بدید و منتظر تاییدش بمونید.
اشتباه رایج شماره 9: در نظر نگرفتن تفاوت های بین پایتون 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 Object) خارج از بلاک except در دسترس نیست.
چرا؟ چون اگه این شیء بعد از خروج از except هنوز در دسترس میموند، یه چرخهی مرجع (Reference Cycle) با استک فریم (Stack Frame) ایجاد میکرد، که تا وقتی که گاربج کالکتور (Garbage Collector) اجرا نشه، توی حافظه باقی میموند.
راهحل:
یکی از روش های حل این مشکل اینه که یه متغیر خارج از except تعریف کنیم تا شیء استثناء رو نگه داره و اون رو در دسترس نگه داریم.
اینجا یه نسخهی بهبود یافته از همون مثال قبلی رو داریم که هم در پایتون 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
بله البته، راهنمای استخدام برنامه نویس پایتون ما به چندین تفاوت مهم دیگه بین پایتون 2 و پایتون 3 اشاره میکنه که دونستنشون برای مهاجرت کد از پایتون 2 به 3 خیلی مهمه.
اشتباه رایج شماره 10: استفاده نادرست از متد __del__
فرض کنید کد زیر رو داخل یه فایل به نام mod.py دارید:
import foo
class Bar(object):
...
def __del__(self):
foo.cleanup(self.myhandle)
و بعد، توی فایل another_mod.py سعی میکنید این کارو انجام بدید:
import mod
mybar = mod.Bar()
نتیجه؟ یه خطای AttributeError دریافت میکنید.
چرا؟
چون وقتی که مفسر پایتون (Python Interpreter) خاموش میشه، تمام متغیرهای سراسری (Global Variables) ماژولها به None تنظیم میشن.
پس در مثال بالا، زمانی که متد __del__ اجرا میشه، متغیر foo قبلا مقدار None گرفته و دیگه قابل استفاده نیست.
راهحل:
برای جلوگیری از این مشکل، به جای __del__ میتونید از () atexit.register استفاده کنید.
چرا؟
- چون قبل از اینکه مفسر خاموش بشه، توابعی که با () 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)
این پیادهسازی یه روش تمیز و مطمئن برای اجرای هر نوع عملیات پاکسازی (Cleanup) هنگام پایان عادی برنامه فراهم میکنه.
البته، این foo.cleanup هست که تصمیم میگیره با شیء متصل به self.myhandle چیکار کنه، اما حالا دیگه کلیت موضوع رو متوجه شدید.
اشتباهات رایج پایتون: مشکلاتی که وقتی جزئیات رو بدونید، قابل اجتناب هستن
پایتون یه زبان قدرتمند و انعطاف پذیر هست که مکانیزم ها و الگوهای زیادی داره که میتونن بهره وری (Productivity) رو تا حد زیادی افزایش بدن.
اما مثل هر ابزار یا زبان برنامه نویسی دیگهای، درک محدود از قابلیت هاش میتونه بیشتر از این که کمک کنه، مانع پیشرفت بشه. اینجاست که “دانستن به اندازهای که خطرناک بشید” معنا پیدا میکنه!
آشنایی با جزئیات کلیدی پایتون، مثل نکاتی که توی این مقاله بهشون اشاره کردیم به شما کمک میکنه تا:
بهینه تر از زبان استفاده کنید
از رایج ترین خطاها و مشکلات در پایتون جلوگیری کنید
امیدواریم نکات این مقاله براتون مفید بوده باشه و منتظر بازخوردهای شما هستیم! یادتون نره نظرات و سوالاتتون رو برای ما کامنت کنین.