بستار در پایتون (Closures) — به زبان ساده
در این مطلب، مفهوم بستار در پایتون (Python Closures) آموزش داده میشود. سپس، روش تعریف کردن یک بستار در پایتون، دلایل این کار و چرایی استفاده از بستار تشریح میشود.
متغیرهای غیر محلی در توابع توکار
پیش از آنکه به بیان مفهوم بستار پرداخته شود، ابتدا باید مفهوم «توابع تو در تو» (Nested Function) و متغیرهای غیر محلی مورد بررسی قرار بگیرد. یک تابع تعریف شده درون تابع دیگر را تابع تو در تو میگویند. توابع تو در تو میتوانند به متغیرهای محصور در دامنه یکدیگر دسترسی داشته باشند.
در پایتون، این متغیرهای غیر محلی به طور پیشفرض «فقط خواندنی» (Read Only) هستند و باید آنها را به طور صریح به عنوان غیر محلی در نظر گرفت (با استفاده از کلیدواژه nonlocal) تا بتوان آنها را ویرایش کرد. در ادامه، مثالی از توابع تودرتو که به متغیر محلی دسترسی دارند آورده شده است.
1def print_msg(msg):
2# This is the outer enclosing function
3
4 def printer():
5# This is the nested function
6 print(msg)
7
8 printer()
9
10# We execute the function
11# Output: Hello
12print_msg("Hello")
میتوان مشاهده کرد که تابع تو در توی ()printer قادر به دسترسی به متغیر غیر محلی msg از تابع محصور در آن است.
تعریف تابع بستار در پایتون
در مثال بالا، اگر آخرین خط از تابع ()print_msg به جای فراخوانی تابع ()printer، آن را باز گرداند، چه اتفاقی میافتاد؟ این یعنی تابع به صورت زیر تعریف شده است.
1def print_msg(msg):
2# This is the outer enclosing function
3
4 def printer():
5# This is the nested function
6 print(msg)
7
8 return printer # this got changed
9
10# Now let's try calling this function.
11# Output: Hello
12another = print_msg("Hello")
13another()
تابع ()print_msg با رشته "Hello" فراخوانی شده است و تابع بازگردانده شده به نام another محدود شده است. با فراخوانی ()another، پیام همچنان به خاطر سپرده میشد، اگرچه، اجرای تابع ()print_msg پیش از این به پایان رسیده بوده است.
این روش که با بهرهگیری از آن برخی دادهها ("Hello") به کد ضمیمه میشوند، «بستار در پایتون» (Closure in Python) نامیده میشود. این مقدار در دامنه محصور حتی هنگامی که متغیر از دامنه خود خارج میشود و یا خود تابع از فضای نام کنونی حذف شود، همچنان به یاد آورده میشود. برای مشاهده خروجی کد زیر، باید آن را در شل پایتون اجرا کرد.
1>>> del print_msg
2>>> another()
3Hello
4>>> print_msg("Hello")
5Traceback (most recent call last):
6...
7NameError: name 'print_msg' is not defined
چه زمانی به بستار نیاز است؟
همانطور که از مثال بالا مشهود است، هنگامی که یک تابع تو در تو، به یک مقدار در دامنه محصور شده خود ارجاع میدهد، یک بستار در پایتون داریم.
معیارهایی که باید برای ساخت بستار در پایتون وجود داشته باشند، در زیر بیان شدهاند:
- باید یک تابع تو در تو وجود داشته باشد (تابع درون تابع).
- تابع تویی باید به مقدار تعریف شده در تابع محصور در آن اشاره داشته باشد.
- تابع محصور باید تابع تویی را باز گرداند.
دلیل استفاده از بستار
پرسشی که در این وهله مطرح میشود این است که بستارها برای چه خوب هستند. بستارها میتوانند مانع استفاده از مقادیر سراسری شوند و برخی از اشکال پنهانسازی داده را فراهم کنند. همچنین، میتوانند یک راهکار شیگرا برای مساله فراهم کند.
هنگامی که متدهای کمی (یک متد در اغلب مواقع) برای پیادهسازی در یک کلاس وجود دارد، بستار میتواند یک راهکار ظریفتر و متناوب (جایگزین) فراهم کند. اما هنگامی که تعداد خصیصهها و متدها بزرگتر میشود، بهتر است یک کلاس پیادهسازی شود. در ادامه، مثال سادهای ارائه شده است که در آن، استفاده از یک بستار ممکن است به تعریف یک کلاس ساخت اشیا ترجیح داده شود. اما انتخاب در نهایت با کاربر است.
1def make_multiplier_of(n):
2 def multiplier(x):
3 return x * n
4 return multiplier
5
6# Multiplier of 3
7times3 = make_multiplier_of(3)
8
9# Multiplier of 5
10times5 = make_multiplier_of(5)
11
12# Output: 27
13print(times3(9))
14
15# Output: 15
16print(times5(3))
17
18# Output: 30
19print(times5(times3(2)))
«دکوراترها در پایتون» (Decorators in Python) استفاده گستردهای از بستارها دارند. شایان ذکر است که مقادیری که در یک تابع بستار محصور میشوند را میتوان یافت.
همه اشیای تابع، دارای خصیصه __closure__ هستند که یک تاپل از اشیای سلول را در صورتی که یک تابع بستار باشد، باز میگرداند. با ارجاع به مثال بالا، times3 و times5 توابع بستار هستند.
>>> make_multiplier_of.__closure__ >>> times3.__closure__ (<cell at 0x0000000002D155B8: int object at 0x000000001E39B6E0>,)
شی cell دارای خصیصه cell_contents است که مقادیر محصور را ذخیره میکند.
>>> times3.__closure__[0].cell_contents 3 >>> times5.__closure__[0].cell_contents 5
اگر نوشته بالا برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی پایتون Python
- آموزش تکمیلی برنامهنویسی پایتون
- مجموعه آموزشهای دادهکاوی و یادگیری ماشین
- زبان برنامهنویسی پایتون (Python) — از صفر تا صد
- یادگیری علم داده (Data Science) با پایتون — از صفر تا صد
- آموزش پایتون (Python) — مجموعه مقالات جامع وبلاگ فرادرس
^^