Faragiri

همهٔ زبان‌ها

آموزش تابعسازه‌های اصلی برنامه

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

۳بخش
۳دقیقه خواندن
۴عمق

یک نام

چهار حلقه‌ای که یک نام در آن‌ها گشته می‌شود

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

  1. داخل خود فراخوانیمحلینام‌های همین فراخوانی، که با برگشتنش تمام می‌شوند
  2. ۰۲تابع دربرگیرندهجایی که Shiraz روی Tehran را می‌پوشاند، و nonlocal برای نوشتن در آن است
  3. ۰۳سطح ماژولهرچه بالای فایل تعریف شده، و global برای نوشتن در آن است
  4. ۰۴نام‌های توکارآخرین جایی که گشته می‌شود، و دقیقا برای همین می‌شود رویش نوشت

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

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

  • def gozaresh(tarikh=datetime.now()):
  • return tarikh

دو بار صدا زدنش با نیم ثانیه فاصله دو مقدار برابر می‌دهد، و فردا هم اگر همان فرایند زنده باشد همان دیروز را می‌دهد. مقدار پیش‌فرض جزو بدنه تابع نیست، جزو خود سطر def است، و آن سطر یک بار اجرا شد. می‌شود دیدش: gozaresh.__defaults__ یک چندتایی با همان تاریخ منجمد در آن است، و inspect.signature(gozaresh) همان را داخل امضای تابع چاپ می‌کند.

راه درستش دو سطر است و همیشه همین دو سطر:

  • def gozaresh(tarikh=None):
  • tarikh = datetime.now() if tarikh is None else tarikh

قاعده‌ای که این را جمع می‌کند کوتاه است: هر مقدار پیش‌فرضی که یک فراخوانی است یا می‌شود در جا عوضش کرد، باید داخل بدنه ساخته شود. فهرست خالی و دیکشنری خالی و datetime.now() هر سه در همین دسته‌اند.

یک نام کجا گشته می‌شود

وقتی داخل یک تابع نامی را می‌خوانید، پایتون چهار جا را به ترتیب می‌گردد و اولین جایی که پیدا کرد برنده است. اگر یک تابع داخل تابع دیگر تعریف شده باشد و هر دو یک نام داشته باشند، درونی مقدار بیرونی را می‌بیند و نه مقدار سطح ماژول را: با shahr برابر Tehran در ماژول و برابر Shiraz در تابع دربرگیرنده، تابع درونی رشته Shiraz را برمی‌گرداند و مقدار ماژول دست‌نخورده می‌ماند.

نوشتن در این حلقه‌ها قاعده جدا دارد. انتساب ساده همیشه نام را محلی می‌کند؛ برای نوشتن در حلقه بیرونی nonlocal لازم است و برای نوشتن در سطح ماژول global. حلقه چهارم، یعنی نام‌های توکار، همیشه آخرین جایی است که گشته می‌شود و درست به همین دلیل می‌شود رویش نوشت: یک list = [1, 2] در سطح ماژول کافی است تا هر list(...) بعدی با TypeError: 'list' object is not callable بشکند، در فایلی که هیچ ایراد دیگری ندارد.

جایی که کار گیر می‌کند، آموزش زودهنگام global است

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

ابزار، و سالی که این حرف درست است

در سال ۲۰۲۶ برای دیدن اینکه یک تابع واقعا چه امضایی دارد، inspect.signature در کتابخانه استاندارد و رایگان است و جای خواندن رشته مستندات یا حدس زدن از روی سطر def را گرفته؛ همان‌طور که بالا دیدید، مقدار پیش‌فرض واقعی را نشان می‌دهد و نه چیزی که نوشته‌اید. برای گرفتن همین اشتباه پیش از اجرا، ruff قاعده‌های bugbear را در خودش دارد و مقدار پیش‌فرضی که یک فراخوانی است یا در جا تغییرپذیر است را علامت می‌زند؛ رایگان است و پس از نصب به شبکه کاری ندارد.

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

کنار این

بالای این