این تابع تاریخ امروز را برنمیگرداند، تاریخ لحظهای را برمیگرداند که فایل برای اولین بار خوانده شد:
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 را در خودش دارد و مقدار پیشفرضی که یک فراخوانی است یا در جا تغییرپذیر است را علامت میزند؛ رایگان است و پس از نصب به شبکه کاری ندارد.
پرسش بعدی این است که وقتی یک فراخوانی برگشت، از نامهایش چه میماند و چه نمیماند، که برنامه چطور یادش میماند باید کجا برگردد جوابش را دارد. و اگر تابعهایتان زیاد شده و نامهایشان دیگر نمیگویند چه میکنند، ارزانترین مستندی که وجود دارد جای بعدی است. همین ایده در زبانی که تابع را مقدار میداند شکل دیگری دارد: نوعها، تابعها و بخشهایی که غافلگیر میکنند.