FA-TOOLS — Header Component
آموزش functools در پایتون — partial، reduce، wraps

آموزش functools در پایتون — partial، reduce، wraps

رفیق برنامه‌نویس! تا حالا شده تو کد پایتون حس کنی یه چیزی کمه؟ یه ابزار که کار با توابع رو برات آسون‌تر و کدهات رو خواناتر کنه؟ اینجاست که ماژول functools وارد میشه و یه عالمه جادو به کدنویسیت اضافه می‌کنه. اگه میخوای کدهات رو تمیزتر، ماژولارتر و حرفه‌ای‌تر بنویسی، خوندن این مقاله رو از دست نده. ما اینجا قراره باهم سه تا از قدرتمندترین ابزارهای این ماژول یعنی partial، reduce و wraps رو موشکافی کنیم. آماده‌ای برای یه سفر جذاب تو دنیای پایتون؟ضمناً، برای اینکه ابزارهای برنامه‌نویسی‌ات همیشه به‌روز و کاربردی باشه، حتماً یه سری به
فروشگاه ابزارهای برنامه‌نویسی ما بزن.
یه عالمه اسنیپت و ابزار باحال پایتون و بقیه زبان‌ها اونجا منتظرته!

🎨 نقشه راه: ماژول functools در یک نگاه

آموزش functools در پایتون — partial، reduce، wraps — تصویر 1

🌟 functools.partial

  • چیست؟ ساخت نسخه جدیدی از یک تابع با آرگومان‌های پیش‌فرض ثابت.
  • 🚀 کاربرد: کاهش تکرار، ساخت توابع تخصصی، استفاده در Callbacks.
  • 💡 مثال ذهنی: یه تابع چایی‌ساز داری، با partial یه چایی‌ساز اختصاصی برای “چایی سبز با شکر کم” می‌سازی.

📉 functools.reduce

  • چیست؟ اعمال یک تابع روی عناصر یک مجموعه به صورت تکراری تا رسیدن به یک نتیجه واحد.
  • 🚀 کاربرد: جمع، ضرب، یافتن حداکثر/حداقل یا هر نوع تجمیع.
  • 💡 مثال ذهنی: یه سبد میوه داری، reduce وزن همه میوه‌ها رو جمع می‌کنه و یه عدد نهایی بهت میده.

🎭 functools.wraps

  • چیست؟ حفظ متادیتای تابع اصلی (مثل اسم، مستندات) هنگام استفاده از Decoratorها.
  • 🚀 کاربرد: نوشتن Decoratorهای “صحیح” و قابل دیباگ.
  • 💡 مثال ذهنی: تو داری یه لباسی (Decorator) روی یه آدم (تابع) می‌پوشی، wraps باعث میشه هویت و اسم اون آدم یادمون نره.

دنیای functools.partial: توابع اختصاصی بساز!

آموزش functools در پایتون — partial، reduce، wraps — تصویر 2

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

partial چیه و چرا لازمه؟

partial از اسمش پیداست: “جزئی”. این تابع به ما اجازه میده یه نسخه “جزئی” از یه تابع موجود بسازیم. یعنی چی؟ یعنی میتونیم بعضی از آرگومان‌های یه تابع رو از قبل براش ست کنیم و یه تابع جدید بگیریم که فقط آرگومان‌های باقی‌مونده رو انتظار داره. این کار باعث میشه کدمون هم تمیزتر بشه و هم از تکرار کد جلوگیری کنیم. دیگه لازم نیست برای هر سناریو یه تابع جدید بنویسی، فقط کافیه یه partial از تابع اصلیت بسازی.

نحوه استفاده از partial

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

from functools import partial

def send_message(sender, receiver, message, msg_type="email"):
    """یک پیام را ارسال می‌کند."""
    print(f"Sending {msg_type} from {sender} to {receiver}: {message}")

# ساخت یک تابع جزئی برای ارسال پیامک
send_sms_from_system = partial(send_message, sender="System", msg_type="sms")

# حالا می‌تونیم از تابع جدیدمون استفاده کنیم
send_sms_from_system(receiver="09123456789", message="Your order is confirmed.")
send_sms_from_system(receiver="09301234567", message="OTP: 123456")

# خروجی:
# Sending sms from System to 09123456789: Your order is confirmed.
# Sending sms from System to 09301234567: OTP: 123456

function copyCode(buttonElement) {
const codeBlock = buttonElement.nextElementSibling.querySelector(‘code’);
const range = document.createRange();
range.selectNode(codeBlock);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
document.execCommand(‘copy’);
window.getSelection().removeAllRanges();
buttonElement.textContent = ‘کپی شد!’;
setTimeout(() => {
buttonElement.textContent = ‘کپی کد’;
}, 2000);
}

ببین چقدر راحت یه تابع جدید و اختصاصی ساختیم! partial نه تنها برای آرگومان‌های کلیدواژه‌ای (kwargs) کار می‌کنه، بلکه میتونه آرگومان‌های موقعیتی (args) رو هم فیکس کنه.

کاربردهای partial در دنیای واقعی

  • GUI Event Handlers: تو رابط‌های کاربری گرافیکی، وقتی یه دکمه رو فشار میدی، یه تابع کال میشه. اگه بخوای همون تابع رو با آرگومان‌های متفاوت برای دکمه‌های مختلف استفاده کنی، partial نجات‌بخش میشه.
  • Default Arguments: اگه یه تابع کتابخانه‌ای داری که آرگومان‌های زیادی داره و بیشتر مواقع میخوای از یه سری مقادیر پیش‌فرض استفاده کنی، با partial میتونی یه نسخه با defaults خودت بسازی.
  • Lambda Functions جایگزین: بعضی وقت‌ها میشه به جای partial از lambda هم استفاده کرد، اما برای توابع پیچیده‌تر، partial خوانایی بهتری داره و کارش رو مشخص‌تر بیان می‌کنه.
نکته کاربردی: استفاده از partial در کنار توابعی مثل map و filter هم خیلی کارآمده. برای مثال، می‌تونی یه تابع partial بسازی که فقط اعداد بزرگتر از یه عدد خاص رو فیلتر کنه و بعد با filter ازش استفاده کنی. برای یادگیری بیشتر در مورد اسنیپت‌های پایتون،
اینجا می‌تونی چیزهای جالبی پیدا کنی.

جادوی functools.reduce: از یک لیست به یک نتیجه

آموزش functools در پایتون — partial، reduce، wraps — تصویر 3

حالا بریم سراغ reduce. این تابع وقتی به کارت میاد که بخوای یه لیست از مقادیر رو به یه مقدار واحد تبدیل کنی. مثلاً جمع تمام اعداد یک لیست، یا پیدا کردن بزرگترین عدد، یا حتی ترکیب کردن چند رشته به هم. اسمش رو از مفهوم “Reduction” در برنامه‌نویسی تابعی (Functional Programming) گرفته.

reduce چطور کار می‌کنه؟

reduce دو تا آرگومان اصلی می‌گیره: یه تابع (که بهش میگن reducer) و یه iterable (مثلاً لیست یا تاپل). این تابع reducer خودش دو تا آرگومان می‌گیره: accumulator (که نتیجه موقت رو نگه می‌داره) و current_item (عنصر فعلی از iterable). reduce از اولین و دومین عنصر iterable شروع می‌کنه، تابع reducer رو روشون اعمال می‌کنه، نتیجه رو نگه می‌داره (accumulator)، بعد این نتیجه رو با عنصر بعدی لیست ترکیب می‌کنه و همینطور ادامه میده تا به انتهای لیست برسه.

from functools import reduce

def add(x, y):
    "تابعی برای جمع دو عدد"
    return x + y

numbers = [1, 2, 3, 4, 5]

# جمع کردن تمام اعداد لیست با reduce
total_sum = reduce(add, numbers)
print(f"The sum is: {total_sum}") # خروجی: The sum is: 15

# پیدا کردن بزرگترین عدد (می‌تونیم از lambda هم استفاده کنیم)
max_number = reduce(lambda a, b: max(a, b), numbers)
print(f"The maximum number is: {max_number}") # خروجی: The maximum number is: 5

# reduce با مقدار اولیه (initial value)
# این مقدار به عنوان اولین accumulator استفاده می‌شود و تابع از اولین عنصر لیست شروع می‌کند.
sum_with_initial = reduce(add, numbers, 100)
print(f"Sum with initial value 100: {sum_with_initial}") # خروجی: Sum with initial value 100: 115

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

با اینکه reduce قدرتمنده، اما در پایتون برای عملیات ساده‌ای مثل جمع زدن، توابع داخلی مثل sum() یا max() خواناتر و سریع‌تر هستن. reduce زمانی مفیدتره که:

  • عملیات تجمیع شما خیلی خاص و پیچیده‌ست و تابع داخلی براش نداریم.
  • میخوای یه رویکرد برنامه‌نویسی تابعی رو دنبال کنی.
  • داری با یه زبان برنامه‌نویسی دیگه کار می‌کنی که reduce (یا fold) توش کاربرد گسترده‌ای داره و میخوای کدهات شبیه به اون زبان باشه.
هشدار! استفاده از reduce برای کارهای ساده‌ای مثل جمع زدن یه لیست، اغلب باعث میشه کد کمتر خوانا و پیچیده‌تر به نظر بیاد. همیشه قبل از استفاده از reduce، ببین راه ساده‌تری مثل sum()، max() یا یک حلقه ساده وجود نداره؟ اگر به دنبال اسنیپت‌های HTML هستید،
اینجا را از دست ندهید.

نقش functools.wraps در Decoratorها: حفظ هویت توابع

خب، رسیدیم به یکی از مهم‌ترین و شاید کمی پیچیده‌ترین بخش‌های functools: wraps. اگه با Decoratorها تو پایتون کار کرده باشی، حتماً میدونی که چقدر می‌تونن مفید باشن برای اضافه کردن قابلیت‌های بیشتر به توابع بدون دستکاری کد اصلیشون. اما یه مشکل کوچیک وجود داره…

مشکل Decoratorهای بدون wraps

وقتی یه Decorator روی یه تابع اعمال می‌کنی، در واقع یه تابع جدید می‌سازی که تابع اصلی رو در خودش می‌پیچونه. نتیجه این کار اینه که متادیتای تابع اصلی (مثل اسم (__name__)، داکسترینگ (__doc__)، و ماژول (__module__)) از بین میره و با متادیتای تابع “درون” Decorator جایگزین میشه. این می‌تونه موقع دیباگ کردن، نوشتن داکیومنت، یا استفاده از ابزارهای introspection (که اطلاعات توابع رو تحلیل می‌کنن) دردسرساز بشه.

def my_simple_decorator(func):
    def wrapper(*args, **kwargs):
        print("Something is happening before the function is called.")
        result = func(*args, **kwargs)
        print("Something is happening after the function is called.")
        return result
    return wrapper

@my_simple_decorator
def say_hello(name):
    """این تابع به شما سلام می‌کند."""
    return f"Hello, {name}!"

print(f"Function name: {say_hello.__name__}")
print(f"Function doc: {say_hello.__doc__}")

# خروجی (بدون wraps):
# Function name: wrapper
# Function doc: None
# می‌بینید که اسم و داکسترینگ تابع اصلی از دست رفته!

wraps میاد مشکل رو حل کنه!

اینجاست که functools.wraps وارد میشه و یه راه حل شیک و پخته برای این مشکل ارائه میده. wraps خودش یه decorator هست که وقتی روی تابع wrapper اعمالش می‌کنی، متادیتای تابع اصلی رو به تابع wrapper کپی می‌کنه و به این ترتیب، تابع دکوریتور شده، تمام خصوصیات تابع اصلی رو از نظر متادیتا حفظ می‌کنه.

from functools import wraps

def my_safe_decorator(func):
    @wraps(func) # اینجا از wraps استفاده می‌کنیم!
    def wrapper(*args, **kwargs):
        print("Something is happening before the function is called.")
        result = func(*args, **kwargs)
        print("Something is happening after the function is called.")
        return result
    return wrapper

@my_safe_decorator
def say_hello_again(name):
    """این تابع به شما دوباره سلام می‌کند."""
    return f"Hello again, {name}!"

print(f"Function name: {say_hello_again.__name__}")
print(f"Function doc: {say_hello_again.__doc__}")

# خروجی (با wraps):
# Function name: say_hello_again
# Function doc: این تابع به شما دوباره سلام می‌کند.
# حالا تمام متادیتای تابع اصلی حفظ شده!

همونطور که دیدی، با اضافه کردن فقط یک خط @wraps(func)، متادیتای اصلی تابع برگشت. این کار نه تنها به خوانایی کد کمک می‌کنه، بلکه ابزارهایی مثل help() یا دیباگرها رو قادر می‌سازه تا اطلاعات دقیق‌تری از تابع اصلی بهت بدن. این مورد، یک ریزه‌کاری بسیار مهم و حرفه‌ای در نوشتن Decoratorهاست که نباید نادیده گرفته بشه.

چرا wraps حیاتیه؟

  • خوانایی و دیباگ: وقتی stack trace رو میبینی، اسم واقعی تابع رو میفهمی، نه اسم wrapper.
  • داکیومنتیشن: help() روی تابع دکوریتور شده، داکسترینگ درست رو نشون میده.
  • ابزارهای توسعه: IDEها و ابزارهای تحلیلی کد بهتر می‌تونن تابع شما رو درک کنن.

برای یادگیری اسنیپت‌های CSS، یه سری به
این بخش بزن، و اگه دنبال اسنیپت‌های JavaScript می‌گردی،
اینجا رو حتماً ببین. همینطور، اسنیپت‌های وردپرس هم
اینجا منتظر شما هستند.

مقایسه کاربردها: partial، reduce، wraps

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

قابلیت توضیحات و کاربرد اصلی
functools.partial این تابع به شما اجازه می‌دهد تا نسخه‌ای جدید از یک تابع موجود بسازید که بعضی از آرگومان‌های آن از قبل ثابت شده‌اند. کاربرد اصلی آن برای ساده‌سازی فراخوانی توابع با آرگومان‌های تکراری، Currying (برنامه‌نویسی تابعی) و ساخت Callbacks است.
functools.reduce یک تابع را به صورت تکراری روی عناصر یک Iterable اعمال می‌کند تا به یک نتیجه واحد نهایی برسد (یک لیست را به یک مقدار کاهش می‌دهد). برای عملیات تجمیع مانند جمع، ضرب، یافتن حداکثر/حداقل یا ترکیب پیچیده‌تر عناصر استفاده می‌شود. نیاز به ایمپورت جداگانه دارد: from functools import reduce.
functools.wraps یک Decorator است که متادیتای تابع اصلی (مثل نام، داکسترینگ) را هنگام استفاده از Decoratorها حفظ می‌کند. این کار به دیباگ، داکیومنتیشن و ابزارهای introspection کمک می‌کند تا تابع دکوریتور شده را به درستی شناسایی کنند و از از بین رفتن اطلاعات مهم جلوگیری می‌کند.

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

عیب‌یابی سریع (Troubleshooting): مشکلات رایج و راه حل‌ها

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

مشکل با partial: آرگومان‌های اشتباه

  • سناریو: یه تابع partial ساختی، اما وقتی کالش می‌کنی، ارور “TypeError: missing a required argument” میگیری.
  • دلیل: احتمالاً آرگومان‌های موقعیتی و کلیدواژه‌ای رو درست تنظیم نکردی، یا یه آرگومان ضروری رو از قلم انداختی. partial اول آرگومان‌های موقعیتی رو پر می‌کنه، بعد کلیدواژه‌ای. اگه آرگومان‌های موقعیتی رو از قبل پر کردی، بقیه آرگومان‌های موقعیتی رو باید موقع فراخوانی تابع partial بدی.
  • راه حل:
    1. مطمئن شو که تابع اصلی رو با آرگومان‌های درست به partial پاس دادی.
    2. اگه از آرگومان‌های موقعیتی استفاده می‌کنی، ترتیبشون خیلی مهمه.
    3. برای دیباگ، می‌تونی your_partial_func.func (تابع اصلی) و your_partial_func.args و your_partial_func.keywords رو پرینت بگیری تا ببینی چی به چیه.

مشکل با reduce: لیست خالی یا نبود مقدار اولیه

  • سناریو: داری reduce رو روی یه لیست خالی اعمال می‌کنی و ارور “TypeError: reduce() of empty sequence with no initial value” میگیری.
  • دلیل: reduce نیاز به حداقل یک عنصر داره تا شروع به کار کنه (یا دو عنصر اگه مقدار اولیه ندی).
  • راه حل:
    1. همیشه یه مقدار اولیه (initial value) به reduce بده. این مقدار، به عنوان اولین accumulator استفاده میشه.
      مثال: reduce(add, [], 0) خروجی 0 خواهد بود.
    2. قبل از فراخوانی reduce، چک کن که iterable خالی نباشه.

مشکل با wraps: فراموشی استفاده

  • سناریو: یه Decorator نوشتی، ولی وقتی help() رو روی تابع دکوریتور شده میزنی، متادیتای تابع اصلی رو نشون نمیده.
  • دلیل: احتمالاً فراموش کردی @wraps(func) رو روی تابع wrapper اعمال کنی.
  • راه حل:
    1. حتماً from functools import wraps رو ایمپورت کن.
    2. درون Decorator، بلافاصله بالای تابع wrapper، @wraps(func) رو اضافه کن. یادت باشه func همون تابعیه که به Decorator پاس دادی.
یک نکته ظریف اما کاربردی: همیشه سعی کن کدهات رو تست کنی، مخصوصاً وقتی از توابع پیشرفته‌تر استفاده می‌کنی. نوشتن تست‌های واحد (Unit Tests) میتونه بهت کمک کنه خیلی زود مشکلات رو پیدا کنی و جلوی اتفاقات غیرمنتظره رو بگیری. هرگونه اسنیپت و کد آماده‌ای که نیاز داری رو می‌تونی در
این بخش پیدا کنی.

نتیجه‌گیری: کدنویسی حرفه‌ای با functools

ماژول functools تو پایتون، یه جعبه ابزار ارزشمند برای هر برنامه‌نویسی هست که میخواد کدهای تمیزتر، ماژولارتر و کارآمدتری بنویسه. با partial میتونی توابع تخصصی با آرگومان‌های ثابت بسازی، با reduce یک لیست از مقادیر رو به یه نتیجه واحد تبدیل کنی (البته با احتیاط و در مواقع لازم)، و با wraps هم به Decoratorهات جون ببخشی و هویت توابع اصلی رو حفظ کنی.

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

📞 نیاز به مشاوره یا کمک بیشتر داری؟

اگه سوالی داری یا نیاز به راهنمایی بیشتری برای پروژه‌های پایتونیت داری، میتونی با ما تماس بگیری!

تماس با ما: 09202232789

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

functools.partial دقیقاً چیکار می‌کنه؟

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

آیا همیشه باید از functools.reduce استفاده کنم؟

نه لزوماً. برای عملیات‌های ساده مثل جمع یا پیدا کردن بزرگترین عدد، توابع داخلی پایتون مثل sum() یا max() خواناتر و بهینه‌تر هستند. reduce بیشتر برای عملیات‌های تجمیع پیچیده یا در رویکردهای برنامه‌نویسی تابعی کاربرد داره.

چرا استفاده از functools.wraps در Decoratorها مهمه؟

wraps متادیتای تابع اصلی (مثل نام، داکسترینگ و ماژول) رو که با اعمال Decorator از بین میره، به تابع wrapper کپی می‌کنه. این کار باعث میشه تابع دکوریتور شده هنگام دیباگ، داکیومنتیشن و استفاده از ابزارهای introspection، هویت اصلیش رو حفظ کنه و مشکلات ناشی از تغییر نام و توضیحات رو حل می‌کنه.

Table of Contents

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