8. دستورهای مرکب

دستورهای مرکب شامل (گروه‌هایی از) دستورهای دیگر می‌شوند؛ آن‌ها به‌نحوی اجرای آن دستورهای دیگر را تحت تأثیر قرار می‌دهند یا کنترل می‌کنند. به‌طور کلی، دستورهای مرکب چندین خط را در بر می‌گیرند، اگرچه در حالت‌های ساده ممکن است یک دستور مرکب کامل در یک خط جای بگیرد.

دستورهای if، while و for ساختارهای کنترل جریان سنتی را پیاده‌سازی می‌کنند. try هندلرهای استثنا و/یا کد پاک‌سازی را برای گروهی از دستورها تعیین می‌کند، در حالی که دستور with امکان اجرای کد مقداردهی اولیه و نهایی‌سازی را پیرامون یک بلوک کد فراهم می‌کند. تعریف تابع و کلاس نیز از نظر سینتکسی دستورهای مرکب هستند.

یک دستور مرکب از یک یا چند «بند» تشکیل شده است. هر بند از یک سرآیند و یک «بدنه» تشکیل شده است. سرآیندهای بندها در یک دستور مرکب مشخص، همگی در یک سطح تورفتگی قرار دارند. هر سرآیند بند با کلیدواژه‌ای که به‌طور یکتا آن را مشخص می‌کند آغاز می‌شود و با یک دونقطه پایان می‌یابد. یک بدنه، گروهی از دستورها است که توسط یک بند کنترل می‌شود. یک بدنه می‌تواند یک یا چند دستور ساده‌ی جداشده با نقطه‌ویرگول در همان خط سرآیند و پس از دونقطه‌ی سرآیند باشد، یا می‌تواند یک یا چند دستور تورفته در سطرهای بعدی باشد. تنها شکل دوم یک بدنه می‌تواند شامل دستورهای مرکب تودرتو باشد؛ مورد زیر غیرمجاز است، بیشتر به این دلیل که مشخص نخواهد بود یک بند else بعدی به کدام بند if تعلق دارد:

if test1: if test2: print(x)

همچنین توجه داشته باشید که در این زمینه، نقطه‌ویرگول اتصال قوی‌تری نسبت به دونقطه دارد، به‌طوری که در مثال زیر، یا همه‌ی فراخوانی‌های print() اجرا می‌شوند یا هیچ‌کدام:

if x < y < z: print(x); print(y); print(z)

خلاصه‌سازی:

compound_stmt: if_stmt
               | while_stmt
               | for_stmt
               | try_stmt
               | with_stmt
               | match_stmt
               | funcdef
               | classdef
               | async_with_stmt
               | async_for_stmt
               | async_funcdef
suite:         stmt_list NEWLINE | NEWLINE INDENT statement+ DEDENT
statement:     stmt_list NEWLINE | compound_stmt
stmt_list:     simple_stmt (";" simple_stmt)* [";"]

توجه داشته باشید که دستورها همیشه به NEWLINE ختم می‌شوند و ممکن است پس از آن یک DEDENT بیاید. همچنین توجه داشته باشید که بندهای ادامه‌ی اختیاری همیشه با کلیدواژه‌ای آغاز می‌شوند که نمی‌تواند یک دستور را آغاز کند، بنابراین هیچ ابهامی وجود ندارد (مشکل «else معلق» در پایتون با الزام به تورفتگی دستورات if تودرتو حل شده است).

قالب‌بندی قواعد دستور زبان در بخش‌های زیر، برای وضوح هر بند را در یک خط جداگانه قرار می‌دهد.

8.1. دستور if

دستور if برای اجرای شرطی استفاده می‌شود:

if_stmt: "if" assignment_expression ":" suite
         ("elif" assignment_expression ":" suite)*
         ["else" ":" suite]

این دستور دقیقاً یکی از بدنه‌ها (suites) را با ارزیابی عبارت‌ها یکی‌یکی انتخاب می‌کند تا زمانی که یکی از آن‌ها درست یافت شود (برای تعریف درست و نادرست به بخش عملیات بولی مراجعه کنید)؛ سپس همان بدنه (suite) اجرا می‌شود (و هیچ بخش دیگری از دستور if اجرا یا ارزیابی نمی‌شود). اگر همه عبارت‌ها نادرست باشند، بدنه بند else، در صورت وجود، اجرا می‌شود.

8.2. دستور while

دستور while برای اجرای مکرر تا زمانی که یک عبارت درست باشد استفاده می‌شود:

while_stmt: "while" assignment_expression ":" suite
            ["else" ":" suite]

این عبارت را به‌طور مکرر آزمایش می‌کند و اگر درست باشد، بلوک اول را اجرا می‌کند؛ اگر عبارت نادرست باشد (که ممکن است اولین باری باشد که آزمایش می‌شود)، بلوکِ بند else، در صورت وجود، اجرا می‌شود و حلقه پایان می‌یابد.

دستور break که در اولین بدنه اجرا می‌شود، حلقه را بدون اجرای بدنه بند else پایان می‌دهد. دستور continue که در اولین بدنه اجرا می‌شود، از بقیه‌ی بدنه می‌گذرد و به ارزیابی عبارت بازمی‌گردد.

8.3. دستور for

دستور for برای پیمایش روی عناصر یک دنباله (مانند رشته، تاپل یا فهرست) یا یک شیء پیمایش‌پذیر دیگر استفاده می‌شود:

for_stmt: "for" target_list "in" starred_expression_list ":" suite
          ["else" ":" suite]

The starred_expression_list expression is evaluated once; it should yield an iterable object. An iterator is created for that iterable. The first item provided by the iterator is then assigned to the target list using the standard rules for assignments (see دستورهای انتساب), and the suite is executed. This repeats for each item provided by the iterator. When the iterator is exhausted, the suite in the else clause, if present, is executed, and the loop terminates.

دستور break که در اولین بدنه اجرا شود، حلقه را بدون اجرای بدنه بند else خاتمه می‌دهد. دستور continue که در اولین بدنه اجرا شود، از بقیه‌ی بدنه می‌گذرد و با آیتم بعدی ادامه می‌یابد، یا اگر آیتم بعدی وجود نداشته باشد، با بند else ادامه می‌یابد.

حلقه‌ی for انتساب‌هایی را به متغیرهای فهرست هدف انجام می‌دهد. این کار تمام انتساب‌های پیشین به آن متغیرها را بازنویسی می‌کند، از جمله انتساب‌هایی که در بدنه‌ی حلقه‌ی for انجام شده‌اند:

for i in range(10):
    print(i)
    i = 5             # this will not affect the for-loop
                      # because i will be overwritten with the next
                      # index in the range

نام‌های موجود در فهرست هدف پس از پایان حلقه حذف نمی‌شوند، اما اگر دنباله خالی باشد، حلقه اصلاً به آن‌ها انتسابی نخواهد داد. نکته: نوع توکار range() بیانگر دنباله‌های حسابی تغییرناپذیر از اعداد صحیح است. برای نمونه، پیمایش range(3) به‌ترتیب ۰، ۱ و سپس ۲ را برمی‌گرداند.

تغییر یافته در نسخه‌ی 3.11: عناصر ستاره‌دار اکنون در فهرست عبارت مجاز هستند.

8.4. دستور try

دستور try هندلرهای استثنا و/یا کد پاک‌سازی را برای گروهی از دستورها مشخص می‌کند:

try_stmt:  try1_stmt | try2_stmt | try3_stmt
try1_stmt: "try" ":" suite
           ("except" [expression ["as" identifier]] ":" suite)+
           ["else" ":" suite]
           ["finally" ":" suite]
try2_stmt: "try" ":" suite
           ("except" "*" expression ["as" identifier] ":" suite)+
           ["else" ":" suite]
           ["finally" ":" suite]
try3_stmt: "try" ":" suite
           "finally" ":" suite

اطلاعات بیشتر درباره‌ی استثناها را می‌توانید در بخش استثناها پیدا کنید، و اطلاعات درباره‌ی استفاده از دستور raise برای پرتاب استثناها را می‌توانید در بخش پرتاب پیدا کنید.

تغییر یافته در نسخه‌ی 3.14: پشتیبانی از حذف اختیاری پرانتزهای گروه‌بندی هنگام استفاده از چندین نوع استثنا. به PEP 758 مراجعه کنید.

8.4.1. بند except

بند(های) except یک یا چند هندلری استثنا را مشخص می‌کنند. هنگامی که هیچ استثنایی در بند try رخ نمی‌دهد، هیچ هندلری استثنایی اجرا نمی‌شود. هنگامی که استثنایی در بلوک try رخ می‌دهد، جست‌وجو برای یافتن یک هندلری استثنا آغاز می‌شود. این جست‌وجو بندهای except را به‌ترتیب بررسی می‌کند تا بندی یافت شود که با استثنا مطابقت دارد. بند except فاقد عبارت، در صورت وجود، باید آخرین بند باشد؛ این بند با هر استثنایی مطابقت دارد.

برای بند except همراه با یک عبارت، عبارت باید به یک نوع استثنا یا تاپلی از انواع استثنا ارزیابی شود. اگر چند نوع استثنا ارائه‌شده باشد و از بند as استفاده نشده باشد، می‌توان پرانتزها را حذف کرد. استثنای پرتاب‌شده با یک بند except مطابقت دارد که عبارت آن به کلاس شیء استثنا یا یک کلاس پایه غیرمجازی برای آن، یا به تاپلی حاوی چنین کلاسی ارزیابی می‌شود.

اگر هیچ بند except با استثنا مطابقت نداشته باشد، جست‌وجو برای هندلر استثنا در کد پیرامونی و روی پشته فراخوانی ادامه می‌یابد. [1]

اگر ارزیابی یک عبارت در سرآیند یک بند except باعث پرتاب یک استثنا شود، جست‌وجوی اصلی برای یک هندلر لغو می‌شود و جست‌وجویی برای استثنای جدید در کد پیرامونی و روی پشته فراخوانی آغاز می‌شود (با آن به‌گونه‌ای رفتار می‌شود که گویی کل دستور try آن استثنا را پرتاب کرده است).

هنگامی که یک بند except منطبق پیدا شود، در صورت وجود، استثنا به هدفی که پس از کلیدواژه as در آن بند except مشخص شده است انتساب می‌یابد و بدنه‌ی بند except اجرا می‌شود. همه‌ی بندهای except باید یک بلوک قابل‌اجرا داشته باشند. هنگامی که به پایان این بلوک برسیم، اجرا به‌صورت عادی پس از کل دستور try ادامه می‌یابد. (این بدان معناست که اگر دو هندلری تودرتو برای یک استثنا وجود داشته باشند و استثنا در بند try هندلری داخلی رخ دهد، هندلری بیرونی آن استثنا را مدیریت نخواهد کرد.)

هنگامی که یک استثنا با استفاده از as target اختصاص داده شده باشد، در پایان بند except پاک می‌شود. این چنین است که:

except E as N:
    foo

به‌صورت زیر ترجمه شد:

except E as N:
    try:
        foo
    finally:
        del N

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

پیش از اجرای بدنه‌ی بند except، استثنا در ماژول sys ذخیره می‌شود، جایی که می‌توان از درون بدنه‌ی بند except با فراخوانی sys.exception() به آن دسترسی داشت. هنگام خروج از یک هندلر استثنا، استثنا ذخیره‌شده در ماژول sys به مقدار پیشین خود بازنشانی می‌شود:

>>> print(sys.exception())
None
>>> try:
...     raise TypeError
... except:
...     print(repr(sys.exception()))
...     try:
...          raise ValueError
...     except:
...         print(repr(sys.exception()))
...     print(repr(sys.exception()))
...
TypeError()
ValueError()
TypeError()
>>> print(sys.exception())
None

8.4.2. بند except*

بند(های) except* یک یا چند هندلر برای گروه‌های استثنا (نمونه‌های BaseExceptionGroup) مشخص می‌کنند. یک دستور try می‌تواند دارای بندهای except یا except* باشد، اما نه هر دو. نوع استثنا برای تطبیق در مورد except* الزامی است، بنابراین except*: یک خطای نحوی است. این نوع همانند حالت except تفسیر می‌شود، اما تطبیق بر استثناهای موجود در گروهی که مدیریت می‌شود انجام می‌شود. اگر نوع مورد تطبیق، زیرکلاسی از BaseExceptionGroup باشد، یک TypeError پرتاب می‌شود، زیرا این حالت دارای معناشناسی مبهمی خواهد بود.

هنگامی که یک گروه استثنا در بلوک try پرتاب می‌شود، هر بند except* آن را (به split() مراجعه کنید) به زیرگروه‌هایی از استثناهای منطبق و غیرمنطبق تقسیم می‌کند. اگر زیرگروه منطبق خالی نباشد، به استثنای مدیریت‌شده (مقدار بازگشتی از sys.exception()) تبدیل می‌شود و به هدف بند except* (در صورت وجود) انتساب می‌یابد. سپس بدنه بند except* اجرا می‌شود. اگر زیرگروه غیرمنطبق خالی نباشد، به همان شیوه توسط بند except* بعدی پردازش می‌شود. این روند ادامه می‌یابد تا همه استثناهای موجود در گروه منطبق شده باشند، یا آخرین بند except* اجرا شده باشد.

پس از اجرای همه‌ی بندهای except*، گروه استثناهای مدیریت‌نشده با هر استثنایی که از درون بندهای except* پرتاب یا دوباره پرتاب شده است، ادغام می‌شود. این گروه استثنای ادغام‌شده منتشر می‌شود:

>>> try:
...     raise ExceptionGroup("eg",
...         [ValueError(1), TypeError(2), OSError(3), OSError(4)])
... except* TypeError as e:
...     print(f'caught {type(e)} with nested {e.exceptions}')
... except* OSError as e:
...     print(f'caught {type(e)} with nested {e.exceptions}')
...
caught <class 'ExceptionGroup'> with nested (TypeError(2),)
caught <class 'ExceptionGroup'> with nested (OSError(3), OSError(4))
  + Exception Group Traceback (most recent call last):
  |   File "<doctest default[0]>", line 2, in <module>
  |     raise ExceptionGroup("eg",
  |         [ValueError(1), TypeError(2), OSError(3), OSError(4)])
  | ExceptionGroup: eg (1 sub-exception)
  +-+---------------- 1 ----------------
    | ValueError: 1
    +------------------------------------

اگر استثنایی که از بلوک try پرتاب می‌شود، یک گروه استثنا نباشد و نوع آن با یکی از بندهای except* مطابقت داشته باشد، آن استثنا گرفته می‌شود و در یک گروه استثنا با رشته پیام خالی پیچیده می‌شود. این امر تضمین می‌کند که نوع هدف e همواره BaseExceptionGroup باشد:

>>> try:
...     raise BlockingIOError
... except* BlockingIOError as e:
...     print(repr(e))
...
ExceptionGroup('', (BlockingIOError(),))

break، continue و return نمی‌توانند در یک بند except* ظاهر شوند.

8.4.3. بند else

بند اختیاری else زمانی اجرا می‌شود که جریان کنترل از بلوک try خارج شود، هیچ استثنایی پرتاب نشده باشد، و هیچ دستور return، continue یا break اجرا نشده باشد. استثناهای بند else توسط بندهای except پیشین مدیریت نمی‌شوند.

8.4.4. بند finally

اگر finally وجود داشته باشد، یک هندلری «پاک‌سازی» را مشخص می‌کند. بند try اجرا می‌شود، از جمله هر بند except و else. اگر استثنایی در هر یک از بندها رخ دهد و رسیدگی نشود، استثنا به‌طور موقت ذخیره می‌شود. بند finally اجرا می‌شود. اگر استثنای ذخیره‌شده‌ای وجود داشته باشد، در پایان بند finally دوباره پرتاب می‌شود. اگر بند finally استثنای دیگری پرتاب کند، استثنای ذخیره‌شده به‌عنوان زمینه‌ی استثنای جدید تنظیم می‌شود. اگر بند finally دستور return، break یا continue را اجرا کند، استثنای ذخیره‌شده دور انداخته می‌شود. برای مثال، این تابع ۴۲ را برمی‌گرداند.

def f():
    try:
        1/0
    finally:
        return 42

اطلاعات استثنا در حین اجرای بند finally در دسترس برنامه نیست.

هرگاه یک دستور return، break یا continue در بلوک try یک دستور try...finally اجرا شود، بند finally نیز «در مسیر خروج» اجرا می‌شود.

مقدار برگشتی یک تابع بر اساس آخرین دستور return ای که اجرا می‌شود تعیین می‌شود. از آن‌جا که بند finally همیشه اجرا می‌شود، یک دستور return که در بند finally اجرا شود، همیشه آخرین دستوری خواهد بود که اجرا می‌شود. تابع زیر 'finally' را برمی‌گرداند.

def foo():
    try:
        return 'try'
    finally:
        return 'finally'

تغییر یافته در نسخه‌ی 3.8: پیش از Python 3.8، استفاده از دستور continue در بند finally به دلیل مشکلی در پیاده‌سازی غیرمجاز بود.

تغییر یافته در نسخه‌ی 3.14: کامپایلر زمانی که یک return، break یا continue در یک بلوک finally ظاهر شود، یک SyntaxWarning نشان می‌دهد (به PEP 765 مراجعه کنید).

8.5. دستور with

دستور with برای دربرگرفتن اجرای یک بلوک با متدهای تعریف‌شده توسط یک مدیر زمینه استفاده می‌شود (به بخش مدیران زمینه‌ی دستور with مراجعه کنید). این کار امکان کپسوله‌سازی الگوهای رایج استفاده از try...except...finally را برای استفاده مجدد آسان فراهم می‌کند.

with_stmt:          "with" ( "(" with_stmt_contents ","? ")" | with_stmt_contents ) ":" suite
with_stmt_contents: with_item ("," with_item)*
with_item:          expression ["as" target]

اجرای دستور with با یک «آیتم» به صورت زیر انجام می‌شود:

  1. عبارت زمینه (عبارتی که در with_item داده شده است) ارزیابی می‌شود تا یک مدیر زمینه به دست آید.

  2. __enter__() مدیر زمینه برای استفاده‌ی بعدی دریافت و ذخیره می‌شود.

  3. متد __exit__() مدیر زمینه برای استفاده بعدی بارگذاری می‌شود.

  4. متد __enter__() مدیر زمینه فراخوانی می‌شود.

  5. اگر هدفی در دستور with گنجانده شده باشد، مقدار بازگشتی از __enter__() به آن انتساب داده می‌شود.

    توجه

    دستور with تضمین می‌کند که اگر متد __enter__() بدون خطا بازگشت کند، آنگاه __exit__() همیشه فراخوانی خواهد شد. بنابراین، اگر در حین انتساب به فهرست هدف خطایی رخ دهد، با آن همان‌گونه رفتار خواهد شد که با خطایی درون بلوک رفتار می‌شود. مرحله ۷ در زیر را ببینید.

  6. این بدنه اجرا می‌شود.

  7. متد __exit__() مدیر زمینه فراخوانی می‌شود. اگر استثنایی باعث خروج از بلوک شده باشد، نوع، مقدار و ردگیری پشته آن به‌عنوان آرگومان به __exit__() ارسال می‌شوند. در غیر این صورت، سه آرگومان None داده می‌شود.

    اگر اجرای بدنه به دلیل یک استثنا متوقف شد و مقدار بازگشتی از متد __exit__() نادرست بود، استثنا دوباره پرتاب می‌شود. اگر مقدار بازگشتی درست بود، استثنا سرکوب می‌شود و اجرا با دستور پس از دستور with ادامه می‌یابد.

    اگر به هر دلیلی به‌جز یک استثنا از بلوک خارج شده باشد، مقدار بازگشتی از __exit__() نادیده گرفته می‌شود و اجرا در محل عادی برای نوع خروجی که رخ داده است ادامه می‌یابد.

کد زیر:

with EXPRESSION as TARGET:
    SUITE

از نظر معنایی معادل است با:

manager = (EXPRESSION)
enter = manager.__enter__
exit = manager.__exit__
value = enter()
hit_except = False

try:
    TARGET = value
    SUITE
except:
    hit_except = True
    if not exit(*sys.exc_info()):
        raise
finally:
    if not hit_except:
        exit(None, None, None)

با این تفاوت که از جست‌وجوی متد ویژه به‌صورت ضمنی برای __enter__() و __exit__() استفاده می‌شود.

با بیش از یک آیتم، مدیران زمینه به گونه‌ای پردازش می‌شوند که گویی چندین دستور with تودرتو شده‌اند:

with A() as a, B() as b:
    SUITE

از نظر معنایی معادل است با:

with A() as a:
    with B() as b:
        SUITE

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

with (
    A() as a,
    B() as b,
):
    SUITE

تغییر یافته در نسخه‌ی 3.1: پشتیبانی از چند عبارت زمینه.

تغییر یافته در نسخه‌ی 3.10: پشتیبانی از استفاده از پرانتزهای گروه‌بندی برای شکستن دستور در چند خط.

همچنین ملاحظه نمائید

PEP 343 - دستور «with»

مشخصات، پیش‌زمینه و نمونه‌هایی برای دستور with پایتون.

8.6. دستور match

اضافه شده در نسخه‌ی 3.10.

دستور match برای تطبیق الگو استفاده می‌شود. سینتکس:

match_stmt:   'match' subject_expr ":" NEWLINE INDENT case_block+ DEDENT
subject_expr: flexible_expression "," [flexible_expression_list [',']]
              | assignment_expression
case_block:   'case' patterns [guard] ":" suite

توجه

این بخش از علامت‌های نقل‌قول تکی برای نشان دادن کلیدواژه‌های نرم استفاده می‌کند.

تطبیق الگو یک الگو را به‌عنوان ورودی می‌گیرد (پس از case) و یک مقدار موضوع را (پس از match). الگو (که ممکن است شامل زیرالگوها باشد) با مقدار موضوع تطبیق داده می‌شود. نتایج عبارت‌اند از:

  • موفقیت یا شکست در تطابق (که به آن موفقیت یا شکست الگو نیز گفته می‌شود).

  • امکان انتساب مقادیر تطبیق‌یافته به یک نام. پیش‌نیازهای این موضوع در ادامه بیشتر بحث شده است.

کلیدواژه‌های match و case کلیدواژه‌های نرم هستند.

همچنین ملاحظه نمائید

  • PEP 634 -- تطبیق الگوی ساختاری: مشخصات

  • PEP 636 -- تطبیق الگوی ساختاری: آموزش

8.6.1. نمای کلی

در اینجا نمای کلی از جریان منطقی یک دستور match آمده است:

  1. عبارت موضوعی subject_expr ارزیابی می‌شود و مقدار موضوعی حاصل به دست می‌آید. اگر عبارت موضوعی شامل کاما باشد، یک تاپل با استفاده از قوانین استاندارد ساخته می‌شود.

  2. هر الگو در یک case_block برای تطابق با مقدار موضوع آزمایش می‌شود. قواعد خاص موفقیت یا شکست در زیر توضیح داده شده است. تلاش تطابق همچنین می‌تواند برخی یا همه‌ی نام‌های مستقل درون الگو را مقید کند. قواعد دقیق مقیدسازی الگو بر حسب نوع الگو متفاوت است و در زیر مشخص شده است. مقیدسازی‌های نام ایجادشده در طی یک تطابق موفق الگو، پس از اجرای بلوک باقی می‌مانند و می‌توانند پس از دستور match استفاده شوند.

    توجه

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

  3. اگر الگو با موفقیت تطابق یابد، نگهبان (guard) متناظر (در صورت وجود) ارزیابی می‌شود. در این حالت تضمین می‌شود که همه‌ی پیوندهای نام (name bindings) انجام شده‌اند.

    • اگر نگهبان (guard) به‌صورت درست ارزیابی شود یا وجود نداشته باشد، block داخل case_block اجرا می‌شود.

    • در غیر این صورت، case_block بعدی همان‌طور که در بالا توضیح داده شد، آزموده می‌شود.

    • اگر بلوک‌های case دیگری وجود نداشته باشند، دستور match کامل می‌شود.

توجه

کاربران به‌طور کلی هرگز نباید به ارزیابی شدن یک الگو تکیه کنند. بسته به پیاده‌سازی، مفسر ممکن است مقادیر را در نهانگاه ذخیره کند یا از بهینه‌سازی‌های دیگری استفاده کند که از ارزیابی‌های مکرر صرف‌نظر می‌کنند.

نمونه‌ای از دستور match:

>>> flag = False
>>> match (100, 200):
...    case (100, 300):  # Mismatch: 200 != 300
...        print('Case 1')
...    case (100, 200) if flag:  # Successful match, but guard fails
...        print('Case 2')
...    case (100, y):  # Matches and binds y to 200
...        print(f'Case 3, y: {y}')
...    case _:  # Pattern not attempted
...        print('Case 4, I match anything!')
...
Case 3, y: 200

در این حالت، if flag یک نگهبان (guard) است. در بخش بعدی بیشتر درباره‌ی آن بخوانید.

8.6.2. نگهبان‌ها (Guards)

guard: "if" assignment_expression

یک guard (که بخشی از case است) باید برقرار باشد تا کد داخل بلوک case اجرا شود. شکل آن به این صورت است: if و به دنبال آن یک عبارت.

جریان منطقی یک بلوک case دارای guard به این صورت است:

  1. بررسی کنید که الگوی موجود در بلوک case موفق بوده است. اگر الگو ناموفق بود، guard ارزیابی نمی‌شود و بلوک case بعدی بررسی می‌شود.

  2. اگر الگو موفقیت‌آمیز بود، guard ارزیابی می‌شود.

    • اگر حاصل ارزیابی شرط guard درست باشد، بلوک case انتخاب می‌شود.

    • اگر حاصل ارزیابی شرط guard نادرست باشد، بلوک case انتخاب نمی‌شود.

    • اگر guard در حین ارزیابی استثنایی را پرتاب کند، آن استثنا به بالا منتشر می‌شود.

نگهبان‌ها (guards) مجازند اثرات جانبی داشته باشند، زیرا عبارت هستند. ارزیابی نگهبان باید از اولین تا آخرین بلوک case، یکی‌یکی پیش برود و بلوک‌های case را که همه‌ی الگوهای آن‌ها موفق نمی‌شوند، رد کند. (یعنی ارزیابی نگهبان باید به‌ترتیب انجام شود.) ارزیابی نگهبان باید پس از انتخاب یک بلوک case متوقف شود.

8.6.3. بلوک‌های case غیرقابل‌انکار

یک بلوک case غیرقابل‌انکار (irrefutable case block)، یک بلوک case تطبیق همه (match-all case block) است. یک دستور match می‌تواند حداکثر یک بلوک case غیرقابل‌انکار داشته باشد، و آن باید آخرین باشد.

یک بلوک case در صورتی غیرقابل‌رد در نظر گرفته می‌شود که هیچ شرط محافظی (guard) نداشته باشد و الگوی آن غیرقابل‌رد باشد. یک الگو در صورتی غیرقابل‌رد در نظر گرفته می‌شود که بتوان تنها از روی سینتکس آن اثبات کرد که همیشه موفق می‌شود. تنها الگوهای زیر غیرقابل‌رد هستند:

8.6.4. الگوها

توجه

این بخش از نمادگذاری‌های گرامری فراتر از EBNF استاندارد استفاده می‌کند:

  • نماد SEP.RULE+ شکل کوتاهی از RULE (SEP RULE)* است

  • نماد !RULE خلاصه‌ای برای ادعای پیش‌نگر منفی (negative lookahead assertion) است

سینتکس سطح بالا برای patterns عبارت است از:

patterns:       open_sequence_pattern | pattern
pattern:        as_pattern | or_pattern
closed_pattern: | literal_pattern
                | capture_pattern
                | wildcard_pattern
                | value_pattern
                | group_pattern
                | sequence_pattern
                | mapping_pattern
                | class_pattern

توصیف‌های زیر برای مقاصد توضیحی، شامل توضیحی «به زبان ساده» درباره‌ی عملکرد یک الگو خواهند بود (با قدردانی از Raymond Hettinger برای سندی که الهام‌بخش بیشتر این توصیف‌ها بود). توجه داشته باشید که این توصیف‌ها صرفاً جنبه‌ی توضیحی دارند و ممکن است پیاده‌سازی زیربنایی را بازتاب ندهند. علاوه بر این، آن‌ها همه‌ی صورت‌های معتبر را پوشش نمی‌دهند.

8.6.4.1. الگوهای OR

الگوی OR شامل دو یا چند الگو است که با سطرهای عمودی | از هم جدا شده‌اند. سینتکس:

or_pattern: "|".closed_pattern+

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

یک الگوی OR هر یک از زیرالگوهای خود را به‌نوبت با مقدار موضوع تطبیق می‌دهد، تا یکی موفق شود. در این صورت، الگوی OR موفق در نظر گرفته می‌شود. در غیر این صورت، اگر هیچ‌یک از زیرالگوها موفق نشوند، الگوی OR ناموفق می‌شود.

به بیان ساده، P1 | P2 | ... تلاش می‌کند P1 را تطبیق دهد، اگر ناموفق باشد، تلاش می‌کند P2 را تطبیق دهد، و در صورت موفقیت هر یک، بلافاصله موفق می‌شود، در غیر این صورت ناموفق می‌شود.

8.6.4.2. الگوهای AS

یک الگوی AS، الگوی OR در سمت چپ کلیدواژه as را با یک موضوع تطبیق می‌دهد. سینتکس:

as_pattern: or_pattern "as" capture_pattern

اگر الگوی OR شکست بخورد، الگوی AS نیز شکست می‌خورد. در غیر این صورت، الگوی AS موضوع را به نام سمت راست کلیدواژه as مقید می‌کند و موفق می‌شود. capture_pattern نمی‌تواند _ باشد.

به بیان ساده، P as NAME با P تطابق خواهد داشت و در صورت موفقیت، NAME = <subject> را تنظیم خواهد کرد.

8.6.4.3. الگوهای لفظی

یک الگوی لفظی (literal pattern) با بیشتر مقادیر لفظی در پایتون مطابقت دارد. سینتکس:

literal_pattern: signed_number
                 | signed_number "+" NUMBER
                 | signed_number "-" NUMBER
                 | strings
                 | "None"
                 | "True"
                 | "False"
signed_number:   ["-"] NUMBER

قاعده‌ی strings و توکن NUMBER در دستور زبان استاندارد پایتون تعریف شده‌اند. رشته‌های سه‌نقل‌قولی پشتیبانی می‌شوند. رشته‌های خام و رشته‌های بایتی پشتیبانی می‌شوند. اف‌استرینگ‌ها و تی‌استرینگ‌ها (t-strings) پشتیبانی نمی‌شوند.

قالب‌های signed_number '+' NUMBER و signed_number '-' NUMBER برای بیان اعداد مختلط هستند؛ سمت چپ باید یک عدد حقیقی و سمت راست باید یک عدد موهومی باشد. مثلاً 3 + 4j.

به بیان ساده، LITERAL تنها در صورتی موفق می‌شود که <subject> == LITERAL باشد. برای مقادیر تک‌نمونه None، True و False، از عملگر is استفاده می‌شود.

8.6.4.4. الگوهای ثبت‌کننده

یک الگوی ثبت‌کننده (capture pattern)، مقدار موضوع را به یک نام مقید می‌کند. سینتکس:

capture_pattern: !'_' NAME

یک زیرخط تکی _ یک الگوی ثبت‌کننده نیست (این همان چیزی است که !'_' بیان می‌کند). در عوض، به‌عنوان یک wildcard_pattern در نظر گرفته می‌شود.

در یک الگوی مشخص، یک نام مشخص فقط می‌تواند یک بار مقید شود. برای مثال case x, x: ... نامعتبر است، در حالی که case [x] | x: ... مجاز است.

الگوهای ثبت‌کننده (capture patterns) همیشه موفق می‌شوند. این پیوند از قواعد محدوده تعیین‌شده توسط عملگر عبارت انتساب در PEP 572 پیروی می‌کند؛ نام به یک متغیر محلی در نزدیک‌ترین محدوده تابع دربرگیرنده تبدیل می‌شود، مگر اینکه یک دستور global یا nonlocal قابل‌اعمال وجود داشته باشد.

به بیان ساده، NAME همیشه موفق خواهد بود و باعث می‌شود NAME = <subject> تنظیم شود.

8.6.4.5. الگوهای وایلدکارد

یک الگوی وایلدکارد (wildcard pattern) همیشه موفق است (با هر چیزی تطابق دارد) و هیچ نامی را مقید نمی‌کند. سینتکس:

wildcard_pattern: '_'

_ در هر الگویی یک کلیدواژه نرم است، اما فقط در داخل الگوها. این، مطابق معمول، یک شناسه است، حتی در عبارت‌های موضوع match، guardها و بلوک‌های case.

به بیان ساده، _ همیشه موفق خواهد بود.

8.6.4.6. الگوهای مقدار

الگوی مقدار، یک مقدار نام‌دار را در پایتون نشان می‌دهد. سینتکس:

value_pattern: attr
attr:          name_or_attr "." NAME
name_or_attr:  attr | NAME

نام نقطه‌دار در الگو با استفاده از قواعد حل نام استاندارد پایتون جستجو می‌شود. الگو زمانی موفق است که مقدار یافت‌شده در مقایسه با مقدار موضوع برابر باشد (با استفاده از عملگر برابری ==).

به بیان ساده، NAME1.NAME2 تنها در صورتی موفق می‌شود که <subject> == NAME1.NAME2

توجه

اگر یک مقدار یکسان چندین بار در یک دستور match یکسان رخ دهد، مفسر ممکن است اولین مقدار یافت‌شده را در نهانگاه ذخیره کند و به جای تکرار همان جست‌وجو، دوباره از آن استفاده کند. این نهانگاه کاملاً به یک اجرای معین از یک دستور match معین وابسته است.

8.6.4.7. الگوهای گروهی

الگوی گروهی به کاربران اجازه می‌دهد تا برای تأکید بر گروه‌بندی موردنظر، پرانتزها را اطراف الگوها اضافه کنند. در غیر این صورت، سینتکس اضافی ندارد. سینتکس:

group_pattern: "(" pattern ")"

به بیان ساده، (P) همان اثر P را دارد.

8.6.4.8. الگوهای دنباله‌ای

یک الگوی دنباله‌ای شامل چندین الگوی فرعی برای تطبیق با عناصر دنباله است. سینتکس آن مشابه واگشایی یک فهرست یا تاپل است.

sequence_pattern:       "[" [maybe_sequence_pattern] "]"
                        | "(" [open_sequence_pattern] ")"
open_sequence_pattern:  maybe_star_pattern "," [maybe_sequence_pattern]
maybe_sequence_pattern: ",".maybe_star_pattern+ ","?
maybe_star_pattern:     star_pattern | pattern
star_pattern:           "*" (capture_pattern | wildcard_pattern)

استفاده از پرانتز یا کروشه برای الگوهای دنباله‌ای هیچ تفاوتی ندارد (یعنی (...) در مقابل [...]).

توجه

یک الگوی واحد که داخل پرانتز قرار گرفته باشد و ویرگول انتهایی نداشته باشد (برای مثال (3 | 4)) یک الگوی گروه است. در حالی که یک الگوی واحد که داخل کروشه قرار گرفته باشد (برای مثال [3 | 4]) همچنان یک الگوی دنباله است.

حداکثر یک زیرالگوی ستاره‌ای می‌تواند در یک الگوی دنباله وجود داشته باشد. زیرالگوی ستاره‌ای می‌تواند در هر جایگاهی رخ دهد. اگر هیچ زیرالگوی ستاره‌ای وجود نداشته باشد، الگوی دنباله یک الگوی دنباله با طول ثابت است؛ در غیر این صورت یک الگوی دنباله با طول متغیر است.

جریان منطقی برای تطبیق یک الگوی دنباله‌ای با یک مقدار موضوع به شرح زیر است:

  1. اگر مقدار موضوع یک دنباله نباشد [2]، الگوی دنباله شکست می‌خورد.

  2. اگر مقدار موضوع، نمونه‌ای از str، bytes یا bytearray باشد، الگوی دنباله شکست می‌خورد.

  3. مراحل بعدی به این بستگی دارد که الگوی دنباله با طول ثابت باشد یا با طول متغیر.

    اگر الگوی دنباله طول ثابت داشته باشد:

    1. اگر طول دنباله‌ی موضوع با تعداد الگوهای فرعی برابر نباشد، الگوی دنباله‌ای شکست می‌خورد

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

    در غیر این صورت، اگر الگوی دنباله با طول متغیر باشد:

    1. اگر طول دنباله‌ی موضوع کمتر از تعداد زیرالگوهای غیرستاره‌ای باشد، الگوی دنباله‌ای شکست می‌خورد.

    2. زیرالگوهای غیرستاره‌ای ابتدایی، همان‌طور که برای دنباله‌های با طول ثابت انجام می‌شود، با آیتم‌های متناظر خود تطبیق داده می‌شوند.

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

    4. زیرالگوهای غیرستاره‌ای باقی‌مانده، مانند یک دنباله با طول ثابت، با آیتم‌های متناظر موضوع مطابقت داده می‌شوند.

    توجه

    طول دنباله‌ی موضوع از طریق len() (یعنی از طریق پروتکل __len__()) به دست می‌آید. ممکن است مفسر این طول را به شیوه‌ای مشابه الگوهای مقدار در نهانگاه ذخیره کند.

به زبان ساده [P1, P2, P3, ... , P<N>] تنها در صورتی مطابقت می‌کند که همه موارد زیر رخ دهد:

  • بررسی کنید که <subject> یک دنباله است

  • len(subject) == <N>

  • P1 با <subject>[0] مطابقت دارد (توجه کنید که این تطابق همچنین می‌تواند نام‌ها را مقید کند)

  • P2 با <subject>[1] مطابقت دارد (توجه داشته باشید که این تطبیق می‌تواند نام‌ها را نیز مقید کند)

  • ... و به همین ترتیب برای الگو/المان متناظر.

8.6.4.9. الگوهای نگاشت

یک الگوی نگاشت شامل یک یا چند الگوی کلید-مقدار است. سینتکس آن شبیه به ساخت یک دیکشنری است. سینتکس:

mapping_pattern:     "{" [items_pattern] "}"
items_pattern:       ",".key_value_pattern+ ","?
key_value_pattern:   (literal_pattern | value_pattern) ":" pattern
                     | double_star_pattern
double_star_pattern: "**" capture_pattern

حداکثر یک الگوی دو ستاره می‌تواند در یک الگوی نگاشت وجود داشته باشد. الگوی دو ستاره باید آخرین زیرالگو در الگوی نگاشت باشد.

کلیدهای تکراری در الگوهای نگاشت مجاز نیستند. کلیدهای لفظی تکراری باعث پرتاب یک SyntaxError می‌شوند. دو کلیدی که در غیر این صورت مقدار یکسانی دارند، در ران‌تایم یک ValueError پرتاب می‌کنند.

جریان منطقی برای تطبیق یک الگوی نگاشت (mapping pattern) با یک مقدار موضوع (subject value) به‌صورت زیر است:

  1. اگر مقدار موضوع یک نگاشت [3] نباشد، الگوی نگاشت شکست می‌خورد.

  2. اگر هر کلید داده‌شده در الگوی نگاشت در نگاشت موضوع وجود داشته باشد و الگوی مربوط به هر کلید با آیتم متناظرِ نگاشت موضوع مطابقت داشته باشد، الگوی نگاشت موفق می‌شود.

  3. اگر کلیدهای تکراری در الگوی نگاشت شناسایی شوند، آن الگو نامعتبر در نظر گرفته می‌شود. برای مقادیر لفظی تکراری، استثنای SyntaxError پرتاب می‌شود؛ یا برای کلیدهای نام‌دار با مقدار یکسان، استثنای ValueError پرتاب می‌شود.

توجه

جفت‌های کلید-مقدار با استفاده از شکل دو آرگومانی متد get() موضوع‌ی نگاشت تطبیق داده می‌شوند. جفت‌های کلید-مقدار تطبیق‌داده‌شده باید از قبل در نگاشت موجود باشند و به‌صورت آنی از طریق __missing__() یا __getitem__() ایجاد نشده باشند.

به زبان ساده {KEY1: P1, KEY2: P2, ... } فقط در صورتی مطابقت دارد که همه‌ی موارد زیر رخ دهد:

  • بررسی می‌کند که <subject> یک نگاشت است

  • KEY1 in <subject>

  • P1 با <subject>[KEY1] مطابقت دارد

  • ... و به همین ترتیب برای جفت KEY/الگوی متناظر.

8.6.4.10. الگوهای کلاس

الگوی کلاس، یک کلاس و آرگومان‌های جایگاهی و کلیدواژه‌ای آن (در صورت وجود) را نشان می‌دهد. سینتکس:

class_pattern:       name_or_attr "(" [pattern_arguments ","?] ")"
pattern_arguments:   positional_patterns ["," keyword_patterns]
                     | keyword_patterns
positional_patterns: ",".pattern+
keyword_patterns:    ",".keyword_pattern+
keyword_pattern:     NAME "=" pattern

کلیدواژه‌ی یکسان نباید در الگوهای کلاس تکرار شود.

روند منطقی تطبیق یک الگوی کلاس با یک مقدار موضوع به شرح زیر است:

  1. اگر name_or_attr نمونه‌ای از type توکار نباشد، TypeError پرتاب می‌شود.

  2. اگر مقدار موضوع، نمونه‌ای از name_or_attr نباشد (که از طریق isinstance() آزمایش می‌شود)، الگوی کلاس شکست می‌خورد.

  3. اگر هیچ آرگومان الگویی وجود نداشته باشد، الگو موفق می‌شود. در غیر این صورت، مراحل بعدی به این بستگی دارد که الگوهای آرگومان کلیدواژه‌ای یا جایگاهی وجود داشته باشند.

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

    اگر فقط الگوهای کلیدواژه‌ای وجود داشته باشند، آن‌ها به‌صورت زیر، یکی‌یکی پردازش می‌شوند:

    1. کلیدواژه به‌عنوان یک ویژگی روی موضوع جست‌وجو می‌شود.

      • اگر این عمل باعث پرتاب استثنایی به‌جز AttributeError شود، استثنا به بالا منتشر می‌شود.

      • اگر این AttributeError را پرتاب کند، الگوی کلاس شکست خورده است.

      • در غیر این صورت، زیرالگوی مرتبط با الگوی کلیدواژه‌ای با مقدار ویژگی موضوع تطبیق داده می‌شود. اگر این تطبیق شکست بخورد، الگوی کلاس شکست می‌خورد؛ اگر موفق باشد، تطبیق به کلیدواژه بعدی ادامه می‌یابد.

    2. اگر همه‌ی الگوهای کلیدواژه‌ای موفق شوند، الگوی کلاس موفق می‌شود.

    اگر الگوهای جایگاهی وجود داشته باشند، آن‌ها پیش از تطبیق، با استفاده از ویژگی __match_args__ در کلاس name_or_attr به الگوهای کلیدواژه‌ای تبدیل می‌شوند:

    1. معادل getattr(cls, "__match_args__", ()) فراخوانی می‌شود.

      • اگر این کار استثناای پرتاب کند، استثنا به بالا منتشر می‌شود.

      • اگر مقدار بازگشتی یک تاپل نباشد، تبدیل شکست می‌خورد و TypeError پرتاب می‌شود.

      • اگر الگوهای جایگاهی بیشتری نسبت به len(cls.__match_args__) وجود داشته باشد، استثنای TypeError پرتاب می‌شود.

      • در غیر این صورت، الگوی جایگاهی i با استفاده از __match_args__[i] به‌عنوان کلیدواژه، به یک الگوی کلیدواژه‌ای تبدیل می‌شود. __match_args__[i] باید یک رشته باشد؛ در غیر این صورت TypeError پرتاب می‌شود.

      • در صورت وجود کلیدواژه‌های تکراری، TypeError پرتاب می‌شود.

    2. پس از اینکه همه‌ی الگوهای جایگاهی به الگوهای کلیدواژه‌ای تبدیل شدند، تطبیق به‌گونه‌ای ادامه می‌یابد که گویی فقط الگوهای کلیدواژه‌ای وجود دارند.

    برای انواع توکار زیر، نحوه‌ی مدیریت زیرالگوهای جایگاهی متفاوت است:

    این کلاس‌ها یک آرگومان جایگاهی می‌پذیرند و الگوی موجود در آن با کل شیء تطبیق داده می‌شود، نه با یک ویژگی. برای مثال int(0|1) با مقدار 0 مطابقت دارد، اما با مقدار 0.0 مطابقت ندارد.

به بیان ساده CLS(P1, attr=P2) تنها در صورتی تطابق دارد که مورد زیر رخ دهد:

  • isinstance(<subject>, CLS)

  • تبدیل P1 به یک الگوی کلیدواژه‌ای با استفاده از CLS.__match_args__

  • برای هر آرگومان کلیدواژه‌ای attr=P2:

    • hasattr(<subject>, "attr")

    • P2 با <subject>.attr مطابقت دارد

  • ... و به همین ترتیب برای جفت آرگومان کلیدواژه‌ای/الگوی متناظر.

همچنین ملاحظه نمائید

  • PEP 634 -- تطبیق الگوی ساختاری: مشخصات

  • PEP 636 -- تطبیق الگوی ساختاری: آموزش

8.7. تعاریف تابع

تعریف تابع، یک شیء تابع تعریف‌شده توسط کاربر را تعریف می‌کند (بخش سلسله‌مراتب انواع استاندارد را ببینید):

funcdef:                   [decorators] "def" funcname [type_params] "(" [parameter_list] ")"
                           ["->" expression] ":" suite
decorators:                decorator+
decorator:                 "@" assignment_expression NEWLINE
parameter_list:            defparameter ("," defparameter)* "," "/" ["," [parameter_list_no_posonly]]
                             | parameter_list_no_posonly
parameter_list_no_posonly: defparameter ("," defparameter)* ["," [parameter_list_starargs]]
                           | parameter_list_starargs
parameter_list_starargs:   "*" star_parameter ("," defparameter)* ["," [parameter_star_kwargs]]
                           | "*" ("," defparameter)+ ["," [parameter_star_kwargs]]
                           | parameter_star_kwargs
parameter_star_kwargs:     "**" parameter [","]
parameter:                 identifier [":" expression]
star_parameter:            identifier [":" ["*"] expression]
defparameter:              parameter ["=" expression]
funcname:                  identifier

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

تعریف تابع، بدنه‌ی تابع را اجرا نمی‌کند؛ این کار تنها زمانی انجام می‌شود که تابع فراخوانی شود. [4]

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

@f1(arg)
@f2
def func(): pass

تقریباً معادل است با

def func(): pass
func = f1(arg)(f2(func))

با این تفاوت که تابع اصلی به‌طور موقت به نام func مقید نمی‌شود.

تغییر یافته در نسخه‌ی 3.9: توابع را می‌توان با هر assignment_expression معتبری آراسته کرد. پیش‌تر، گرامر بسیار محدودتر بود؛ برای جزئیات PEP 614 را ببینید.

می‌توان فهرستی از پارامترهای نوع را داخل کروشه، بین نام تابع و پرانتز بازِ فهرست پارامترهای آن قرار داد. این موضوع به بررسی‌کننده‌های نوع ایستا نشان می‌دهد که تابع عام است. در ران‌تایم، می‌توان پارامترهای نوع را از ویژگی __type_params__ تابع بازیابی کرد. برای اطلاعات بیشتر توابع عام را ببینید.

تغییر یافته در نسخه‌ی 3.12: فهرست‌های پارامترهای نوع در پایتون 3.12 جدید هستند.

هنگامی که یک یا چند پارامتر به شکل پارامتر = عبارت باشند، گفته می‌شود که تابع «مقادیر پیش‌فرض پارامترها» دارد. برای پارامتری که مقدار پیش‌فرض دارد، می‌توان آرگومان متناظر را در یک فراخوانی حذف کرد، که در این صورت مقدار پیش‌فرض پارامتر جایگزین می‌شود. اگر پارامتری مقدار پیش‌فرض داشته باشد، تمام پارامترهای بعدی تا «*» نیز باید مقدار پیش‌فرض داشته باشند — این یک محدودیت نحوی است که در گرامر بیان نشده است.

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

def whats_on_the_telly(penguin=None):
    if penguin is None:
        penguin = []
    penguin.append("property of the zoo")
    return penguin

معناشناسی فراخوانی تابع با جزئیات بیشتر در بخش فراخوانی‌ها توضیح داده شده است. یک فراخوانی تابع همیشه مقادیری را به تمام پارامترهای ذکرشده در فهرست پارامترها اختصاص می‌دهد؛ خواه از آرگومان‌های جایگاهی، خواه از آرگومان‌های کلیدواژه‌ای، خواه از مقادیر پیش‌فرض. اگر قالب «*identifier» وجود داشته باشد، این پارامتر به یک تاپل مقداردهی اولیه می‌شود که هر پارامتر جایگاهی اضافی را دریافت می‌کند و مقدار پیش‌فرض آن یک تاپل خالی است. اگر قالب «**identifier» وجود داشته باشد، این پارامتر به یک نگاشت ترتیب‌دار (ordered mapping) جدید مقداردهی اولیه می‌شود که هر آرگومان کلیدواژه‌ای اضافی را دریافت می‌کند و مقدار پیش‌فرض آن یک نگاشت خالی جدید از همان نوع است. پارامترهای بعد از «*» یا «*identifier» پارامترهای فقط کلیدواژه‌ای هستند و فقط می‌توان آن‌ها را با آرگومان‌های کلیدواژه‌ای ارسال کرد. پارامترهای قبل از «/» پارامترهای فقط جایگاهی هستند و فقط می‌توان آن‌ها را با آرگومان‌های جایگاهی ارسال کرد.

تغییر یافته در نسخه‌ی 3.8: می‌توان از سینتکس / در پارامترهای تابع برای نشان دادن پارامترهای فقط جایگاهی استفاده کرد. برای جزئیات، PEP 570 را ببینید.

پارامترها ممکن است یک حاشیه‌نویسی به‌شکل «: expression» پس از نام پارامتر داشته باشند. هر پارامتر ممکن است حاشیه‌نویسی داشته باشد، حتی آن‌هایی که به‌شکل *identifier یا **identifier هستند. (به‌عنوان یک حالت خاص، پارامترهای به‌شکل *identifier ممکن است حاشیه‌نویسی «: *expression» داشته باشند.) توابع ممکن است حاشیه‌نویسی «return» به‌شکل «-> expression» پس از فهرست پارامترها داشته باشند. این حاشیه‌نویسی‌ها می‌توانند هر عبارت معتبر پایتون باشند. وجود حاشیه‌نویسی‌ها معنای تابع را تغییر نمی‌دهد. برای اطلاعات بیشتر در مورد حاشیه‌نویسی‌ها، حاشیه‌نویسی‌ها را ببینید.

تغییر یافته در نسخه‌ی 3.11: پارامترهایی به شکل "*identifier" می‌توانند یک حاشیه‌نویسی به شکل ": *expression" داشته باشند. به PEP 646 مراجعه کنید.

همچنین امکان ایجاد توابع ناشناس (توابعی که به یک نام مقید نیستند) برای استفاده فوری در عبارت‌ها وجود دارد. این کار با عبارات لامبدا انجام می‌شود که در بخش لامبدا توضیح داده شده‌اند. توجه داشته باشید که عبارت لامبدا صرفاً میان‌بری برای تعریف ساده‌شده تابع است؛ می‌توان تابعی را که در یک دستور «def» تعریف شده است، دقیقاً مانند تابعی که با یک عبارت لامبدا تعریف شده است، منتقل کرد یا به نام دیگری انتساب داد. قالب «def» در واقع قدرتمندتر است، زیرا امکان اجرای چندین دستور و حاشیه‌نویسی‌ها را فراهم می‌کند.

یادداشت برنامه‌نویس: توابع، اشیای درجه یک هستند. یک دستور "def" که درون تعریف یک تابع اجرا شود، یک تابع محلی تعریف می‌کند که می‌تواند برگردانده شود یا به‌عنوان مقدار منتقل شود. متغیرهای آزاد استفاده‌شده در تابع تودرتو می‌توانند به متغیرهای محلی تابع حاوی def دسترسی داشته باشند. برای جزئیات، بخش نام‌گذاری و مقیدسازی را ببینید.

همچنین ملاحظه نمائید

PEP 3107 - حاشیه‌نویسی‌های تابع

مشخصات اولیه برای حاشیه‌نویسی‌های تابع .

PEP 484 - راهنمایی‌های نوع

تعریف یک معنای استاندارد برای حاشیه‌نویسی‌ها : راهنمایی‌های نوع .

PEP 526 - سینتکس حاشیه‌نویسی متغیرها

توانایی افزودن اشاره‌ی نوع به اعلان‌های متغیر، شامل متغیرهای کلاس و متغیرهای نمونه.

PEP 563 - ارزیابی به‌تعویق‌افتاده‌ی حاشیه‌نویسی‌ها

پشتیبانی از ارجاع‌های پیشرو درون حاشیه‌نویسی‌ها با نگه‌داری حاشیه‌نویسی‌ها به‌صورت رشته‌ای در ران‌تایم به‌جای ارزیابی فوری.

PEP 318 - دکوراتورها برای توابع و متدها

دکوراتورهای تابع و متد معرفی شدند. دکوراتورهای کلاس در PEP 3129 معرفی شدند.

8.8. تعاریف کلاس

تعریف کلاس، یک شیء کلاس را تعریف می‌کند (به بخش سلسله‌مراتب انواع استاندارد مراجعه کنید):

classdef:    [decorators] "class" classname [type_params] [inheritance] ":" suite
inheritance: "(" [argument_list] ")"
classname:   identifier

تعریف کلاس یک دستور قابل‌اجرا است. فهرست وراثی معمولاً فهرستی از کلاس‌های پایه را ارائه می‌کند (برای کاربردهای پیشرفته‌تر فراکلاس‌ها را ببینید)، بنابراین هر آیتم در این فهرست باید به یک شیء کلاس ارزیابی شود که امکان زیرکلاس‌سازی را فراهم کند. کلاس‌های بدون فهرست وراثی، به‌طور پیش‌فرض از کلاس پایه object ارث می‌برند؛ بنابراین،

class Foo:
    pass

معادل است با

class Foo(object):
    pass

سپس بدنه کلاس در یک فریم اجرای جدید (execution frame) اجرا می‌شود (ببینید نام‌گذاری و مقیدسازی)، با استفاده از یک فضای نام محلی به‌تازگی ایجادشده و فضای نام سراسری اصلی. (معمولاً مجموعه بیشتر شامل تعریف توابع است.) هنگامی که اجرای مجموعه‌ی کلاس به پایان می‌رسد، فریم اجرای آن دور انداخته می‌شود اما فضای نام محلی آن ذخیره می‌شود. [5] سپس یک شیء کلاس با استفاده از فهرست وراثت برای کلاس‌های پایه و فضای نام محلی ذخیره‌شده برای دیکشنری ویژگی‌ها ایجاد می‌شود. نام کلاس در فضای نام محلی اصلی به این شیء کلاس مقید می‌شود.

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

ایجاد کلاس را می‌توان با استفاده از فراکلاس‌ها به‌شدت سفارشی کرد.

کلاس‌ها را نیز می‌توان دکور کرد: درست مانند زمانی که توابع را دکور می‌کنید،

@f1(arg)
@f2
class Foo: pass

تقریباً معادل است با

class Foo: pass
Foo = f1(arg)(f2(Foo))

قواعد ارزیابی عبارت‌های دکوراتور همان قواعد دکوراتورهای تابع است. سپس نتیجه به نام کلاس پیوند داده می‌شود.

تغییر یافته در نسخه‌ی 3.9: می‌توان کلاس‌ها را با هر assignment_expression معتبر دکور کرد. پیش‌تر، دستور زبان بسیار محدودتر بود؛ برای جزئیات، PEP 614 را ببینید.

می‌توان فهرستی از پارامترهای نوع را بلافاصله پس از نام کلاس در کروشه قرار داد. این موضوع به بررسی‌کننده‌های نوع ایستا نشان می‌دهد که کلاس عام است. در ران‌تایم، پارامترهای نوع را می‌توان از ویژگی __type_params__ کلاس بازیابی کرد. برای اطلاعات بیشتر کلاس‌های عام را ببینید.

تغییر یافته در نسخه‌ی 3.12: فهرست‌های پارامترهای نوع در پایتون 3.12 جدید هستند.

یادداشت برنامه‌نویس: متغیرهایی که در تعریف کلاس تعریف می‌شوند، صفات کلاس هستند؛ آن‌ها بین نمونه‌ها مشترک‌اند. صفات نمونه می‌توانند در یک متد با self.name = value تنظیم شوند. هم صفات کلاس و هم صفات نمونه از طریق نمادگذاری "self.name" قابل دسترسی هستند، و یک صفت نمونه هنگام دسترسی به این روش، صفت کلاس با همان نام را می‌پوشاند. صفات کلاس می‌توانند به‌عنوان مقادیر پیش‌فرض برای صفات نمونه استفاده شوند، اما استفاده از مقادیر تغییرپذیر در آنجا می‌تواند به نتایج غیرمنتظره منجر شود. توصیف‌گرها می‌توانند برای ایجاد متغیرهای نمونه با جزئیات پیاده‌سازی متفاوت استفاده شوند.

همچنین ملاحظه نمائید

PEP 3115 - فراکلاس‌ها در پایتون 3000

پیشنهادی که اعلام فراکلاس‌ها را به سینتکس کنونی تغییر داد، و معناشناسی چگونگی ساخت کلاس‌های دارای فراکلاس.

PEP 3129 - دکوراتورهای کلاس

پیشنهادی که دکوراتورهای کلاس را افزود. دکوراتورهای تابع و متد در PEP 318 معرفی شدند.

8.9. هم‌روال‌ها

اضافه شده در نسخه‌ی 3.5.

8.9.1. تعریف تابع هم‌روال

async_funcdef: [decorators] "async" "def" funcname "(" [parameter_list] ")"
               ["->" expression] ":" suite

اجرای هم‌روال‌های پایتون را می‌توان در نقاط بسیاری معلق کرد و از سر گرفت (به هم‌روال مراجعه کنید). عبارت‌های await، async for و async with فقط در بدنه‌ی تابع هم‌روال قابل استفاده‌اند.

توابعی که با سینتکس async def تعریف می‌شوند، همیشه توابع هم‌روال هستند، حتی اگر شامل کلیدواژه‌های await یا async نباشند.

استفاده از عبارت yield from در بدنه‌ی یک تابع هم‌روال، موجب SyntaxError می‌شود.

نمونه‌ای از یک تابع هم‌روال:

async def func(param1, param2):
    do_stuff()
    await some_coroutine()

تغییر یافته در نسخه‌ی 3.7: await و async اکنون کلیدواژه هستند؛ پیش‌تر فقط در بدنه‌ی یک تابع هم‌روال به‌عنوان کلیدواژه با آن‌ها رفتار می‌شد.

8.9.2. دستور async for

async_for_stmt: "async" for_stmt

یک پیمایش‌پذیر ناهمگام یک متد __aiter__ فراهم می‌کند که مستقیماً یک پیمایش‌گر ناهمگام را بازمی‌گرداند، که می‌تواند کد ناهمگام را در متد __anext__ خود فراخوانی کند.

دستور async for امکان پیمایش آسان پیمایش‌پذیرهای ناهمگام را فراهم می‌کند.

کد زیر:

async for TARGET in ITER:
    SUITE
else:
    SUITE2

از نظر معنایی معادل است با:

iter = (ITER).__aiter__()
running = True

while running:
    try:
        TARGET = await iter.__anext__()
    except StopAsyncIteration:
        running = False
    else:
        SUITE
else:
    SUITE2

به جز اینکه جستجوی متد ویژه به‌صورت ضمنی برای __aiter__() و __anext__() استفاده می‌شود.

استفاده از دستور async for خارج از بدنه یک تابع هم‌روال، موجب SyntaxError می‌شود.

8.9.3. دستور async with

async_with_stmt: "async" with_stmt

مدیر زمینه ناهمگام یک مدیر زمینه است که می‌تواند اجرا را در متدهای enter و exit خود به حالت تعلیق درآورد.

کد زیر:

async with EXPRESSION as TARGET:
    SUITE

از نظر معنایی معادل است با:

manager = (EXPRESSION)
aenter = manager.__aenter__
aexit = manager.__aexit__
value = await aenter()
hit_except = False

try:
    TARGET = value
    SUITE
except:
    hit_except = True
    if not await aexit(*sys.exc_info()):
        raise
finally:
    if not hit_except:
        await aexit(None, None, None)

به‌جز اینکه از جستجوی متد ویژه به‌صورت ضمنی برای __aenter__() و __aexit__() استفاده می‌شود.

استفاده از دستور async with خارج از بدنه‌ی یک تابع هم‌روال، SyntaxError است.

همچنین ملاحظه نمائید

PEP 492 - هم‌روال‌ها با سینتکس async و await

پیشنهادی که هم‌روال‌ها را به مفهومی مستقل و واقعی در پایتون تبدیل کرد و سینتکس پشتیبانی را افزود.

8.10. فهرست‌های پارامتر نوع

اضافه شده در نسخه‌ی 3.12.

تغییر یافته در نسخه‌ی 3.13: پشتیبانی از مقادیر پیش‌فرض افزوده شد (ببینید PEP 696).

type_params:  "[" type_param ("," type_param)* "]"
type_param:   typevar | typevartuple | paramspec
typevar:      identifier (":" expression)? ("=" expression)?
typevartuple: "*" identifier ("=" expression)?
paramspec:    "**" identifier ("=" expression)?

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

def max[T](args: list[T]) -> T:
    ...

async def amax[T](args: list[T]) -> T:
    ...

class Bag[T]:
    def __iter__(self) -> Iterator[T]:
        ...

    def add(self, arg: T) -> None:
        ...

type ListOrSet[T] = list[T] | set[T]

از نظر معنایی، این نشان می‌دهد که تابع، کلاس، یا نام مستعار نوع، نسبت به یک متغیر نوع، عام است. این اطلاعات در درجه اول توسط بررسی‌کننده‌های نوع ایستا استفاده می‌شود، و در ران‌تایم، اشیاء عام بسیار شبیه همتایان غیرعام خود رفتار می‌کنند.

پارامترهای نوع در کروشه‌ها ([]) بلافاصله پس از نام تابع، کلاس یا نام مستعار نوع اعلان می‌شوند. پارامترهای نوع در محدوده شیء عام قابل دسترسی هستند، اما در جاهای دیگر قابل دسترسی نیستند. بنابراین، پس از اعلان def func[T](): pass، نام T در محدوده ماژول در دسترس نیست. در ادامه، معناشناسی اشیاء عام با دقت بیشتری توصیف می‌شود. محدوده پارامترهای نوع با یک تابع خاص مدل‌سازی می‌شود (از نظر فنی، یک annotation scope) که ایجاد شیء عام را در بر می‌گیرد.

توابع عام، کلاس‌ها و نام‌های مستعار نوع دارای یک ویژگی __type_params__ هستند که پارامترهای نوع آن‌ها را فهرست می‌کند.

پارامترهای نوع در سه دسته قرار دارند:

  • typing.TypeVar، که با یک نام ساده معرفی می‌شود (مثلاً T). از نظر معنایی، این نشان‌دهنده‌ی یک نوع واحد برای بررسی‌گر نوع است.

  • typing.TypeVarTuple، که با نامی با پیشوند یک ستاره معرفی می‌شود (برای مثال *Ts). از نظر معنایی، این نشان‌دهنده‌ی تاپلی از هر تعداد نوع است.

  • typing.ParamSpec، که با نامی با پیشوند دو ستاره معرفی می‌شود (برای مثال، **P). از نظر معنایی، این بیانگر پارامترهای یک شیء فراخوانی‌پذیر است.

اعلان‌های typing.TypeVar می‌توانند کران‌ها (bounds) و محدودیت‌ها (constraints) را با یک دونقطه (:) و به‌دنبال آن یک عبارت تعریف کنند. یک عبارت تنها پس از دونقطه نشان‌دهنده یک کران است (برای مثال T: int). از نظر معنایی، این بدان معنا است که typing.TypeVar فقط می‌تواند نشان‌دهنده انواعی باشد که زیرنوع این کران هستند. یک تاپل داخل پرانتز از عبارت‌ها پس از دونقطه نشان‌دهنده مجموعه‌ای از محدودیت‌ها است (برای مثال T: (str, bytes)). هر عضو این تاپل باید یک نوع باشد (باز هم، این موضوع در ران‌تایم اعمال نمی‌شود). متغیرهای نوع محدودشده فقط می‌توانند یکی از انواع موجود در فهرست محدودیت‌ها را بپذیرند.

برای typing.TypeVarهایی که با سینتکس فهرست پارامترهای نوع اعلام‌شده‌اند، کران و محدودیت‌ها هنگام ایجاد شیء عام ارزیابی نمی‌شوند، بلکه تنها زمانی ارزیابی می‌شوند که به مقدار آن‌ها به‌صراحت از طریق ویژگی‌های __bound__ و __constraints__ دسترسی پیدا شود. برای دستیابی به این هدف، کران‌ها یا محدودیت‌ها در یک محدوده حاشیه‌نویسی (annotation scope) جداگانه ارزیابی می‌شوند.

typing.TypeVarTupleها و typing.ParamSpecها نمی‌توانند کران یا محدودیت داشته باشند.

هر سه گونه از پارامترهای نوع نیز می‌توانند یک مقدار پیش‌فرض داشته باشند، که هنگامی استفاده می‌شود که پارامتر نوع به‌صراحت ارائه نشده باشد. این مقدار با افزودن یک علامت تساوی (=) و به دنبال آن یک عبارت اضافه می‌شود. مانند کران‌ها و قیدهای متغیرهای نوع، مقدار پیش‌فرض هنگام ایجاد شیء ارزیابی نمی‌شود، بلکه تنها زمانی ارزیابی می‌شود که به ویژگی __default__ پارامتر نوع دسترسی پیدا شود. به همین منظور، مقدار پیش‌فرض در یک محدوده حاشیه‌نویسی (annotation scope) جداگانه ارزیابی می‌شود. اگر مقدار پیش‌فرضی برای یک پارامتر نوع مشخص نشده باشد، ویژگی __default__ به شیء نشانگر (sentinel) ویژه‌ی typing.NoDefault تنظیم می‌شود.

مثال زیر مجموعه‌ی کاملی از اعلان‌های پارامتر نوع مجاز را نشان می‌دهد:

def overly_generic[
   SimpleTypeVar,
   TypeVarWithDefault = int,
   TypeVarWithBound: int,
   TypeVarWithConstraints: (str, bytes),
   *SimpleTypeVarTuple = (int, float),
   **SimpleParamSpec = (str, bytearray),
](
   a: SimpleTypeVar,
   b: TypeVarWithDefault,
   c: TypeVarWithBound,
   d: Callable[SimpleParamSpec, TypeVarWithConstraints],
   *e: SimpleTypeVarTuple,
): ...

8.10.1. توابع عام

توابع عام به‌صورت زیر تعریف می‌شوند:

def func[T](arg: T): ...

این سینتکس معادل است با:

annotation-def TYPE_PARAMS_OF_func():
    T = typing.TypeVar("T")
    def func(arg: T): ...
    func.__type_params__ = (T,)
    return func
func = TYPE_PARAMS_OF_func()

در اینجا annotation-def یک محدوده annotation را نشان می‌دهد، که در واقع در ران‌تایم به هیچ نامی مقید نیست. (یک آزادی عمل دیگر نیز در ترجمه اعمال شده است: این سینتکس از دسترسی به ویژگی ماژول typing استفاده نمی‌کند، بلکه مستقیماً یک نمونه از typing.TypeVar ایجاد می‌کند.)

حاشیه‌نویسی‌های توابع عام در محدوده حاشیه‌نویسی‌ای که برای اعلان پارامترهای نوع استفاده می‌شود، ارزیابی می‌شوند، اما مقادیر پیش‌فرض و دکوراتورهای تابع این‌گونه ارزیابی نمی‌شوند.

مثال زیر قوانین محدوده را برای این موارد، و همچنین برای گونه‌های دیگری از پارامترهای نوع نشان می‌دهد:

@decorator
def func[T: int, *Ts, **P](*args: *Ts, arg: Callable[P, T] = some_default):
    ...

به‌جز ارزیابی تنبل کران TypeVar، این معادل است با:

DEFAULT_OF_arg = some_default

annotation-def TYPE_PARAMS_OF_func():

    annotation-def BOUND_OF_T():
        return int
    # In reality, BOUND_OF_T() is evaluated only on demand.
    T = typing.TypeVar("T", bound=BOUND_OF_T())

    Ts = typing.TypeVarTuple("Ts")
    P = typing.ParamSpec("P")

    def func(*args: *Ts, arg: Callable[P, T] = DEFAULT_OF_arg):
        ...

    func.__type_params__ = (T, Ts, P)
    return func
func = decorator(TYPE_PARAMS_OF_func())

نام‌های با حروف بزرگ مانند DEFAULT_OF_arg در واقع در ران‌تایم مقید نمی‌شوند.

8.10.2. کلاس‌های عام

کلاس‌های عام به صورت زیر تعریف می‌شوند:

class Bag[T]: ...

این سینتکس معادل است با:

annotation-def TYPE_PARAMS_OF_Bag():
    T = typing.TypeVar("T")
    class Bag(typing.Generic[T]):
        __type_params__ = (T,)
        ...
    return Bag
Bag = TYPE_PARAMS_OF_Bag()

در اینجا نیز annotation-def (که یک کلیدواژه واقعی نیست) یک محدوده حاشیه‌نویسی (annotation scope) را نشان می‌دهد، و نام TYPE_PARAMS_OF_Bag در واقع در ران‌تایم مقید نمی‌شود.

کلاس‌های عام به‌طور ضمنی از typing.Generic ارث می‌برند. کلاس‌های پایه و آرگومان‌های کلیدواژه‌ای کلاس‌های عام در محدوده نوع برای پارامترهای نوع ارزیابی می‌شوند، و دکوراتورها خارج از آن محدوده ارزیابی می‌شوند. این موضوع در این مثال نشان داده شده است:

@decorator
class Bag(Base[T], arg=T): ...

این معادل است با:

annotation-def TYPE_PARAMS_OF_Bag():
    T = typing.TypeVar("T")
    class Bag(Base[T], typing.Generic[T], arg=T):
        __type_params__ = (T,)
        ...
    return Bag
Bag = decorator(TYPE_PARAMS_OF_Bag())

8.10.3. نام‌های مستعار نوع عام

همچنین می‌توان از دستور type برای ایجاد یک نام مستعار نوع عام استفاده کرد:

type ListOrSet[T] = list[T] | set[T]

به‌جز ارزیابی تنبل مقدار، این معادل است با:

annotation-def TYPE_PARAMS_OF_ListOrSet():
    T = typing.TypeVar("T")

    annotation-def VALUE_OF_ListOrSet():
        return list[T] | set[T]
    # In reality, the value is lazily evaluated
    return typing.TypeAliasType("ListOrSet", VALUE_OF_ListOrSet(), type_params=(T,))
ListOrSet = TYPE_PARAMS_OF_ListOrSet()

در اینجا، annotation-def (که یک کلیدواژه واقعی نیست) یک محدوده‌ی حاشیه‌نویسی را نشان می‌دهد. نام‌های با حروف بزرگ مانند TYPE_PARAMS_OF_ListOrSet در واقع در ران‌تایم پیوند داده نمی‌شوند.

8.11. حاشیه‌نویسی‌ها

تغییر یافته در نسخه‌ی 3.14: حاشیه‌گذاری‌ها اکنون به‌طور پیش‌فرض به‌صورت تنبل ارزیابی می‌شوند.

متغیرها و پارامترهای تابع می‌توانند دارای حاشیه‌نویسی‌ها باشند، که با افزودن علامت دونقطه پس از نام و سپس یک عبارت ایجاد می‌شوند:

x: annotation = 1
def f(param: annotation): ...

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

def f() -> annotation: ...

حاشیه‌نویسی‌ها به‌صورت قراردادی برای راهنمای نوع استفاده می‌شوند، اما زبان این موضوع را تحمیل نمی‌کند، و به‌طور کلی حاشیه‌نویسی‌ها می‌توانند شامل عبارت‌های دلخواه باشند. وجود حاشیه‌نویسی‌ها معناشناسی ران‌تایم کد را تغییر نمی‌دهد، مگر اینکه از سازوکاری استفاده شود که حاشیه‌نویسی‌ها را درون‌نگری کند و از آن‌ها استفاده کند (مانند dataclasses یا @functools.singledispatch).

به‌طور پیش‌فرض، حاشیه‌نویسی‌ها در یک محدوده حاشیه‌نویسی به‌صورت تنبل ارزیابی می‌شوند. این بدان معناست که وقتی کد حاوی حاشیه‌نویسی ارزیابی می‌شود، آن‌ها ارزیابی نمی‌شوند. در عوض، مفسر اطلاعاتی را ذخیره می‌کند که در صورت درخواست، می‌توان از آن برای ارزیابی حاشیه‌نویسی بعداً استفاده کرد. ماژول annotationlib ابزارهایی برای ارزیابی حاشیه‌نویسی‌ها فراهم می‌کند.

اگر دستور future from __future__ import annotations وجود داشته باشد، همه حاشیه‌نویسی‌ها به‌جای آن به‌صورت رشته ذخیره می‌شوند:

>>> from __future__ import annotations
>>> def f(param: annotation): ...
>>> f.__annotations__
{'param': 'annotation'}

این دستور future در نسخه‌ای آینده از پایتون منسوخ و حذف خواهد شد، اما نه پیش از آن که پایتون 3.13 به پایان عمر خود برسد (به PEP 749 مراجعه کنید). در صورت استفاده از آن، احتمال کمتری وجود دارد که ابزارهای درون‌نگری مانند annotationlib.get_annotations() و typing.get_type_hints() بتوانند حاشیه‌نویسی‌ها را در ران‌تایم حل کنند.

پانویس‌ها