درس ۰۶: سینتکس زبان پایتون

سینتکس یا دستور نحو زبان پایتون

Photo by Tim J

در این درس به معرفی اجزای پایه در زبان برنامه‌نویسی پایتون پرداخته شده و اینکه چه هستند، چه گرامری دارند، چه کاری انجام می‌دهند یا... مورد بررسی قرار گرفته است. همچنین در موارد بسیاری نکته‌هایی از شیوه استاندارد پایتون‌نویسی که توسط سند PEP 8 به برنامه‌نویسان پایتون پیشنهاد می‌شود نیز آورده شده است؛ رعایت این اصول به یکدستی کدهای جامعه پایتون کمک می‌کند. [1]

سینتکس (Syntax) مجموعه‌ای از قواعد است که چگونگی برنامه‌نویسی به یک زبان مشخص را تعریف می‌کند؛ برای نمونه اینکه یک متن چطور نوشته شود که توسط مفسر پایتون به عنوان توضیح در نظر گرفته شود یا یک شی رشته، به رعایت سینتکس تعریف شده در پایتون بستگی دارد و چنانچه مفسر نتواند متن را با هیچ قاعده‌ تعریف شده‌ای مطابقت دهد یک استثنا گزارش خواهد شد. سینتکس پایتون تنها محدود به این درس نیست و موارد بسیار دیگری به مانند چگونگی تعریف اشیا گوناگون را در طی درس‌های آتی مشاهده خواهید کرد.

سطح: مقدماتی



سطرها

مفسر پایتون و همچنین کاربر، کدهای درون هر ماژول را در قالب تعدادی سطر می‌بینند؛ سطرهای فیزیکی (Physical Lines) و منطقی (Logical Lines). سطرهای فیزیکی در واقع همان سطرهایی است که توسط ویرایشگرهای متن شماره‌گذاری می‌شوند و به راحتی توسط کاربر قابل تشخیص هستند ولی سطرهای منطقی برداشت مفسر از اجزای برنامه است؛ هر سطر منطقی بیانگر یک دستور (Statement) پایتون است. برای نمونه دستور انتساب (Assign):

1msg = "Hello World!"
2print(msg)

در نمونه کد بالا: سطر یکم، یک دستور انتساب (Assign) را نشان می‌دهد؛ این دستور مقدار سمت راست خودش را به متغیر msg نسبت می‌دهد. کم و بیش با دستور سطر دوم نیز آشنا هستید، این دستور با فراخواندن تابع print مقدار مربوط به متغیر دریافتی را بر روی خروجی نمایش می‌دهد. در اینجا دو دستور یعنی دو سطر منطقی وجود دارد که هر یک در قالب یک سطر فیزیکی پیاده‌سازی شده است.

هر چند که پیشنهاد می‌شود همیشه هر سطر فیزیکی تنها شامل یک سطر منطقی باشد ولی یک سطر فیزیکی را می‌توان شامل چند سطر منطقی نیز در نظر گرفت:

1msg = "Hello World!"; print(msg)

در این حالت می‌بایست سطرهای منطقی (یا همان دستور‌ها)، توسط کاراکتر ; (Semicolon) از یکدیگر جدا گردند.

گاهی نیز بهتر است برای خوانایی بیشتر، یک سطر منطقی را در قالب چند سطر فیزیکی پیاده‌سازی نماییم:

1msg = "Python Programming \
2Language."  # This is a message.
3print(msg)

در نمونه کد بالا: دو سطر فیزیکی نخست از دید مفسر به شکل تنها یک سطر منطقی دیده می‌شود. در پایتون برای شکستن یک دستور در چند سطر فیزیکی از کاراکتر \ (Backslash) استفاده می‌گردد. البته توجه داشته باشید که از \ نمی‌توان برای شکستن سطر توضیح (Comment) استفاده کرد و همچنین نمی‌توان پس از آن هیچ توضیحی درج کرد.

نکته

[PEP 8]: طول هر سطر فیزیکی نباید از ۷۹ کاراکتر بیشتر شود. برای متن‌های طولانی نیز مانند توضیح (Comment) و Docstring طول هر سطر فیزیکی باید حداکثر ۷۲ کاراکتر در نظر گرفته شود.

برای خوانایی بیشتر بهتر است دستور‌های طولانی شکسته شوند. دستورهایی که شامل { }، [ ] و ( ) هستند را می‌توان بدون استفاده از \ شکست و در قالب چند سطر فیزیکی نوشت:

1month_names = ['Januari', 'Februari', 'Maart',      # These are the
2               'April',   'Mei',      'Juni',       # Dutch names
3               'Juli',    'Augustus', 'September',  # for the months
4               'Oktober', 'November', 'December']   # of the year

که در این حالت برخلاف استفاده از \ می‌توان پس از شکستن سطرها، توضیح نیز اضافه کرد.

«سطرهای خالی» (Blank lines): سطری که تنها حاوی فضای خالی (Spaceها یا Tabها) باشد، توسط مفسر نادیده گرفته می‌شود و به بایت‌کد ترجمه نمی‌گردد. از این سطرها می‌توان برای خوانایی بیشتر کدها بهره گرفت - مانند سطر سوم در نمونه کد پایین:

1def power(a, b):
2    return a ** b
3
4print power(2, 3)

مستند‌سازی

هر چند اساس طراحی زبان پایتون بر خوانایی بالای کد است ولی «مستندسازی» (Documentation) برنامه یعنی استفاده از امکاناتی همچون ارایه توضیح در کدها می‌تواند به درک و خوانایی هر چه بهتر کدهای برنامه برای مراجعات آینده برنامه‌نویس و افراد دیگری که می‌خواهند بر روی توسعه آن فعال باشند یا از آن استفاده کنند نیز بسیار مفید خواهد بود. در این بخش به بررسی دو امکان درج Comment و Docstring برای مستند‌سازی برنامه می‌پردازیم.

توضیح

یک «توضیح» (Comment) در زبان پایتون توسط کاراکتر # آغاز می‌شود و با پایان سطر فیزیکی هم پایان می‌پذیرد. توضیح‌ها نیز مانند سطرهای خالی توسط مفسر نادیده گرفته شده و به بایت‌کد ترجمه نمی‌شوند.

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

توضیح در پایتون تنها به شکل تک سطری تعریف شده است و برای درج توضیح‌هایی با بیش از یک سطر فیزیکی باید توجه داشت که هر سطر به صورت جداگانه می‌بایست با # آغاز شود.

نکته

[PEP 8]: متن توضیح‌ با یک فضای خالی (Space) بعد از # آغاز شود. در توضیح‌های چند سطری برای جداسازی پاراگراف‌ها از یک سطر توضیح بدون متن (سطری خالی که با # آغاز می‌شود) استفاده شود. هنگام درج توضیح در همان سطرهای دستور، توضیح حداقل به اندازه دو فضای خالی از انتهای دستور فاصله داده شود.

1# This is a comment, it helps you to understand the code later on.
2# Anything that comes after the # symbol is ignored by Python.
3
4print("I could have code like this.")   # This comment explains that the following comment will be ignored.
5
6# You can also use a comment to temporarily "disable" or comment out a piece of code:
7# print ("This won't run.")
8
9print("This will run.")

Docstring

در کنار «توضیح»؛ ”Docstring“ نیز امکان دیگری در پایتون برای ارایه توضیح بیشتر درباره کدهای برنامه است. متن Docstring توسط سه علامت نقل قول (""" یا ''') شروع و پایان می‌یابد [PEP 257] و معمولا از آن به عنوان نخستین دستور در ماژول، کلاس، تابع و متد استفاده می‌شود. Docstring توسط مفسر نادیده گرفته نمی‌شود و در زمان اجرا نیز با استفاده از صفت __doc__ قابل دستیابی است:

1def complex(real=0.0, imag=0.0):
2    """Form a complex number.
3
4    Keyword arguments:
5    real -- the real part (default 0.0)
6    imag -- the imaginary part (default 0.0)
7    """
>>> complex.__doc__
'Form a complex number.\n\n    Keyword arguments:\n    real -- the real part (default 0.0)\n    imag -- the imaginary part (default 0.0)\n    '

>>> print(complex.__doc__)
Form a complex number.

    Keyword arguments:
    real -- the real part (default 0.0)
    imag -- the imaginary part (default 0.0)

>>>

ملاحظه

n\ بیانگر پایان سطر جاری و رفتن به سطر بعدی است - برای مشاهده درست این چنین متن‌هایی که حاوی n\ هستند می‌بایست از print استفاده نمایید.

مخاطب متن «توضیح‌» موجود در کد، کاربرانی می‌باشند که آن کد را توسعه می‌دهند در حالی که مخاطب اصلی Docstring‌ها کاربرانی است که از کد مربوط به آن استفاده می‌کنند بنابراین Docstring باید به توضیح چگونگی استفاده از کد (به صورت خاص: ماژول، تابع، کلاس و متد) بپردازد.

Docstring باید به عنوان دستور نخست درج گردد و این نکته برای یک ماژول در صورت وجود سطر اجرای مفسر به صورت پایین در نظر گرفته می‌شود:

#!/usr/bin/env python

"""
Module docstring.
"""

import [...]
[...]

بسته‌ها (Packages) نیز می‌توانند Docstring داشته باشند؛ برای این منظور Docstring باید درون ماژول init__.py__ نوشته شود.

نکته

Docstring‌ها در هر جای دیگری از کدهای برنامه نیز به عنوان جایگزینی برای توضیح‌های چند سطری قابل استفاده هستند که در این حالت مفسر آن‌ها نادیده گرفته و دیگر قابل دستیابی نیستند.

تورفتگی

بلاک‌بندی در زبان پایتون توسط «تورفتگی» (Indentation) سطرها مشخص می‌گردد؛ این عمل در زبان‌هایی مانند C و Java توسط آکولاد { } انجام می‌شود. تورفتگی در واقع عبارت است از میزان فضای خالی (Spaceها و Tabها) هر دستور از ابتدای سطر فیزیکی خود. نکته مهم این است که تمام دستورهای موجود در یک بلاک می‌بایست به یک میزان فاصله نسبت به سرآیند خود تورفتگی داشته باشند:

// C

if (x > y) {
x = 1;
y = 2;
}
# Python

if x > y:
    x = 1
    y = 2

در تصویر پایین به شیوه تورفتگی‌ بلاک‌ها نسبت به سرآیند خود توجه نمایید:

../_images/l06-python-Indentation-block.png

نکته

[PEP 8]: در ایجاد تورفتگی استفاده از کلید Space نسبت به کلید Tab ترجیح داده می‌شود - برای هر مرتبه تورفتگی از چهار کلید Space استفاده نمایید.

روش رایج ایجاد تورفتگی استفاده از کلید Space است و سعی کنید هرگز به صورت ترکیبی از کلیدهای Sapce و Tab استفاده نکنید چرا که از نسخه 3 پایتون امکان استفاده ترکیبی از این دو کلید وجود ندارد! اگر مایل به استفاده از کلید Tab هستید باید به صورت یکدست تمام تورفتگی‌های برنامه خود را فقط با استفاده از آن ایجاد نمایید.

اجباری به تورفتگی آن بخشی از دستورها که به سطرهای فیزیکی دیگر شکسته شده‌اند وجود ندارد اما بهتر است برای خوانایی بالاتر، این بخش‌ها را با کمی تورفتگی بیشتر نسبت به دستور‌های بدنه بلاک جاری نوشته شوند:

1def long_function_name(
2        var_one, var_two, var_three,
3        var_four):
4    print(var_one)

در دستورهایی به مانند پایین نیز ایجاد تراز آرگومان‌ها هم حالت مناسبی است:

1foo = long_function_name(var_one, var_two,
2                         var_three, var_four)

دستور

«دستور» (Statement) واحدی از کد است که شامل کلمه‌های کلیدی بوده، اجرا می‌گردد و کاری را به انجام می‌رساند. در پایتون دو نوع دستور وجود دارد:

دستورهای ساده (Simple Statements): دستورهایی هستند که تنها در یک سطر منطقی پیاده‌سازی می‌شوند. مانند دستور import، دستور انتساب، فراخوانی تابع و...

دستورهای مرکب (Compound Statements): گروهی از دستورها هستند که می‌توانند یک بخشی (مانند: دستور def - تعریف تابع) یا چند بخشی (مانند: دستور شرط if / elif / else) باشند؛ هر بخش (Clause) نیز شامل یک سرآیند (Header) و یک بدنه (Suite) است. هر سرآیند با یک کلمه کلیدی آغاز می‌شود و با یک : (Colon) نیز پایان می‌پذیرد. بدنه پس از سرآیند و با رعایت سطح تورفتگی بیشتر نسبت به آن نوشته می‌شود:

 1def binary_search(seq, key):
 2    lo = 0
 3    hi = len(seq) - 1
 4    while hi >= lo:
 5        mid = lo + (hi - lo) // 2
 6        if seq[mid] < key:
 7            lo = mid + 1
 8        elif seq[mid] > key:
 9            hi = mid - 1
10        else:
11            return mid
12    return False

شناسه

«شناسه» (Identifier) نامی است نمادین که به دلخواه کاربر تعیین و از آن برای شناسایی (identify) متغیر‌ها، توابع، کلاس‌ها، ماژول‌ها یا دیگر اشیا پایتون از یکدیگر استفاده می‌شود. انتخاب شناسه در پایتون نکاتی دارد که می‌بایست از سوی کاربر در نظر گرفته شود:

  • تنها با یکی از حروف بزرگ یا کوچک الفبا انگلیسی (A..Z یا a..z) یا کاراکتر _ (Underscore) شروع شود.

  • در ادامه می‌تواند هیچ یا چند حرف الفبا انگلیسی (کوچک و بزرگ)، کاراکتر _ و عدد (9..0) - با هر ترتیبی - آورده شود.

  • هیچ محدودیتی در طول شناسه وجود ندارد و می‌تواند از یک تا هر تعداد کاراکتر باشد.

با یک نگاه حرفه‌ای‌تر، ساختار لغوی (Lexical) شناسه به شکل پایین بیان می‌شود [اسناد پایتون]:

identifier  ::= (letter|"_") (letter | digit | "_")*

letter      ::= lowercase | uppercase
lowercase   ::= "a"..."z"
uppercase   ::= "A"..."Z"
digit       ::= "0"..."9"

ملاحظه

در تعاریف regex: از پرانتز ( ) برای گروه‌بندی استفاده می‌شود. نماد | به معنی یا می‌باشد و از آن برای جدا‌سازی دو عبارت متفاوت استفاده می‌شود. نماد * به معنی صفر بار تکرار یا بیشتر می‌باشد. [دروس پانزدهم و شانزدهم به بررسی Regular Expression اختصاص یافته است.]

نکته

  • استفاده از کاراکترهای خاص به مانند .، !، @، #، $، % و... مجاز نمی‌باشد.

  • استفاده از «فاصله» (Space) مجاز نمی‌باشد.

  • استفاده از «خط تیره» (Hyphen) یعنی کاراکتر - برای جداسازی کلمه‌ها در نام ماژول مجاز است ولی پیشنهاد نمی‌شود.

برای نمونه - چند شناسه درست:

a    _p    __var    MyClass    get_length    getLength    var2    var_2    S01E16

برای نمونه - چند شناسه نادرست:

me@mail    get.length    2_var    6    $var    4pi

نکته

همانطور که از درس یکم می‌دانیم، پایتون یک زبان حساس به حرف (Case Sensitive) است و مفسر آن بین حروف کوچک (Lowercase) و بزرگ (Uppercase) به مانند a و A تمایز می‌گذارد.

برای نمونه، تمامی شناسه‌های CaR ،cAR ،CAr ،caR ،cAr ،Car ،car و CAR متفاوت با یکدیگر ارزیابی می‌شوند.

در پایتون از تکنیکی به نام Name Mangling استفاده می‌شود. توسط این تکنیک و تنها با شیوه انتخاب شناسه‌ها، نقشی خاص به آن‌ها داده می‌شود:

  • شناسه خصوصی (Private) ماژول: اگر شناسه‌ای با یک کاراکتر _ آغاز شود (و نه پایان پذیرد) توسط مفسر پایتون در این نقش ارزیابی می‌گردد. مانند: name_ (و نه: _name_ یا _name)

  • شناسه خصوصی کلاس: اگر شناسه‌ای با دو کاراکتر _ آغاز شود (و نه پایان پذیرد) توسط مفسر پایتون در این نقش ارزیابی می‌گردد. مانند: name__ (و نه: __name__ یا __name)

جدا از این مورد، در پایتون صفت‌ها (Attributes) و متدهای خاصی وجود دارد که از پیش تعریف گشته‌اند و برای مفسر مفهوم مشخصی دارند. شناسه این صفت‌ها و متدها با دو کاراکتر _ آغاز می‌شود و همینطور پایان می‌پذیرد؛ درست به مانند صفت‌های __class__ و __doc__ که پیش از این استفاده کردیم.

بنابراین به هنگام استفاده از کاراکتر _ در شناسه (به خصوص در ابتدای آن) باید آگاهی کافی داشته باشیم. [به موارد اشاره شده در آینده پرداخته خواهد شد.]

نکته

[PEP 8]: شیوه استاندارد انتخاب شناسه برای کلاس‌، تابع، متد و متغیر به صورت پایین است:

  • کلاس‌ها به شیوه PascalCase - یعنی تنها حرف نخست هر کلمه بزرگ باشد و کلمه‌ها بدون فاصله کنار هم قرار بگیرند - نام‌گذاری شوند. مانند: AnimalClass ،Animal.

  • نام انتخابی برای یک تابع و متد‌ نیز باید تنها شامل حروف کوچک باشد و برای جداسازی کلمه‌ها از _ استفاده شود. مانند: bubble_sort ،binary_search و... البته می‌توان از شیوه camelCase (همانند PascalCase با این تفاوت که حرف نخست کلمه یکم هم می‌بایست حرف کوچک باشد) نیز استفاده نماییم. مانند: bubbleSort ،binarySearch و...

  • نام‌ متغیرها تنها شامل حروف کوچک باشد که کلمه‌های آن توسط _ از یکدیگر جدا شده‌اند. مانند: body_color ،color و...

برای شناسه‌های تک حرفی توجه داشته باشید که از انتخاب حروف l (اِل کوچک) و I (آی بزرگ) بهتر است اجتناب کنید زیرا این دو حرف در برخی فونت‌ها شبیه هم هستند و البته همینطور حرف O (اُ بزرگ) که می‌تواند شبیه به صفر باشد.

کلمه‌های کلیدی

نکته پایانی در مورد شناسه‌ها این است که: نمی‌توان یک شناسه را برابر با یکی از «کلمه‌های کلیدی» (keywords) پایتون انتخاب کرد. کلمه‌های کلیدی در واقع شناسه‌هایی هستند که از پیش برای مفسر پایتون تعریف شده‌اند و معنای مشخصی برای آن دارند. فهرست این کلمه‌های در پایتون به صورت پایین است:

>>> help("keywords")
Here is a list of the Python keywords.  Enter any keyword to get more help.

  False               class               from                or
  None                continue            global              pass
  True                def                 if                  raise
  and                 del                 import              return
  as                  elif                in                  try
  assert              else                is                  while
  async               except              lambda              with
  await               finally             nonlocal            yield
  break               for                 not

در کتابخانه استاندارد پایتون ماژولی به نام keyword وجود دارد [اسناد پایتون]:

>>> import keyword

>>> keyword.iskeyword(a)
False

>>> keyword.iskeyword("def")
True

>>> keyword.kwlist
['False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']

ملاحظه

تابع ()iskeyword بررسی می‌کند که آیا آرگومان دریافتی یکی از کلمه‌های کلیدی می‌باشد یا نه؛ در صورت تایید مقدار True را باز می‌گرداند. kwlist نیز در واقع یک شی لیست حاوی تمام کلمه‌های کلیدی است.

شاید کنجکاو باشید تعداد کلمه‌های کلیدی پایتون را بدانید؛ برای این منظور نیازی به شمارش دستی نیست!:

>>> import keyword
>>> len(keyword.kwlist)
33

ملاحظه

تابع ()len تعداد اعضای یک شی را باز می‌گرداند [اسناد پایتون].

نکته

تنها کلمه‌های کلیدی True ،False و None با حرف بزرگ آغاز می‌شوند.

نکته

[PEP 8]: چنانچه می‌خواهید شناسه‌ای مشابه با یکی از کلمه‌های کلیدی انتخاب نمایید؛ می‌توانید این کار را با استفاده از یک _ در انتهای کلمه مورد نظر به انجام برسانید. مانند: _def

متغیر

یک «متغیر» (Variable) در بیشتر زبان‌های برنامه‌نویسی به مانند C بیانگر محلی در حافظه می‌باشد که مقداری در آن قرار گرفته است. برای نمونه سه دستور پایین را در نظر بگیرید:

int a = 1;

a = 2;

int b = a;

در نمونه کد بالا: دستور ;int a = 1 بیان می‌کند که محلی از حافظه به نام a برای نگهداری اعداد صحیح (integers) در نظر گرفته شود و مقدار 1 در آن قرار بگیرد؛ از این پس متغیر a معرف این نقطه از حافظه می‌باشد (درست به مانند یک جعبه) که اکنون حاوی مقدار 1 است (شکل پایین - یک). در ادامه دستور ;a = 2 باعث می‌شود مقدار پیشین متغیر a حذف (از جعبه خارج) و مقدار جدید یعنی 2 در آن قرار داده شود (شکل پایین - دو). توجه داشته باشید که در این دسته زبان‌ها، نوع (type) توسط متغیر تعیین می‌گردد و تلاش برای قرار دادن نوع داده دیگری به غیر از int در متغیر a (به مانند 3.7 یا "string") باعث بروز خطا در زمان کامپایل می‌گردد. دستور سوم:‌ ;int b = a در ابتدا باعث ایجاد یک محل جدید در حافظه با نام b و از نوع همان اعداد صحیح می‌شود و سپس مقدار درون متغیر a را درون آن کپی می‌کند؛ اکنون دو محل برای نگهداری نوع داده int در حافظه موجود است که هر دو حاوی مقدار 2 می‌باشند (شکل پایین - سه).

../_images/l06-c-variable.png

ولی در پایتون:

یک متغیر چیزی نیست جز یک نام که به یک شی مشخص در حافظه ارجاع (یا اشاره) دارد. تعریف متغیر در پایتون بسیار ساده است و تنها با انتساب (Assign) شی به یک نام ایجاد می‌گردد. نمادِ =، عملگر (Operator) انتساب در پایتون است. در تعریف متغیر پایتون برخلاف آنچه در زبان C مشاهده کردیم ;int a،‌ نیازی به تعیین نوع برای آن نیست چرا که نوع (type) از روی شی تعیین می‌گردد و یک متغیر در طول زمان اجرا می‌تواند به شی‌هایی از انواع متفاوت ارجاع داشته باشد. برای نمونه سه دستور پایین را در نظر بگیرید:

a = 1

a = 2

b = a

مفسر با رسیدن به دستور a = 1، سه گام پایین را انجام می‌دهد:

  1. یک شی از نوع اعداد صحیح و مقدار 1 را در جایی از حافظه ایجاد می‌کند. چرا اعداد صحیح؟ نوع توسط شی تعیین می‌گردد و 1 عددی است صحیح!.

  2. متغیرِ (یا همان نامِ) a را در جایی دیگر از حافظه ایجاد می‌کند (البته در صورتی که قبلا ایجاد نشده باشد).

  3. یک پیوند از متغیر a به شی 1 برقرار می‌کند. به این پیوند «ارجاع» (Reference) گفته می‌شود .

../_images/l06-python-variable-01.png

انتساب شی دیگری (که می‌تواند از هر نوع دیگری باشد) به یک متغیر موجود؛ باعث حذف ارجاع قبلی آن و ارجاع به شی جدید می‌شود. دستور a = 2 باعث ایجاد شی 2، حذف ارجاع متغیر a به شی 1 و ایجاد ارجاعی جدید از متغیر a به شی 2 می‌شود. هر متغیر نامی است برای اشاره به یک شی؛ دستور b = a نیز می‌گوید: یک متغیر جدید با نام b ایجاد گردد و به همان شی‌ای ارجاع داشته باشد که متغیر a ارجاع دارد.

ولی اکنون که ارجاعی به شی 1 وجود ندارد، با آن چه می‌شود؟

هر شی شامل یک «شمارنده ارجاع» (Reference Counter) نیز هست؛ به این صورت که در هر لحظه تعداد ارجاع‌ها به آن شی را نشان می‌دهد و با هر ارجاع جدید به شی، یک واحد به آن اضافه می‌شود و با حذف هر ارجاع نیز یک واحد کاهش می‌یابد. چنانچه مقدار آن به صفر برسد، شی آن توسط تکنیک ”Garbage Collection“ پاک می‌گردد و مقدار حافظه‌ای که توسط شی مصرف شده بود آزاد می‌گردد. برای مشاهده تعداد ارجاع‌ها به یک شی می‌توان از تابع ()getrefcount درون ماژول sys استفاده کرد [اسناد پایتون].

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

>>> import sys

>>> a = 1
>>> sys.getrefcount(1)
760

>>> a = 2
>>> sys.getrefcount(1)
759
>>> sys.getrefcount(2)
96

>>> b = a
>>> sys.getrefcount(2)
97

در نمونه کد بالا همانطور که مشاهده می‌نمایید تعداد ارجاع‌ها به شی 1 و 2 خارج از حد انتظار است که نشان می‌دهد در پشت صحنه اجرای مفسر پایتون نیز ارجاع‌های دیگری به این اشیا وجود دارد. به کاهش و افزایش تعداد ارجاع‌ها در مثال بالا توجه نمایید.

انتساب چندگانه

امکان ایجاد همزمان چند متغیر یا انتساب‌های چندگانه در پایتون وجود دارد - بنابراین می‌توان چند متغیر که همگی به یک شی ارجاع دارند را تنها با یک دستور ایجاد کرد:

>>> a = b = c = "python"

>>> a
'python'
>>> b
'python'
>>> c
'python'

برای انتساب اشیا متفاوت می‌بایست از ویرگول (Comma) و تنها یک عملگر انتساب (=) استفاده نماییم - توجه داشته باشید که تعداد عناصر دو طرف عملگر انتساب می‌بایست برابر باشد:

>>> a, b, c, d = 1, 4.5, "python", 2

>>> a
1
>>> b
4.5
>>> c
'python'
>>> d
2

یکی از کاربردهای انتساب چندگانه این است که می‌توان اشیا دو متغیر را به سادگی و تنها با یک سطر دستور با یکدیگر عوض کرد:

>>> a = 1
>>> b = 2

>>> a, b = b, a

>>> a
2
>>> b
1

ثابت

«ثابت» (Constant) به متغیری گفته می‌شود که مقدار آن همواره ثابت بوده و پس از تعریف دیگر امکان تغییر مقدار آن وجود ندارد. برای نمونه یک ثابت در زبان Java به شکل پایین تعریف می‌گردد - پس از دستور پایین هر گونه تلاش برای تغییر مقدار ثابت HOURS با خطا روبرو می‌گردد:

1final int HOURS = 24;

در پایتون امکانی برای تعریف ثابت پیش‌بینی نشده است!.

علاوه بر امکان ایجاد ثابت‌ها برخی موارد دیگر نیز در پایتون نادیده گرفته شده است. در واقع فرض پایتون بر این است که کاربران او افراد باهوشی هستند که از پس مشکلات برمی‌آیند؛ در نتیجه می‌گوید: من به کاربرانم اعتماد دارم پس نیازی نیست که تمام کارها را من برای آن‌ها انجام دهم، یک برنامه‌نویس باید بتواند ریسک کند!.

ولی برای ایجاد ثابت می‌توانید متغیر‌های مورد نظر خود را در ماژولی جدا تعریف نمایید و در هر جایی که لازم بود با import آن به متغیرهای مورد نظر خود دسترسی یابید:

1# File: constant.py
2# Path: /home/user/Documents/MyModule
3
4HOURS = 24
>>> import sys
>>> sys.path.append('/home/user/Documents/MyModule')

>>> import constant
>>> constant.HOURS
24

البته اگر تغییر‌ناپذیر بودن متغیرها برایتان اهمیت ویژه دارد می‌توانید ماژولی حاوی کد پایین ایجاد نمایید [منبع]:

 1# File: constant.py
 2# Path: /home/user/Documents/MyModule
 3
 4class _const:
 5
 6    class ConstError(TypeError): pass
 7
 8    def __setattr__(self, name, value):
 9        if name in self.__dict__:
10            raise self.ConstError("Can't rebind const(%s)" % name)
11        self.__dict__[name] = value
12
13import sys
14sys.modules[__name__] = _const()
>>> import sys
>>> sys.path.append('/home/user/Documents/MyModule')

>>> import constant
>>> constant.HOURS = 24
>>> constant.HOURS
24
>>> constant.HOURS = 23
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/user/Documents/MyModule/constant.py", line 10, in __setattr__
    raise self.ConstError("Can't rebind const(%s)" % name)
constant.ConstError: Can't rebind const(HOURS)
>>> constant.HOURS
24

درک کد کلاس const_ نیاز به مطالعه دروس مربوط به شی‌گرایی و استثنا‌ها را دارد (Exceptions) دارد. ولی برای توضیحی کوتاه در این درس باید گفت که:

مفسر پایتون برای اینکه بداند کدام نام به کدام مقدار یا شی ارجاع دارد از ساختاری مشابه {... ,name : value} که به نوع دیکشنری (Dictionary) معروف است استفاده می‌کند؛ صفت‌های هر شی و مقدار‌ آن‌ها نیز توسط چنین ساختاری نگهداری می‌شود که برای مشاهده این دیکشنری که در واقع همان فهرستی از صفت‌‌‌های هر شی به همراه مقدار آن‌هاست می‌توانید از صفت ویژه __dict__ استفاده نمایید. متد __setattr__ [اسناد پایتون] از متدهای ویژه است - این متدها امکانی هستند تا بتوانیم برای مواقعی خاص، رفتارهای مشخصی را تعریف نماییم - __setattr__ هر زمان که به یکی از صفت‌های شی‌ای از کلاس مقداری نسبت داده شود به صورت خودکار فراخوانی می‌گردد و وظیفه آن ذخیره صفت‌ها و مقدار آن‌ها در این دیکشنری است.

در اینجا رفتار متد __setattr__ کمی تغییر داده شده است به این صورت که بررسی می‌شود (سطر 9) چنانچه پیش از این صفت مورد نظر وجود نداشته باشد (یعنی: پیش از این هیچ مقداری به آن انتساب داده نشده است که بخواهد در فهرست باشد؛ تعریف متغیر را به یاد بیاورید) همراه با مقدار به فهرست صفت‌های شی افزوده خواهد شد (سطر 11)؛ در غیر این صورت یک خطا گزارش می‌گردد که باعث توقف اجرای متد شده و در نتیجه از درج جدید در فهرست که باعث تغییر مقدار صفت مورد نظر می‌گردد جلوگیری خواهد شد (سطر 10).

با ماژول‌ها هم در پایتون به صورت شی برخورد می‌شود، پس مفسر پایتون باید بداند کدام نام ماژول به کدام شی مربوط است؛ sys.modules یک دیکشنری حاوی تمام ماژول‌هایی است که در این لحظه از اجرای برنامه بارگذاری شده‌اند. [__sys.modules[__name به عضوی از این دیکشنری که نام آن __name__ است اشاره دارد. می‌دانیم که __name__ بیانگر نام ماژول جاری است؛ بنابراین عبارت [__sys.modules[__name معرف نامی است که به شی ماژول constant.py ارجاع دارد. دستور سطر 14 باعث می‌شود تا ارجاع این نام به ماژول حذف شود و در عوض به شی‌ای از کلاس const_ نسبت داده شود که این عمل باعث حذف شی ماژول از حافظه می‌گردد (چون که دیگر ارجاعی به آن وجود ندارد). از طرفی می‌دانیم که با import هر ماژول، تمام محتویان آن اجرا می گردد؛ با import ماژول constant.py و پس از اجرای کدهای آن به ويژه سطر 14 همانطور که گفته شده ماژول مربوطه حذف می‌شود ولی کدهای آن هنوز در بایت‌کد باقی است. بنابراین پس از import می‌توان به آسانی از نام ماژول که اکنون ارجاع به شی‌ای از کلاس const_ دارد برای ایجاد صفت‌ها که حکم ثابت‌های ما را دارند استفاده کرد. [تمام این مفاهیم در آینده به صورت کامل بررسی خواهد شد]

نکته

[PEP 8]: برای نام‌گذاری ثابت‌ها (Constants) تنها از حروف بزرگ و برای جداسازی کلمه‌ها نیز از ـ استفاده شود. مانند: MAX_OVERFLOW ،TOTAL و...

NoneType

[None] یک شی آماده و بدون مقدار در پایتون است:

>>> n = None

>>> type(n)
<class 'NoneType'>

>>> print(n)
None

>>> import sys
>>> sys.getsizeof(a)
16

>>> n = 5
>>> type(n)
<class 'int'>

این شی را می‌توان معرف مقدار هیچی یا null در پایتون نیز در نظر گرفت. یک کاربرد مهم از این شی در زمانی است که می‌خواهیم یک متغیر بدون مقدار اولیه ایجاد و مقداردهی را بر اساس شرایطی دیگر در ادامه برنامه تعریف نماییم.

این شی کاربردهای فراوانی دارد که به مرور با آن‌ها آشنا خواهید شد.

عملگر‌ها

«عملگر» (Operator) به نمادی گفته می‌شود که عمل مشخصی را بر روی اشیا به انجام می‌رساند؛ به مانند عملگر انتساب = که پیش از این بررسی شد. همچنین به اشیایی که عملگر‌ بر روی آن‌ها عملی را به انجام می‌رساند «عملوند» (Operand) گفته می‌شود. عملگرها دارای انواع مختلفی هستند که در ادامه بررسی خواهیم کرد.

عملگرهای حسابی (Arithmetic Operators)

+ - * ** / // %

  • + جمع (Addition): مقدار عملوندهای دو طرف خود را با یکدیگر جمع می‌کند. 2 + 1 حاصل: 3

  • - تفریق (Subtraction): مقدار عملوند سمت راست را از مقدار عملوند سمت چپ خود منها می‌کند: 4 - 7 حاصل: 3

  • * ضرب (Multiplication): مقدار عملوندهای دو طرف خود را در یکدیگر ضرب می‌کند: 2 * 5 حاصل: 10

  • ** توان (Exponent): مقدار عملوند سمت چپ را به توان مقدار عملوند سمت راست خود می‌رساند. 3 ** 2 حاصل: 8

  • / تقسیم (Division): مقدار عملوند سمت چپ را بر مقدار عملوند سمت راست خود تقسیم می‌کند و خارج قسمت را برمی‌گرداند:

    >>> 7 / 3
    2.3333333333333335
    >>> 12 / 3
    4.0
    >>> 6.0 / 2
    3.0
    
  • // تقسیم گردشده پایین (Floor Division): مقدار عملوند سمت چپ را بر مقدار عملوند سمت راست خود تقسیم می‌کند و خارج قسمت را با حذف مقدار اعشاری (در صورت وجود) برمی‌گرداند. حاصل این عملگر برای اعداد صحیح به صورت یک عدد صحیح محاسبه می‌گردد، به نتایج نمونه کد پایین توجه نمایید:

    >>> 7 // 3
    2
    >>> 12 // 3
    4
    >>> 6.0 // 2
    3.0
    >>> 7.0 // 3
    2.0
    
  • % باقی‌ مانده (Modulus): مقدار عملوند سمت چپ را بر مقدار عملوند سمت راست خود تقسیم می‌کند و باقی‌ مانده را برمی‌گرداند. 3 % 7 حاصل: 1

عملگرهای مقایسه‌ (Comparison Operators)

== =! <> < > =< =>

  • == برابر (Equal): چنانچه مقدار عملوندهای دو طرف برابر باشند، True را برمی‌گرداند. 1 == 3 : False

  • =! نابرابر (Not Equal): چنانچه مقدار عملوندهای دو طرف برابر نباشند، True را برمی‌گرداند. 1 =! 3 : True

  • < بزرگتر از (Greater Than): چنانچه مقدار عملوند سمت چپ بزرگتر از مقدار عملوند سمت راست آن باشد، True را برمی‌گرداند. 5 < 3 : False

  • > کوچکتر از (Less Than): چنانچه مقدار عملوند سمت چپ کوچکتر از مقدار عملوند سمت راست آن باشد، True را برمی‌گرداند. 5 > 3 : True

  • =< برابر یا بزرگتر از (Greater Than or Equal): چنانچه مقدار عملوند سمت چپ برابر یا بزرگتر از مقدار عملوند سمت راست آن باشد، True را برمی‌گرداند. 5 =< 7 : True

  • => برابر یا کوچکتر از (Less Than or Equal): چنانچه مقدار عملوند سمت چپ برابر یا کوچکتر از مقدار عملوند سمت راست آن باشد، True را برمی‌گرداند. 5 => 7 : False

عملگرهای انتساب (Assignment Operators)

  • = عملوند سمت راست را به عملوند سمت چپ خود نسبت می‌دهد. چنانچه یک عبارت محاسباتی در سمت راست باشد، حاصل آن را به عملوند سمت چپ نسبت می‌دهد:

    >>> a = 3
    
    >>> b = 2
    
    >>> c = a + b
    >>> c
    5
    
  • =+ =- =* =** =/ =// =% عملگرهای ترکیبی (انتساب حسابی): این عملگرها ابتدا عمل مربوط به عملگر حسابی را بر روی مقدار عملوندهای دو طرف خود به انجام می‌رسانند و سپس حاصل را به عملوند سمت چپ نسبت می‌دهند:

    >>> a += b
    >>> a
    5
    
    >>> a -= b
    >>> a
    1
    
    >>> a *= b
    >>> a
    6
    
    >>> a **= b
    >>> a
    9
    
    >>> a /= b
    >>> a
    1.5
    

عملگرهای بیتی (Bitwise Operators)

& | ^ ~ >> <<

این دسته از عملگرها، عمل مشخصی را بر روی تک تک بیت‌های عملوند‌(ها) انجام می‌دهند. در پایتون برای استفاده از این عملگرها لزومی به تبدیل اعداد به پایه دو (دودویی یا باینری Binary) وجود ندارد ولی در اینجا برای مشاهده بهتر عملکرد آن‌ها از اعداد دودویی استفاده کرده‌ایم. در زبان پایتون اعداد پایه دو همواره می‌بایست با یک 0b شروع شوند:

>>> a = 0b0011
>>> a
3
>>> b = 0b0010
>>> b
2

از تابع ()bin می‌توان برای به دست آوردن مقدار دودویی یک عدد ده‌دهی استفاده کرد؛ البته توجه داشته باشید که این تابع مقدار دودویی را در قالب متنی (نوع String) بر می‌گرداند - نکته دیگر اینکه در حالت تعاملی پایتون با وارد کردن اعداد دودویی، خروجی ده‌دهی به دست می‌آید:

>>> bin(10)
'0b1010'

>>> type(bin(10))
<class 'str'>

>>> 0b1010
10
  • &: معادل AND بیتی است - تنها بیت‌هایی از خروجی آن 1 خواهند بود که هر دو بیت متناظر از عملوندهای آن 1 باشند:

    >>> a & b                  0000 0011
    2                          0000 0010
                        (AND) -----------
                               0000 0010  = 2
    
  • |: معادل OR بیتی است - تنها بیت‌هایی از خروجی آن 0 خواهند بود که هر دو بیت متناظر از عملوندهای آن 0 باشند:

    >>> a | b                  0000 0011
    3                          0000 0010
                         (OR) -----------
                               0000 0011  = 3
    
  • ^: معادل XOR بیتی است - تنها بیت‌هایی از خروجی آن 1 خواهند بود که هر دو بیت متناظر از عملوندهای آن مخالف یکدیگر باشند:

    >>> a ^ b                  0000 0011
    1                          0000 0010
                        (XOR) -----------
                               0000 0001  = 1
    
  • ~ معادل NOT بیتی و تک عملوندی است - هر یک از بیت‌های عملوند خود را از 0 به 1 و برعکس تبدیل می‌کند:

    >>> ~ a                    0000 0011
    -4                  (NOT) -----------
                               1111 1100
    

    یک شیوه برای نمایش اعداد علامت دار دودویی، همین عمل یعنی برعکس کردن بیت‌ها (0 به 1 و 1 به 0) است که به آن نمایش «مکمل یک» (One's Complement) اعداد دودویی گفته می‌شود. ولی مفسر پایتون به صورت پیش‌فرض اعداد علامت دار را به شیوه رایج دیگری ارزیابی می‌کند که به نام نمایش «مکمل دو» (Two's Complement) شناخته می‌شود؛ در این روش پس از برعکس شدن بیت‌ها، حاصل با عدد 1 جمع می‌شود. بنابراین در نمونه کد بالا حاصل NOT عدد a برابر 11111100 می‌شود که نمایش عدد 4 - در پایه دو به شیوه مکمل دو است:

    n = 3                       0000 0011
    3 = 0000 0011        (NOT) -----------
                                1111 1100  = -3 (in One’s Complement)
                                           = -4 (in Two’s Complement)
    
    Two’s Complement
    
    n = 4                       0000 0100
    4 = 0000 0100        (NOT) -----------
                                1111 1011
                                        1
                         ( + ) -----------
                                1111 1100  = -4
    

    می‌توان مقدار a ~ را برابر حاصل عبارت 1 - a- در نظر گرفت. بنابراین:

    >>> ~ a + 1
    -3
    
  • >> شیفت چپ (Left Shift): بیت‌های عملوند سمت چپ را به مقدار عملوند سمت راست خود به سمت چپ جابه‌جا می‌کند - مکان‌های رد شده با صفر مقداردهی می‌شوند:

    >>> a << 3                  0000 0011
    24                   (LSH) -----------
                                0001 1000  = 24
    
  • << شیفت راست (Right Shift): بیت‌های عملوند سمت چپ را به مقدار عملوند سمت راست خود به سمت راست جابه‌جا می‌کند - مکان‌های رد شده با صفر مقداردهی می‌شوند:

    >>> a = 88                  0101 1000
    >>> a >> 3           (RSH) -----------
    11                          0000 1011  = 11
    
  • =& =| =^ =>> =<< عملگرهای ترکیبی (انتساب بیتی): این عملگرها ابتدا عمل مربوط به عملگر بیتی را بر روی عملوندهای دو طرف خود به انجام می‌رسانند و سپس حاصل را به عملوند سمت چپ نسبت می‌دهند.

عملگرهای منطقی (Logical Operators)

این عملگرها عبارتند از not or and که در دستورات شرطی کاربرد دارند. عملگرهای منطقی عملوند‌های خود را بر اساس ارزش‌‌های True (درست) و False (نادرست) مورد ارزیابی قرار می‌دهند و نتایج خود را بر اساس جدول پایین برمی‌گردانند. عملگر not تک عملوندی است.

a

b

a and b

a or b

not a

not b

True

False

False

True

False

True

False

True

False

True

True

False

False

False

False

False

True

True

True

True

True

True

False

False

عملگر and تنها زمانی که هر دو عملوند آن ارزش True داشته باشند، True را بر می‌گرداند. عملگر or تنها زمانی که هر دو عملوند آن ارزش False داشته باشند، False را برمی‌گرداند. عملگر not نیز ارزش عملود خود را برعکس می‌کند (True به False و False به True).

عملگرهای عضویت (Membership Operators)

شامل دو عملگر in و not in می‌باشد که از آن‌ها برای بررسی وجود یک مقدار در میان اعضای یک دنباله (sequence مانند: رشته، لیست و...) استفاده می‌شود.

  • in: اگر مقدار عملوند سمت چپ خود را در عملوند سمت راست بیابد، True و در غیر این صورت False را بر می‌گرداند.

  • not in: اگر مقدار عملوند سمت چپ خود را در عملوند سمت راست نیابد، True و در غیر این صورت False را بر می‌گرداند.

>>> "py" in "python"
True
>>> 1 in [1, 2, 3]
True
>>> "s" not in "python"
True
>>> 3 not in [1, 2, 3]
False

عملگرهای هویت (Identity Operators)

شامل دو عملگر is و is not است که از آن‌ها برای بررسی یکی بودن دو شی استفاده می‌شود.

  • is: اگر هر دو عملوند به یک شی ارجاع داشته باشند، True و در غیر این صورت False را بر می‌گرداند.

  • is not: اگر هر دو عملوند به یک شی ارجاع نداشته باشند، True و در غیر این صورت False را بر می‌گرداند.

>>> a = 3

>>> a is 3
True

عملگر‌ شیرماهی (Walrus Operator)

از نسخه 3.8 پایتون یک عملگر جدید به نام Assignment Expressions یا Walrus Operator به سینتکس پایتون اضافه شده است. [PEP 572]. نمایش این عملگر به شکل =: (شبیه دو چشم و عاج یک شیرماهی! [تصویر]) می‌باشد و به ما این امکان را می‌دهد که عملیات انتساب و بازگرداندن مقدار را به صورت همزمان به انجام برسانیم. به نمونه کد زیر توجه نمایید:

>>> walrus = False
>>> print(walrus)
False
>>> print(walrus := True)  # Python >= 3.8
True

>>> walrus
True

دو نمونه کد بالا عملکرد یکسانی دارند با این تفاوت که در نمونه دوم تنها با استفاده یک سطر کد، متغیر walrus با انتساب مقدار True ایجاد و سپس به تابع print ارسال می‌گردد.

ترتیب اجرای عملگرها (Operator Precedence)

چنانچه عبارتی شامل چندین عملگر باشد؛ اینکه ابتدا عمل کدامیک بررسی شود، در حاصل نتیجه تاثیر‌گذار خواهد بود. هر کدام از عملگرها از دید مفسر پایتون دارای حق تقدم متفاوتی است (مانند دیگر زبان‌های برنامه‌نویسی). بنابراین همواره هنگام درج یک عبارت، می‌بایست به میزان تقدم آن‌ها نیز توجه کرد. در بخش پایین به بررسی اولویت عملگرها خواهیم پرداخت - اولویت از بالا به پایین کاهش میابد:

 1( )               # پرانتز
 2**
 3~
 4-       +         # منفی و مثبت
 5*       /       //      %
 6-       +         # بعلاوه و منها
 7<<      >>
 8&       ^       |
 9==      !=      <>
10<       <=      >       >=
11=       **=     /=      //=      %=      *=      -=      +=
12is      is not
13in      in not
14not     and     or

پرانتز بالاترین اولویت را دارد به این معنی که هر عبارتی داخل آن قرار بگیرد اولویت بیشتری برای بررسی خواهد داشت؛ در پرانتزهای تودرتو نیز اولویت داخلی‌ترین پرانتز از همه بیشتر است. چنانچه عبارتی شامل چند عملگر هم سطح باشد؛ اولویت عملگر ‌سمت چپ‌تر بیشتر است. به عبارت‌های پایین و حاصل آن‌ها توجه نمایید:

>>> 4 + 2 - 3 + 2 * 5
13
>>> 4 + ((2 - 3) + 2) * 5
9
>>> 9 / 3 * 2
6.0
>>> 3 * 2 / 9
0.6666666666666666
>>> (5 - 3) ** (7 - 3)
16
>>> 4 + 3 ** 2 - 9 / 3 * 3
4.0
>>> 4 * 2 == 5 + 3
True