PEP 8 چیست؟ نقش PEP8 در زبان پایتون
هر برنامه نویس پایتونی با هر سطحی، میخواد یک برنامه نویس مبتدی باشه که شروع به یادگیری و آموزش پایتون میکنه و از خودش میپرسه پایتون چیست تا یک برنامه نویس حرفه ای زبان پایتون؛ همه برنامه نویس ها سعی میکنن که اصول کدنویسی تمیز(clean code) رو رعایت کنن و کد های استاندارد تر و اصولی تری بنویسن.
PEP8 چیست؟
PEP 8 یک استاندارد است که شامل یک سری دستورالعمل است که رعایت کردن اون ها باعث میشه خوانایی و هماهنگی کد ها بالاتر بره که نتیجه رعایت PEP8 که یک شیوهنامه نگارش(Style Guide) برای پایتون است کد ها رو استانداردتر و خوانا تر میکنه.
خود واژه PEP مخفف Python Enhancement Proposal که به معنی پیشنهادی برای بهبود پایتون است. PEP های زیادی وجود دارن که هر PEP یک Index دارد که یک عدد است تا PEP ها رو از هم تفکیک و مرتب کنه که یکی از کاربردی ترین اون ها PEP 8 است.
یکی از نویسنده های PEP 8 خالق پایتون Guido van Rossum است، یکی از دیدگاه هایی Guido van Rossum این بود:
“““ کد ها خیلی بیشتر از اینکه نوشته بشن خونده میشن “““
دستورالعمل هایی هم که در PEP8 گفته شده به خوانایی و سازگاری کدها اشاره میکنن و میگه که تا جایی که امکانش هست از اون ها پیروی کنید ولی بعضی اوقات شرایطی وجود داره که باعث میشه که اصول این شیوهنامه رو نادیده بگیریم که شامل:
- 1. زمانی که خوانایی کد به خطر بیفته: هدف اصلی PEP 8 بهبود خوانایی کد است پس اگه رعایت یک قانون خاص در PEP 8 باعث بشه خوانایی کد کاهش پیدا کنه میشه از رعایت اون قانون صرف نظر کرد.
- 2. کار کردن با کدهای بسیار قدیمی: یکسری کدها ممکنه حتی قبل از معرفی PEP 8 نوشته شده باشن در چنین موقعیت هایی تغییر سبک نگارش به طور کامل میتونه خیلی هزینه بر و زمان بر باشه.
- 3. برای یکدست شدن با سایر کد های پروژه: اگر در تمام کدهای دیگه پروژه PEP 8 رعایت نشده باشه، برای اینکه سبک نگارش در کل پروژه یکدست بمونه، میشه از رعایت PEP 8 صرفه نظر کرد.
- 4. زمانی که سرعت اجرا مهم تر از خوانایی کد باشه: در بعضی موارد بخصوص مثل برنامه هایی با کارایی بالا که سرعت اجرا میتونه اولویت پیدا کنه نسبت به خوانایی کد، توی چنین مواردی ممکنه از بعضی قوانین PEP 8 برای سرعت بیشتر صرفه نظر کرد.
- 5. سازگاری کد با ورژن های قدیمی پایتون: مواقعی که نیازه کد با ورژن های قدیمی پایتون سازگار باشه و ممکنه که اون ورژن قدیمی از روش های داخل PEP 8 پشتیبانی نکنه.
1. تورفتگی (Indentation)
PEP 8 پیشنهاد میکنه که برای هر پله تورفتگی (Indentation level) از 4 تا اسپیس یا همون فاصله استفاده کنیم و از استفاده همزمان تب و اسپیس با هم خودداری کنیم پس برای تورفتگی از اسپیس استفاده کنید.
خطوط ادامه داری که در چند خط تورفتگی برایش ایجاد میشه بهتره که از نظر عمودی هم تراز باشن به طوری که اگه کسی اون کد رو دید متوجه بشه که این شکستگی های ایجاد شده مربوط به یک خط بلند بوده به طور مثال:
# Correct:
# Aligned with opening delimiter.
foo = long_function_name(var_one, var_two,
var_three, var_four)
# Add 4 spaces (an extra level of indentation) to distinguish arguments from the rest.
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
# Hanging indents should add a level.
foo = long_function_name(
var_one, var_two,
var_three, var_four)
# Wrong:
# Arguments on first line forbidden when not using vertical alignment.
foo = long_function_name(var_one, var_two,
var_three, var_four)
# Further indentation required as indentation is not distinguishable.
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
رعایت این قانون 4 اسپیش برای Indentation اجباری نیست میشه برای خط های ادامه دار نادیده گرفته بشه:
# Hanging indents *may* be indented to other than 4 spaces.
foo = long_function_name(
var_one, var_two,
var_three, var_four)
2. بیشترین طول خط
بیشترین اندازه طول یک خط نباید بیشتر از 79 خط باشه.
کتابخانه استاندارد پایتون خیلی محافظه کاره واسه همین لازمه که تمام خطوط کمتر از 79 کارکتر باشه. برای بلوک های متنی که محدودیت ساختاری ندارن (مثل کامنت ها و داک استرینگ ها) طول خط نباید بیشتر از 72 کارکتر باشه.
3. خطوط بعد از عملگر های ریاضی شکسته بشن یا قبلش؟
برای مدت ها روشی که استفاده میشد این بود که خطوط بعد از عملگر هایی ریاضی شکسته بشن، ولی بعد ها متوجه شدن که توی این حالت سخت میشه فهمید که کدوم عبارت داره کم میشه کدوم جمع میشه و چشم کسی کد رو میخونه باید بیشتر کار کنه تا کد رو متوجه بشه برای مثال:
# Wrong:
# operators sit far away from their operands
income = (gross_wages +
taxable_interest +
(dividends - qualified_dividends) -
ira_deduction -
student_loan_interest)
برای حل مشکل خانوایی کد، خطوط قبل از عملگرهای ریاضی شکسته میشه(در واقع عملگر ریاضی اول عبارت سکشته شده نوشته میشه) مثل:
# Correct:
# easy to match operators with operands
income = (gross_wages
+ taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction
- student_loan_interest)
4. ایمپورت ها (imports)
ایمپورت ها بهتره که در خطوط جداگانه باشن:
# Correct:
import os
import sys
# Wrong:
import sys, os
هر چند مشکلی هم نداره اگه این طوری باشه:
# Correct:
from subprocess import Popen, PIPE
ایمپورت ها معمولا توی ابتدای فایل نوشته میشن، درست تر اینه که بگیم بعد از کامنت ها و مستندات اون ماژول ولی قبل از تعریف متغیر های ماژول قرار میگیره.
ترتیب گروه بندی ایمپورت ها:
- 1. ایمپورت های کتابخانه استاندارد
- 2. ایمپورت های کتابخانه شخص ثالث
- 3. ایمپورت کتابخانه ها و کدهای محلی(local)
بین هر کدام از گروه ایمپورت ها یک خط خالی باید گذاشته بشه.
معمولا پیشنهاد میشه که ایمپورت ها رو به صورت مطلق (Absolute) انجام دهید چون معمولا خواناترن (البته نه همیشه):
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example
البته فکر نکنید هیچوت از ایمپورت های نسبی (Relative) استفاده نمیشه، مثلا در شرایطی که ترتیب پیچیده بسته ها وجود داره استفاده از ایمپورت های مطلق منطقی نیست چون کدها رو شلوغ میکنه و خانوایی کد رو کاهش میده پس:
from . import sibling
from .sibling import example
برای کدهای کتابخانه استاندارد همیشه از ایمپورت های مطلق استفاده کنید
5. قراردادهای نامگذاری
- -کلاس ها (class): معمولا بصورت CapWords یا PascalCase که یک قرارداد (convention) است نام گذاری میشن که حرف اول کلمات بصورت بزرگ و باقی کلمات کوچیک و بدون فاصله و underscore:
# Correct class MyClass: pass
# Wrong class my_Class: pass
- -توابع، ماژول ها، متغیرها: مطابق قرارداد snake_case نامگذاری میشن که همه حروف کوچک هستن و کلمات با underscore از هم جدا میشن:
# Correct import module_name def function_name(): pass my_name = "Saeed Amini"
# Wrong import ModuleName def FunctionName(): pass MyName = "Saeed Amini"
- -اکسپشن ها (Exceptions): مثل class ها بصورت PascalCase نامگذاری میشود:
# Correct try: pass except TypeError: pass
# Wrong try: pass except typeError: pass
- -ثابت ها (Constants): تمامی حروف بصورت بزرگ است و کلمات با underscore از هم جدا میشن:
# Correct PI = 3.14 GRAVITY = 9.8 MY_DOMAIN = "https://geekbaz.com/"
# Wrong pi = 3.14 Gravity = 9.8 MYDOMAIN = "https://geekbaz.com/"
- -پکیج ها: باید بصورت حروف کوچیک باشن و نمیشه برای جداسازی کلمات از underscore استفاده کرد:
# Correct import pandas import polars
# Wrong import Pandas import POLARS
6. فضای خالی در دستورات و عبارات:
از گذاشتن فضای خالی (فاصله) در موقعیت های زیر خودداری کنید:
- -بعد از باز کردن پرانتز، بریس و براکت؛ همچنین قبل از بستن آنها:
# Correct: spam(ham[1], {eggs: 2})
# Wrong: spam( ham[ 1 ], { eggs: 2 } )
- -بعد از کاما انتهایی و قبل از بستن پرانتز:
# Correct: foo = (0,)
# Wrong: bar = (0, )
- -قبل از کاما، سمیکالِن، یا کالِن:
# Correct: if x == 4: print(x, y); x, y = y, x
# Wrong: if x == 4 : print(x , y) ; x , y = y , x
- -اسلایس بندی لیست ها:
# Correct: ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
# Wrong ham[1: 9], ham[1 :9], ham[1:9 :3]
- -قبل از باز کردن پرانتز در صدا زدن توابع:
# Correct: spam(1)
# Wrong: spam (1)
- -قبل از باز کردن براکت مربوط به ایندکس لیست و دیکشنری:
# Correct: dct['key'] = lst[index]
# Wrong: dct ['key'] = lst [index]
- -بیشتر از یک فاصله اطراف عملگر مساوی(=) برای اینکه با کلمه های خطوط دیگه هم تراز بشه:
# Correct: x = 1 y = 2 long_variable = 3
# Wrong: x = 1 y = 2 long_variable = 3
7. زمان استفاده از کاما انتهایی
استفاده کامای انتهایی معمولا اختیاریه البته توی تاپل های تک عضوی، وجود کاما اجباری میشه:
# Correct:
FILES = ('setup.cfg',)
# Wrong:
FILES = 'setup.cfg',
در موارد زیر گذاشتن کامای انتهایی اضافی میتونه مفید باشه:
- 1. اکثرا در مواقعی که از سیستم های کنترل ورژن(مثل git) استفاده میشه
- 2. مواقعی که انتظار میره که در طول زمان لیستی از مقادیر، آرگومان ها یا آیتم های ایمپورت شده زیاد بشن. الگوی استفاده از اون هم این طوریه که مقادیر در هر خط نوشته و انتهاش یه کاما اضافه کنیم و بعد پرانتز یا براکت یا بریس رو در خط بعدی بنویسیم، هر چند کامای انتهایی در پرانتز یا براکت یا بریس در یک خط بی معنیه (به جز تاپل های تک عضوی).
# Correct:
FILES = [
'setup.cfg',
'tox.ini',
]
initialize(FILES,
error=True,
)
# Wrong:
FILES = ['setup.cfg', 'tox.ini',]
initialize(FILES, error=True,)
8. کامنت ها
نکاتی درمورد کامنت ها:
- 1. بدتر از این ننوشتن کامنت، کامنتیه که با کد در تناقض باشه، پس هنگام تغییر کد، کامنت ها رو هم حتما آپدیت کنید
- 2. کامنت ها باید جملات کاملی باشن. کلمه اول باید با حرف بزرگ شروع بشن مگر اینکه به یک شناسه (متغیر، تایع، ماژول و...) اشاره شده باشه که حرف اولش کوچیگ باشه
- 3. کامنت ها حتما باید به زبان انگلیسی نوشته بشن حتی اگر اون برنامه نویس پایتون انگلیسی زبان نباشه مگر اینکه 120% مطمئن باشید که هیچوقت کدهای شما توسط هیچ برنامه نویسی که غیر هم زبان خودتون باشه خونده نمیشه
9. کامنت های درون خطی
تا جایی که میتونین از کامنت های درون خطی (inline comments) استفاده نکنید.
کامنت های درون خطی کامنت هایی هستن که توی همون خط کد نوشته میشه. این کامنت ها باید حداقل با دو فاصله از کد نوشته بشه. متن کامنت باید بعد از یک فاصله بعد از # شروع بشه.
اگر کامنت درون خطی یک موضوع خیلی واضح رو توضیح بده کاملا غیر ضروری و اضافه کاریه، سعی کنید انجام ندید:
x = x + 1 # Increment x
ولی مواقعی میتونن مفید باشن:
x = x + 1 # Compensate for border
10. توصیه های برنامه نویسی
- -کد ها باید طوری نوشته بشن که به پیاده سازی های دیگه پایتون آسیب نزنه مثل (PyPy, Jython, IronPython, Cython, Psycoو..)، به عبارت هایی مثل a += b یا a = a + b تکیه نکنید. چنین بهینه سازی هایی حتی توی CPython هم قابل اعتماد نیست(چون فقط برای یکسری از انواع داده کار میکند).
توی بخش هایی که عملکرد حساسی دارن استفاده از متد .join() به جای a += b یا a = a + b پیشنهاد میشه. - -مقایسه با singleton هایی مثل None باید از is یا is not به جای عملگر مساوی (=) استفاده بشه
# Correct: x = None if x is None: pass
# Wrong: x = None if x == None: pass
- -اگر توی class ها خواستید از عملیات های مقایسه ای استفاده کنید بهتره که از متدهایی مثل (
__eq__
,__ne__
,__lt__
,__le__
,__gt__
,__ge__
) استفاده کنین. حتی برای حداقل رسوندن پیچیدگی ابزاری به نام ()functools.total_ordering ارائه شده. - -همیشه به جای تخصیص مستقیم عبارت لامبدا به یک شناسه، از یک دستور def استفاده کنید:
# Correct: def f(x): return 2*x
# Wrong: f = lambda x: 2*x
شکل اول برای ردیابی و نمایش رشته ها مناسب تره.
-
-مواقعی که میخواید پسوند و پیشوند یک رشته رو چک کنید بهتره به جای اسلایس کردن رشته از "".startswith() و "".endswith() استفاده کنید که هم تمیز تر و هم کم خطا ترن:
# Correct: if foo.startswith('bar'):
# Wrong: if foo[:3] == 'bar':
- -برای مقایسه نوع (type) یک شئ، به جای مقایسه مستقیم از متد ()isinstance استفاده کنید:
# Correct: if isinstance(obj, int):
# Wrong: if type(obj) is type(1):
- -برای نوع داده هایی که ترتیب دارن (sequences) که شامل (strings, lists, tuples) این نکته در نظر بگیرید که همیشه True در نظر گرفته میشن مگر اینکه خالی باشن:
# Correct: seq = list() if not seq: pass if seq: pass
# Wrong: if len(seq): pass if not len(seq): pass
- مقادیر بولی (Boolean) رو با عملگر های مقایسه ای، مقایسه نکنین:
# Correct: if greeting: pass
# Wrong: if greeting == True: pass
بدترین:
# Wrong: if greeting is True: pass
سخن آخر
توی این مقاله سعی کردیم اکثر موارد رو در PEP 8 برای علاقه مندان پایتون به زبان ساده جا بندازیم، ولی خوندن خود داکیومنت PEP 8 هم برای اطلاعات بیشتر میتونه براتون مفید باشه.
سعید امینی
نویسنده مقالهالان بیشتر از پنج ساله که مشغول برنامه نویسی وب هستم و در طول این مدت کلی چالش رو پشت سر گذاشتم و عاشق اینم که هر چیزی رو که در این مدت یاد گرفتم رو به بقیه هم یاد بدم، الانم در بستر سایت گیک باز دارم دانشم رو با بقیه تقسیم میکنم :)