FA-TOOLS — Header Component
Closure در پایتون — مفهوم و کاربرد با مثال

closure در پایتون — مفهوم و کاربرد با مثال

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

نکته مهم: اگه نیاز به کدهای پایتون آماده داری، حتماً سری به بخش اسنیپت‌های پایتون ما بزن تا کارهات سریع‌تر پیش بره!

🔍 کلوژر در یک نگاه (اینفوگرافیک متنی)

Closure در پایتون — مفهوم و کاربرد با مثال — تصویر 1

خلاصه هر آنچه که باید درباره کلوژر بدونی:

💡 مفهوم اصلی

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

⚙️ اجزای تشکیل‌دهنده

  • تابع بیرونی (Outer Function): تابعی که کلوژر رو برمی‌گردونه.
  • تابع درونی (Inner Function): خود کلوژر.
  • متغیرهای غیرمحلی (Non-local Variables): متغیرهایی از تابع بیرونی که کلوژر بهشون دسترسی داره.

🌟 کاربردها

  • ساخت کارخانه‌های تابع (Function Factories).
  • حفظ وضعیت (State Retention) و کپسوله‌سازی.
  • پایه‌ای برای ساخت دکوراتورها.
  • Callbacks و برنانویسی رویدادمحور.

⚠️ نکات کلیدی

  • متغیرها رو با `nonlocal` تغییر بده.
  • مراقب مشکل `Late Binding` در حلقه‌ها باش.
  • برای کپسوله‌سازی، گاهی کلاس‌ها انتخاب بهتری هستند.

کلوژر چیست؟ یک تعریف ساده و دوستانه

Closure در پایتون — مفهوم و کاربرد با مثال — تصویر 2

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

حالا اینو به دنیای پایتون بیاریم: یک کلوژر (Closure)، یک تابع درونی (nested function) هست که به متغیرهای محلی تابع بیرونی خودش دسترسی داره، حتی بعد از اینکه تابع بیرونی کارش رو تموم کرده و از پشته فراخوانی (call stack) خارج شده. به عبارت دیگه، کلوژر “محیط” یا “کانتکست” که در اون تعریف شده رو به یاد می‌سپاره.

پیش‌نیازها: تابع تو در تو و دامنه متغییرها

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

  • توابع تو در تو (Nested Functions): یعنی تابعی رو داخل تابع دیگه تعریف کنی. این کار به تو کمک می‌کنه کدهات رو مرتب‌تر نگه داری و از تداخل اسمی جلوگیری کنی.
  • دامنه متغیرها (Variable Scope): پایتون قوانین مشخصی برای دسترسی به متغیرها داره که بهش LEGB (Local, Enclosing, Global, Built-in) می‌گیم. کلوژرها با دامنه `Enclosing` (محیط در برگیرنده) سروکار دارن.

اینم یه مثال ساده از تابع تو در تو:


def تابع_بیرونی(پیام):
    # پیام یک متغیر محلی برای تابع_بیرونی است
    # اما برای تابع_درونی، یک متغیر غیرمحلی (enclosing) است

    def تابع_درونی():
        print(f"سلام: {پیام}") # دسترسی به پیام از تابع بیرونی

    return تابع_درونی # تابع درونی رو برمی‌گردونیم، نه نتیجه اجراش رو

حالا بیا کلوژر رو تست کنیم:


تابع_من = تابع_بیرونی("جهان")
تابع_من() # خروجی: سلام: جهان

دیدید؟ تابع بیرونی (تابع_بیرونی) اجراش تموم شد و برگشت، اما تابع_من (که همون تابع_درونی هست) هنوز پیام رو یادش بود! این دقیقا جادوی کلوژره.

مغز متفکر کلوژر: حفظ حالت (State Retention)

Closure در پایتون — مفهوم و کاربرد با مثال — تصویر 3

قلب کلوژر، توانایی اون در حفظ و به یاد سپردن حالت (State) محیطی هست که در اون تعریف شده. یعنی می‌تونه به متغیرهای محلی تابع بیرونی دسترسی پیدا کنه و حتی اونها رو تغییر بده. برای تغییر این متغیرها، باید از کلمه کلیدی nonlocal استفاده کنیم.

اینم یه مثال با nonlocal که یه کانتر می‌سازه:


def ساخت_کانتر():
    شماره = 0 # متغیر غیرمحلی که توسط کلوژر حفظ می‌شود

    def افزایش_دهنده():
        nonlocal شماره # اعلام می‌کنیم که شماره یک متغیر غیرمحلی است
        شماره += 1
        return شماره
    return افزایش_دهنده

کانتر_اول = ساخت_کانتر()
print(کانتر_اول()) # خروجی: 1
print(کانتر_اول()) # خروجی: 2
print(کانتر_اول()) # خروجی: 3

کانتر_دوم = ساخت_کانتر() # یک کانتر جدید و مستقل
print(کانتر_دوم()) # خروجی: 1
print(کانتر_اول()) # خروجی: 4 (کانتر اول هنوز حالت خودش رو حفظ کرده!)

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

چرا اینقدر کلوژر کاربردیه؟ موارد استفاده واقعی

کلوژرها تو دنیای واقعی کاربردهای زیادی دارن. بیا چندتا از مهم‌ترین‌هاشون رو با هم ببینیم:

۱. کارخانه‌های تابع (Function Factories)

وقتی می‌خوای توابع مختلفی رو بسازی که همگی یک الگوی رفتاری رو دارن، اما با یه سری تنظیمات متفاوت، کلوژر مثل یه کارخونه عمل می‌کنه که این توابع رو برات تولید می‌کنه. مثلاً ساخت توابعی برای اعمال تخفیف‌های مختلف:


def ساخت_تخفیف_دهنده(درصد_تخفیف):
    def اعمال_تخفیف(قیمت):
        return قیمت * (1 - درصد_تخفیف / 100)
    return اعمال_تخفیف

تخفیف_۱۰_درصد = ساخت_تخفیف_دهنده(10)
تخفیف_۲۰_درصد = ساخت_تخفیف_دهنده(20)

print(f"قیمت با ۱۰% تخفیف: {تخفیف_۱۰_درصد(100)}") # خروجی: 90.0
print(f"قیمت با ۲۰% تخفیف: {تخفیف_۲۰_درصد(100)}") # خروجی: 80.0

۲. حفظ وضعیت (Encapsulation/Data Hiding)

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


def ساخت_ژنراتور_ID():
    آخرین_ID = 0

    def تولید_ID():
        nonlocal آخرین_ID
        آخرین_ID += 1
        return آخرین_ID
    return تولید_ID

ژنراتور_کاربران = ساخت_ژنراتور_ID()
ژنراتور_محصولات = ساخت_ژنراتور_ID()

print(f"ID کاربر جدید: {ژنراتور_کاربران()}") # خروجی: ID کاربر جدید: 1
print(f"ID محصول جدید: {ژنراتور_محصولات()}") # خروجی: ID محصول جدید: 1
print(f"ID کاربر بعدی: {ژنراتور_کاربران()}") # خروجی: ID کاربر بعدی: 2

۳. دکوراتورها (Decorators)

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

۴. Callbacks و برنانویسی رویدادمحور

در برنامه‌نویسی رویدادمحور (مثل برنامه‌های GUI یا Asynchronous)، ما معمولاً توابعی رو به‌عنوان Callbacks پاس می‌دیم که بعداً توسط سیستم فراخوانی می‌شن. کلوژرها می‌تونن اطلاعات مورد نیاز رو از زمان تعریف Callback، همراه خودشون نگه دارن و وقتی فراخوانی شدن، ازشون استفاده کنن.

تفاوت کلوژر با سایر مفاهیم مرتبط

کلوژر در مقابل کلاس (Class vs. Closure)

خیلی‌ها می‌پرسن که خب، این که شبیه کلاسه! درسته، کلوژرها و کلاس‌ها هر دو می‌تونن وضعیت (state) رو حفظ کنن و رفتار (behavior) رو ارائه بدن، اما تفاوت‌های کلیدی دارن:

ویژگی کلوژر (Closure)
ساختار تابعی که تابع دیگری را برمی‌گرداند و به متغیرهای تابع بیرونی دسترسی دارد.
سادگی برای موارد ساده‌تر و تک‌رفتاری، معمولاً سینتکس ساده‌تری دارد.
قابلیت استفاده مجدد بیشتر برای تولید توابع شخصی‌سازی شده مناسب است.
تغییر وضعیت با استفاده از `nonlocal` انجام می‌شود.

چه زمانی از کدام استفاده کنیم؟

  • کلوژر: وقتی فقط یک رفتار (تابع) و یک یا چند تکه داده کوچک رو می‌خوای کپسوله کنی. برای مثال‌های بالا (کانتر، تخفیف‌دهنده) خیلی مناسبه.
  • کلاس: وقتی با موجودیت‌های پیچیده‌تر سروکار داری که چندین رفتار (متد) و چندین تکه داده (ویژگی) دارن و نیاز به ارث‌بری یا پلی‌مورفیسم داری.

کلوژر در مقابل لمبدا (Lambda vs. Closure)

لمبدا (Lambda) در پایتون صرفاً یک سینتکس کوتاه‌تر برای تعریف توابع ناشناس (anonymous functions) هست که معمولاً برای عملیات ساده و تک‌خطی استفاده میشه. یک تابع لمبدا می‌تونه یک کلوژر باشه، اگه به متغیرهای غیرمحلی دسترسی پیدا کنه. پس لمبدا صرفاً یه نوع خاص از تابع هست، در حالی که کلوژر یه خاصیت یا قابلیت برای توابع (چه عادی و چه لمبدا) به حساب میاد.

مثال: لمبدا به عنوان کلوژر


def سازنده_ضرب_کننده(ضریب):
    return lambda عدد: عدد * ضریب

ضرب_در_۱۰ = سازنده_ضرب_کننده(10)
print(ضرب_در_۱۰(5)) # خروجی: 50

چالش‌ها و نکات مهم در کار با کلوژرها

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

مشکل متغیر لوپ (Late Binding Closures)

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

این کد مشکل داره!


توابع = []
for i in range(5):
    def تابع_من():
        return i
    توابع.append(تابع_من)

for f in توابع:
    print(f()) # خروجی: 4, 4, 4, 4, 4 (همیشه آخرین مقدار i)

راه حل: می‌تونی با استفاده از مقدار پیش‌فرض (default argument) برای تابع درونی، مقدار i رو در زمان تعریف کلوژر “ثابت” کنی:

راه حل صحیح!


توابع_صحیح = []
for i in range(5):
    def تابع_من(x=i): # مقدار i را به عنوان پیش‌فرض x ذخیره می‌کند
        return x
    توابع_صحیح.append(تابع_من)

for f in توابع_صحیح:
    print(f()) # خروجی: 0, 1, 2, 3, 4 (حالا درست کار می‌کنه!)

مدیریت حافظه (Memory Management)

اگه کلوژرها رو زیاد و با متغیرهای بزرگ استفاده کنی، ممکنه به خاطر حفظ حالت (state retention) حافظه بیشتری مصرف کنن. برای اکثر کاربردها این مسئله‌ای نیست، اما در سیستم‌های با محدودیت حافظه باید حواست باشه. کلوژر تا زمانی که خودش از بین نرفته، به متغیرهایی که بهشون ارجاع داده، اجازه نمی‌ده پاک بشن.

خوانایی کد (Code Readability)

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

راهنمای عملی: چطور یک کلوژر بنویسیم؟ (آموزش گام به گام)

حالا که مفاهیم رو یاد گرفتیم، بیا یک کلوژر واقعی بنویسیم که یه پیام رو با یه پیشوند ثابت ترکیب کنه:

گام ۱: تعریف تابع بیرونی

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


def ساخت_پیام_ساز(پیشوند):
    # 'پیشوند' متغیر غیرمحلی ماست

گام ۲: تعریف تابع درونی (کلوژر)

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


def ساخت_پیام_ساز(پیشوند):
    def پیام_بده(متن):
        return f"{پیشوند} {متن}"

گام ۳: برگرداندن تابع درونی

حالا تابع بیرونی باید تابع درونی رو برگردونه. نکته مهم: خود تابع رو برگردون، نه نتیجه فراخوانی اون رو.


def ساخت_پیام_ساز(پیشوند):
    def پیام_بده(متن):
        return f"{پیشوند} {متن}"
    return پیام_بده

گام ۴: استفاده از کلوژر

حالا می‌تونیم کارخانه‌های پیام‌ساز مختلفی با پیشوندهای متفاوت بسازیم و ازشون استفاده کنیم:


خوش_آمدید_گو = ساخت_پیام_ساز("سلام!")
خداحافظی_گو = ساخت_پیام_ساز("خداحافظ.")

print(خوش_آمدید_گو("به سایت ما خوش آمدید.")) # خروجی: سلام! به سایت ما خوش آمدید.
print(خداحافظی_گو("دوباره می‌بینمتون.")) # خروجی: خداحافظ. دوباره می‌بینمتون.

پرسش‌های متداول (FAQ)

سوالات رایج درباره کلوژر

سوال: آیا کلوژرها فقط برای توابع استفاده می‌شوند؟

پاسخ: بله، کلوژرها همواره یک تابع هستند (تابع درونی) که به متغیرهای یک تابع بیرونی دسترسی دارند. این ویژگی مختص توابع است.

سوال: تفاوت کلوژر با تابع عادی چیه؟

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

سوال: آیا استفاده از کلوژر به جای کلاس همیشه بهتره؟

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

سوال: کلمه `nonlocal` دقیقا چه کاری انجام میده؟

پاسخ: `nonlocal` به پایتون می‌گه که متغیری که داری بهش ارجاع می‌دی، نه متغیر محلی این تابع درونیه و نه متغیر گلوبال. بلکه متعلق به دامنه تابع بیرونی (enclosing scope) هست و قصد داری مقدار اون متغیر رو در همون دامنه تغییر بدی.

عیب‌یابی سریع: مشکلات رایج در کلوژرها و راه‌حل‌ها

گاهی اوقات با کلوژرها به مشکل می‌خوریم. بیا چندتا از رایج‌ترین مشکلات و راه حل‌هاشون رو مرور کنیم تا دیگه بهشون برنخوری:

مشکل ۱: همه کلوژرهای من آخرین مقدار متغیر لوپ رو برمی‌گردونن!

سناریو: تو حلقه‌ای چند تا کلوژر می‌سازی، اما وقتی هر کدوم رو فراخوانی می‌کنی، همشون یه نتیجه رو نشون میدن (معمولاً آخرین مقدار متغیر حلقه).

علت: این همون مشکل “Late Binding” هست که قبلاً توضیح دادیم. کلوژرها به متغیر ارجاع میدن، نه به مقدار اون در زمان تعریف.

راه‌حل: از مقدار پیش‌فرض برای تابع درونی استفاده کن تا مقدار متغیر رو “قفل” کنی. مثل این:


توابع_درست = []
for i in range(5):
    توابع_درست.append(lambda x=i: x) # مقدار i در لحظه به x اختصاص می‌یابد

for f in توابع_درست:
    print(f()) # خروجی: 0, 1, 2, 3, 4

مشکل ۲: نمی‌تونم متغیر غیرمحلی رو تغییر بدم، فقط می‌تونم بخونمش!

سناریو: یه کلوژر نوشتی، اما وقتی سعی می‌کنی متغیر تابع بیرونی رو داخل تابع درونی تغییر بدی، پایتون بهت ارور `UnboundLocalError` میده یا یه متغیر محلی جدید با همون اسم می‌سازه.

علت: پایتون به طور پیش‌فرض، اگه داخل یه تابع به متغیری مقدار بدی (مثلا `x = 5`)، اون رو به عنوان متغیر محلی اون تابع در نظر می‌گیره. برای اینکه به پایتون بگی داری متغیر دامنه بیرونی رو تغییر میدی، باید از `nonlocal` استفاده کنی.

راه‌حل: قبل از تغییر متغیر، `nonlocal` رو به این شکل استفاده کن:


def تست_nonlocal():
    x = 0
    def اینر():
        # اگه 'nonlocal x' رو نذاری، x جدیدی تعریف میشه
        nonlocal x
        x += 1
        return x
    return اینر

افزایش = تست_nonlocal()
print(افزایش()) # خروجی: 1
print(افزایش()) # خروجی: 2

بهترین کدها و ابزارهای برنامه‌نویسی رو می‌خوای؟

یه سری کدهای آماده و اسنیپت‌های خفن برات آماده کردیم که می‌تونه سرعت پیاده‌سازی پروژه‌هات رو چند برابر کنه.
از اسنیپت‌های پایتون گرفته تا CSS، جاوااسکریپت و وردپرس.

برای هر گونه سوال یا نیاز به پشتیبانی، با ما تماس بگیر:
09202232789

Table of Contents

آخرین نوشته‌ها