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

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

یک برنامه ساده پایتون احتمالاً مشکل زیادی با حافظه نخواهد داشت، اما وقتی با پروژههایی که به حافظه بالایی نیاز دارند سروکار داریم، مسئله استفاده از حافظه بسیار مهم میشود. همواره توصیه میشود که وقتی بر روی یک پروژه بزرگ کار میکنیم، میزان استفاده از حافظه را از همان آغاز کار، در ذهن داشته باشیم.
برخلاف زبان ++C/C، مفسر پایتون مدیریت حافظه را خود بر عهده میگیرد و کاربر کنترلی بر روی آن ندارد. مدیریت حافظه در پایتون به وسیله یک هیپ (heap) خصوصی انجام میشود که همه اشیا و ساختارهای داده پایتون در آن قرار دارند.
مدیر حافظه پایتون به صورت داخلی مسئله مدیریت این هیپ اختصاصی را تضمین میکند. زمانی که یک شی ایجاد میشود، ماشین مجازی پایتون حافظه مورد نیاز را مدیریت میکند و تصمیم میگیرد که این شی باید در کجای چیدمان حافظه قرار بگیرد.
با این وجود داشتن بینشی از نحوه عملکرد پایتون و روشهای مختلف انجام کارها، کمک میکند که استفاده برنامه از حافظه را تا حد امکان، کاهش دهیم.
از تولیدکنندهها استفاده کنید
از تولیدکنندهها (generator) برای محاسبه مجموعههای بزرگی از نتایج استفاده کنید. تولیدکنندهها امکان ارزیابی کُند را به ما میدهند. میتوان با استفاده از تکنیکهای تکرار، از آنها استفاده کرد. مثلاً میتوان آنها را به طور صریح با استفاده از «for» یا از طریق ارسال به تابع یا سازهای که تکرار میشود، مورد استفاده قرار داد. تولیدکنندهها چند آیتم را درست مانند یک فهرست بازمیگردانند، با این تفاوت که این آیتمها را یکبهیک برمیگردانند. تابع تولیدکننده تا زمانی که آیتم دیگری تقاضا شود، مکث میکند.
از کتابخانه هایی مانند NumPy استفاده کنید
برای ایجاد رشتههای بزرگ از عملگر (+) استفاده نکنید
از آنجا که در پایتون str یک شی غیر قابلتغییر است استفاده از عملگر (+) توصیه نمیشود. چون در این صورت رشتههای چپ و راست این عملگر باید به یک رشته جدید کپی شوند تا یک رشته جدید ساخته شود. اگر بخواهیم ۴ رشته به طول ۱۰ کاراکتر را به هم الحاق کنیم باید (10+10) + ((10+10)+10) + (((10+10)+10)+10) = 90 کاراکتر را به جای ۴۰ کاراکتر کپی کنیم. وقتی اندازه و تعداد رشتهها افزایش یابد، این وضعیت به صورت تابع درجه دوم، وخیمتر میشود.
در جاوا این حالت به وسیله تبدیل سری الحاقها با استفاده از StringBuilder بهینهسازی میشود اما CPython چنین حالتی ندارد، بنابراین توصیه میشود که از عملگرهای Format. و % استفاده شود. هرچند این عملگرها برای رشتههای کوتاه کمی کندتر از + عمل میکنند. گزینه بهتر این است که اگر محتوایی که میخواهیم الحاق کنیم به شکل یک شی قابل تکرار باشد در این صورت میتوان از Join(iterable_object)." استفاده کرد که بسیار سریعتر است.
Def Add_string_with_plus(iters):
s = ""
For i In range(iters):
s += "xyz"
Assert len(s) == 3*iters
Def Add_string_with_format(iters):
fs = "{}"*iters
s = fs.Format(*(["xyz"]*iters))
Assert len(s) == 3*iters
Def Add_string_with_join(iters):
l = []
For i In range(iters):
l.Append("xyz")
s = "".Join(l)
Assert len(s) == 3*iters
Def Convert_list_to_string(l, iters):
s = "".Join(l)
Assert len(s) == 3*iters
خروجی:
>>> timeit(add_string_with_plus(10000)) 100 loops, best of 3: 9.73 ms per loop >>> timeit(add_string_with_format(10000)) 100 loops, best of 3: 5.47 ms per loop >>> timeit(add_string_with_join(10000)) 100 loops, best of 3: 10.1 ms per loop >>> l = ["xyz"]*10000 >>> timeit(convert_list_to_string(l, 10000)) 10000 loops, best of 3: 75.3 µs per loop

هنگام تعریف کردن یک کلاس پایتون از slots استفاده کنید
به این ترتیب به پایتون میگوییم که از یک dict دینامیک استفاده نکند و تنها به مجموعه ثابتی از خصوصیات، فضا اختصاص دهد. بنابراین سربار استفاده از یک dict برای هر شیء با تعیین __slots__ بر روی کلاس به فهرست ثابتی از نام خصوصیات کاهش مییابد. Slots همچنین از انتساب خصوصیات دلخواه بر روی یک شی جلوگیری میکند و بنابراین شکل شی در همه جا یکسان باقی میماند.
استفاده از ماژولهای داخلی
ابزارهای مدیریت حافظه
رویه دوم: کد زیبا بنویسید چون «اولین تأثیر، آخرین تأثیر است»

در این راهنماها در مورد نحوه استفاده از فواصل، کاما و آکولاد و همچنین نامگذاری اشیا توضیح داده شده است. با اینکه ممکن است در مواردی این راهنماها با همدیگر تعارض داشته باشند، اما همه آنها یک هدف مشخص دارند: «یک استاندارد تمیز، خوانا و قابل دیباگ برای کد».
چه از یک راهنما پیروی کنید، یا سبک شخصی خودتان را داشته باشید، در هر صورت رویه کد نویسی شما نمیبایست تفاوت زیادی با استانداردهایی که به صورت عمومی پذیرفته شدهاند، داشته باشد.
استفاده از ابزارهای استاتیک تحلیل کد
ابزارهای اوپنسورس زیادی وجود دارند که میتوان برای هماهنگ ساختن کد با سبکهای استاندارد و رویههای بهینه کد نویسی استفاده کرد.
این ابزار همچنین گزارشهایی به همراه آمارها و معیارهای مفید ارائه میدهد که به وسیله آن میتوان در مورد کیفیت کد داوری کرد. همچنین میتوان یک فایل Pylintrc. سفارشی ایجاد کرد و از آن استفاده نمود.
Pylint تنها گزینه موجود نیست، ابزارهای دیگری مانند PyChecker, PyFlakes و بستههایی مانند pep8 و flakes8 نیز وجود دارند.
مستندسازی صحیح کد
این جنبه از کد نویسی برای حفظ قابلیت استفاده و خوانایی کد بسیار حائز اهمیت است. همواره توصیه میشود که تا بیشترین حد ممکن برای کد، مستندات تهیه شود تا دیگر توسعهدهندگان برای درک کد با دشواری کمتری مواجه شوند.
به طور معمول مستندات درونخطی یک تابع باید شامل موارد زیر باشد:
- یک خلاصه یکخطی از کاری که تابع انجام میدهد.
- در صورت نیاز ارائه مثالهای تعاملی، مناسب است. به این ترتیب توسعهدهندگان جدید میتوانند نحوه استفاده و خروجی تابع را به سرعت مشاهده کنند. همچنین میتوان از ماژول doctest برای تضمین صحیح بودن این مثالها استفاده کرد. برای توضیحات بیشتر به مستندات آن مراجعه کنید.
- مستندسازی پارامترها معمولاً به صورت توضیح یکخطی در مورد پارامتر و نقش آن در تابع صورت میگیرد.
- مستندسازی نوع بازگشتی، مگر در مواردی که تابع مقداری بازنمیگرداند، مناسب خواهد بود.

رویه سوم: عملکرد کد را افزایش دهید
چند پردازشی، نه چند رشتهای
زمانی که با موضوع بهبود زمان اجرای کدهای چندوظیفهای مواجه میشویم، ممکن است بخواهیم از چند هستهی CPU برای اجرای همزمان وظایف مختلف استفاده کنیم. میتوان به عنوان یک روش ابتکاری چند رشته ایجاد کرد و اجازه داد به صورت همزمان اجرا شوند. اما به دلیل اینکه در پایتون یک قفل مفسر سراسری وجود دارد، هر کاری که بکنید رشتهها به ترتیب بر روی یک هسته اجرا خواهند شد.
1. سیستمعامل (با انجام وظایف چند پردازشی)
2. یک برنامه خارجی که کد پایتون شما را فراخوانی میکند (مانند Spark یا Hadoop).
3. نوشتن کد دیگری که کد پایتون شما، آن را فراخوانی کند (برای مثال میتوانید کد پایتون خود را طوری بنویسید که یک تابع C را فراخوانی کند و این تابع کارهای سنگین چند رشتهای را انجام دهد)
جدا از بحث کد نویسی چند برنامهای، روشهای دیگری هم برای بهبود عملکرد وجود دارد. برخی از آنها را در ادامه فهرست کردهایم.
استفاده از آخرین نسخه پایتون
این یکی از سرراستترین کارها محسوب میشود. چون بهروزرسانیهای جدید، معمولاً شامل بهبودهایی برای کارکردهای موجود پایتون هستند که جهت افزایش عملکرد مفید محسوب میشوند.
استفاده از تابعهای درونی، هر جا که ممکن باشد
تابعهای درونی به دقت طراحی شدهاند و از سوی برخی از بهترین توسعهدهندگان پایتون در سراسر جهان بازبینی شدهاند. بنابراین در اغلب موارد بهترین روش انجام کارها محسوب میشوند.
از Ctypes استفاده کنید
از Cython استفاده کنید
از PyPy استفاده کنید
به طراحی و ساختار دادهها دقت کنید
این توصیه در مورد هر زبانی صدق میکند. مطمئن شوید که از ساختار داده درستی استفاده میکنید، متغیرها را در محل صحیح اعلان کنید، از حوزه تعریف معقولی استفاده کنید و هر جا که ممکن باشد نتایجتان را کَش کنید.
به طور مشخص در مورد زبان پایتون میتوان گفت که این زبان معمولاً در زمان دسترسی به متغیرهای سراسری و شناسایی آدرسهای توابع، کند عمل میکند، بنابراین تعریف متغیرهای محلی، باعث میشود دسترسی به آنها در حوزه مربوطهشان و اجرای برنامه سریعتر شود.

رویه چهارم: نسخه صحیح را انتخاب کنید
پایتون ۲ یا پایتون ۳
ازیک سو پایتون ۳ برخی خصوصیات جدید و عالی در خود دارد. از سوی دیگر ممکن است بخواهید از بستهای استفاده کنید که تنها پایتون 2 از آن پشتیبانی میکند. برنامه پایتون ۳ با نسخههای قبلی سازگاری ندارد. این بدین معنی است که اجرای کد پایتون ۲ بر روی مفسر پایتون 3 احتمالاً با خطا مواجه خواهد شد.
توصیه میشود که هنگام آغاز یک پروژه از جدیدترین نسخه پایتون استفاده کنید، اما اگر بنا به دلایلی مجبور شدید از پایتون 2 استفاده کنید در این صورت بهتر است کدتان را طوری بنویسید که بر روی مفسر پایتون 2 و همچنین پایتون ۳ کار کند. متداولترین روش بدین منظور استفاده از بستههای مانند future, builtins, و six است که یک کد تمیز و سازگار با پایتون 3 تولید میکنند که از هر دو پایتون نسخه ۲ و ۳ با کمترین سربار اضافی پشتیبانی میکنند.
مدیریت الزامات pip (مدیر بسته پایتون)
به طور کلی همه وابستگیهای pip برای یک پروژه در یک فایل به نام requirements.txt مشخص میشوند که در ریشه پروژه قرار دارد. اگر فرد دیگری سعی کند پروژه شما را اجرا کند میتواند بهسادگی با استفاده از این فایل و فرمان pip install-r requirements.txt همه پیشنیازها را نصب کند. همچنین توصیه میشود که وابستگیهای لازم برای اجرای تستها در یک فایل جداگانه به نام test-requirements.txt قرار گیرند.
توجه داشته باشید که وقتی پروژه شما به عنوان یک وابستگی برای پروژه دیگر نصب میشود، pip از فایل requirements.txt استفاده نمیکند. برای حل این مشکل معمولاً وابستگیها در آرگومانهای install_requires و tests_require تابع setuptools.Setup در فایل setup.py پروژه تعیین میشود. اگر بخواهید یک فایل تعیین وابستگیهای مشترک برای بستهبندی و توسعه داشته باشید میتوانید مانند زیر عمل کنید.
Import os
from setuptools import setup
With open('requirements.Txt') as f:
required = f.Read().Splitlines()
Setup(...
install_requires=required,
...)
از محیطهای مجازی استفاده کنید
نسخه بندی پروژه
رویه پنجم: کد خود را تحلیل کنید
این ماژول همچنین مشخص میکند که هر تابع چند بار فراخوان شده است و بدین ترتیب محلهایی که باید بهینهسازی شوند به آسانی مشخص میشوند. یک تحلیل ساده با استفاده از cProfile چیزی مانند زیر خواهد بود:
- memory_profiler یک ماژول پایتون برای نظارت بر مصرف حافظه در پردازشها و همچنین تحلیل خط به خط مصرف حافظه در برنامههای پایتون است.
- Objgraph امکان نمایش N شیئی که بیشترین مقدار حافظه را در پایتون اشغال کردهاند، فراهم کرده است. این ماژول همچنین اشیایی که حذف یا اضافه شدهاند و همه ارجاعها به اشیای مفروض را در کد شما مشخص میکند.
- resource سازوکار اولیهای برای اندازهگیری و کنترل منابع مورد استفاده برنامه ارائه میدهد. دو استفاده مقدماتی از این ماژول، شامل محدودسازی تخصیص منابع و کسب اطلاعات در مورد استفاده کنونی از منابع است.

رویه ششم: تست کردن و یکپارچهسازی مداوم
تست کردن
- Nose میتواند تستهای unittest را اجرا کند و به کد نویسی زیادی نیاز ندارد.
- Pytest هم تستهای unittest را اجرا میکند و کدهای تکراری کمی دارد. این ماژول گزارش دهی بهتری دارد و امکانات اضافی و جالب زیادی در خود جای داده است. برای داشتن یک مقایسه خوب در میان این ماژولها میتوانید به این نوشته مراجعه کنید.
البته ماژول doctest را نیز نباید فراموش کنیم که کد شما را با استفاده از مثالهای تعاملی که در مستندات درونخطی توضیح داده شدهاند، تست میکند.
اندازهگیری پوشش
اندازهگیری پوشش معمولاً برای سنجش کارآمدی تستها مورد استفاده قرار میگیرد. این ابزار همچنین نشان میدهد که کدام بخشهای کد به وسیله تستها آزموده شدهاند و کدام بخشها نشدهاند. معمولاً توصیه میشود که برنامه به پوشش ۱۰۰ درصدی دست یابد. این بدین معنی است که تستها باید بتوانند خروجی همه بخشهای پروژه را اجرا و تأیید کنند.
یکپارچهسازی پیوسته
یکپارچهسازی پیوسته (CI) یک سرویس برای پروژه است که از همان ابتدای آغاز به کار مورد توجه قرار میگیرد و در بلندمدت برای پروژه مفید خواهد بود. با استفاده از سرویس CI میتوان جنبههای مختلف کد را به راحتی مورد تست قرار داد. برخی از بررسیهای معمولی که در CI انجام میشوند شامل موارد زیر هستند:
- اجرای تست در محیط دنیای واقعی. مواردی وجود دارند که تستها بر روی برخی معماریها با موفقیت همراه هستند، ولی بر روی برخی دیگر شکست میخورند. سرویس CI میتواند تستهای شما را بر روی معماری سیستمهای مختلف اجرا کند.
- سرویس CI میتواند پوشش کامل کد شما را تضمین کند.
- CI کد پروژه را در محیط عملی ساخته و توزیع میکند (این کار در پلتفرمهای مختلف انجام میشود).
در این نوشته تلاش کردیم برخی رویههای کدنویسی کارآمد برای زبان برنامهنویسی پایتون را با شما به اشتراک بگذاریم. در طی این مسیر 6 رویه صحیح را با ذکر مثال و ارجاع به راهنماهای مختلف بررسی کردیم. اگر این نوشته برای شما مفید بوده است احتمالاً مطالب زیر نیز مورد توجه شما واقع خواهند شد:
==
مطالب مجله فرادرس را در جستجوی گوگل سریعتر ببینید













