تابع در برنامهنویسی به یک قطعه کد با نام مشخص گفته میشود که تنها زمانی اجرا میشود که فراخوانی (صدا زده) شود. به کمک توابع میتوان یک برنامه را به بخشهای کوچکتر و قابل مدیریت تقسیم کرد و از تکرار کد جلوگیری نمود. در این مقاله، بصورت مرحلهبهمرحله با مفهوم توابع در زبان پایتون آشنا میشویم. این آموزش برای افراد مبتدی مفید است و در عین حال نکات کاربردی برای برنامهنویسان حرفهای نیز ارائه میکند.
تعریف تابع با def
در پایتون برای تعریف یک تابع جدید از کلمهکلیدی def استفاده میکنیم. به طور کلی ساختار یک تابع به شکل زیر است:
def function_name(parameter1, parameter2, ...):
"""Docstring اختیاری: توضیح درباره کارکرد تابع"""
# بدنهی تابع
# ...
return result # خروجی اختیاری
نکات کلیدی در تعریف تابع:
- پس از def نام تابع و جفت پرانتز () میآید. داخل پرانتز میتوان لیستی از پارامترها (parameters) را تعریف کرد. اگر تابع ورودی نگیرد، پرانتزها را خالی میگذاریم.
- بدنهی تابع با تورفتگی (indentation) مشخص میشود. تمام خطوط مربوط به بدنه باید یک پله تو رفتگی (مثلاً ۴ فاصله) نسبت به خط تعریف تابع داشته باشند
- اولین خط داخل بدنه میتواند یک رشته از توضیحات یا docstring باشد (اختیاری) که توضیح مختصری دربارهی وظیفهی تابع میدهد.
- تابع میتواند با دستور return مقداری را به عنوان خروجی برگرداند. اگر return صریحاً ذکر نشود، پس از اتمام بدنه، تابع به طور ضمنی None برمیگرداند.
به عنوان مثال، یک تابع ساده تعریف میکنیم که یک عدد را دو برابر میکند:
def double(x):
"""این تابع عدد ایکس را دو برابر میکند."""
result = x * 2
return result
در اینجا نام تابع double است، یک پارامتر x میگیرد و نتیجهی x * 2 را برمیگرداند. اکنون هر زمان که نیاز به دو برابر کردن عددی داشتیم، میتوانیم این تابع را صدا بزنیم به جای آنکه منطق را دوباره بنویسیم
y = double(5) # برابر ۱۰ خواهد شدy
print(y) # خروجی: 10
ورودی و خروجی توابع پایتون
مشخص کردهایم. هنگام صدا زدن تابع، باید به ازای هر پارامتر یک آرگومان (argument) معین به تابع بدهیم (مگر آنکه برای آن پارامتر مقدار پیشفرض تعیین شده باشد که در بخش بعد توضیح داده میشود). خروجی تابع نیز مقداری است که تابع با دستور return برمیگرداند.
برای مثال، تابع double که در بالا تعریف کردیم یک ورودی x میگیرد و خروجی آن مقدار دوبرابرشدهی x است. اگر تابعی هیچ مقدار خروجی مشخصی نداشته باشد و از return استفاده نکند، پایتون به طور پیشفرض مقدار ویژه None (هیچ) را برمیگرداند. به طور کلی، میتوان گفت هر تابع در پایتون حتی اگر صراحتاً مقداری برنگرداند، در نهایت یک مقدار (که همان None است) برمیگرداند.
مثال دیگر: تابعی مینویسیم که دو رشته متن را به هم متصل کرده و نتیجه را برمیگرداند
def concatenate(str1, str2):
combined = str1 + " " + str2
return combined
result = concatenate("Hello", "World")
print(result) # خروجی: Hello World
در این مثال، تابع concatenate دو ورودی (str1 و str2) دریافت میکند و خروجی آن ترکیب این دو رشته است.
تفاوت بین پارامتر و آرگومان
اغلب کلمات پارامتر و آرگومان به جای یکدیگر استفاده میشوند، اما تفاوت ظریفی بین آنها وجود دارد که دانستن آن خالی از لطف نیست.
به طور خلاصه:
- پارامتر متغیری است که در تعریف تابع ذکر میشود و نقش ظرفی را دارد که هنگام فراخوانی، مقداردهی خواهد شد.
- آرگومان مقداری است که هنگام فراخوانی تابع، به آن پاس داده میشود تا در پارامتر مربوطه قرار گیرد.
به عبارت دیگر، پارامترها در زمان تعریف تابع مشخص میشوند، در حالی که آرگومانها در زمان فراخوانی تابع تعیین میشوند.
به مثال زیر توجه کنید:
def add(a, b): # a و b پارامترهای تابع هستند
return a + b
result = add(5, 4) # 5 و 4 آرگومانهایی هستند که به تابع پاس دادهایم
print(result) # خروجی: 9
در اینجا a و b پارامترهای تابع add هستند (در زمان تعریف تابع مشخص شدهاند) و 5 و 4 آرگومانهایی هستند که هنگام فراخوانی add(5, 4) به تابع داده شدهاند.
نکتهی تکمیلی: در زبانهای برنامهنویسی ایستا مثل C/C++ باید نوع دادهٔ پارامترها مشخص شود، اما پایتون یک زبان پویا است و نیازی به اعلان نوع پارامترها نیست، این انعطاف موجب میشود تابع بتواند با انواع مختلف دادهها کار کند، هرچند که مسئولیت اطمینان از نوع صحیح آرگومانها بر عهدهٔ برنامهنویس است (مثلاً میتوان در داخل تابع از تابع کمکی isinstance برای بررسی نوع داده بهره برد.
مقدار پیشفرض(default) برای پارامترها
در پایتون امکان تعریف مقدار پیشفرض برای پارامترهای تابع وجود دارد. با این کار، اگر در زمان فراخوانی تابع برای آن پارامتر آرگومانی ارائه نشود، مقدار پیشفرض به کار میرود
تعریف پارامتر پیشفرض به این صورت است که در تعریف تابع، پس از نام پارامتر علامت = و سپس مقدار پیشفرض را قرار میدهیم:
def greet(name, message="سلام"):
print(f"{message}, {name}!")
در این مثال، پارامتر message دارای مقدار پیشفرض “سلام” است. بنابراین میتوان تابع greet را به دو صورت صدا زد:
greet("علی") # خروجی: سلام, علی!
greet("صبحت بخیر" , "علی") # خروجی: صبحت بخیر, علی!
“صبحت بخیر” جایگزین مقدار پیشفرض شده است.مزیت پارامتر پیشفرض این است که تابع را انعطافپذیرتر میکند و امکان فراخوانی آن با آرگومانهای کمتر از تعداد پارامترها را فراهم میسازد.
با این حال، هنگام استفاده از مقادیر پیشفرض باید به یک نکته مهم توجه کنیم:
نکته مهم: مقدار پیشفرض پارامترها تنها یک بار، در زمان تعریف تابع ارزیابی (محاسبه) میشود نه هر بار اجرای تابع .
به این معنی که اگر یک شیء قابل تغییر (مانند لیست یا دیکشنری) به عنوان مقدار پیشفرض استفاده کنیم، آن شیء بین همهٔ فراخوانیهای تابع به اشتراک گذاشته میشود. این میتواند منجر به رفتارهای پیشبینینشده شود. به کد زیر توجه کنید
def add_to_list(element, my_list=[]):
my_list.append(element)
return my_list
print(add_to_list(1)) # خروجی: [1]
print(add_to_list(2)) # خروجی: [1, 2] – انتظار [2] داشتیم!
print(add_to_list(3)) # خروجی: [1, 2, 3]
شاید انتظار داشته باشید هر بار که تابع add_to_list را صدا میزنیم، اگر آرگومان لیستی ندادهایم یک لیست جدید ساخته شود. اما چون مقدار پیشفرض my_list یک لیست خالی است که فقط یکبار (هنگام تعریف تابع) ایجاد میشود، هر بار که تابع را بدون مشخص کردن آن آرگومان صدا میکنیم، عملیات append بر روی همان لیست انجام میگیرد به همین دلیل در بالا پس از چند بار فراخوانی، لیست پیشفرض همهٔ مقادیر افزودهشده را نگه داشته است
راه حل این مسئله آن است که به جای استفاده مستقیم از یک شیء قابل تغییر به عنوان پیشفرض، از None به عنوان مقدار پیشفرض استفاده کنیم و سپس داخل تابع شیء جدید ایجاد نماییم
def add_to_list(element, my_list=None):
if my_list is None:
my_list = []
my_list.append(element)
return my_list
اکنون با این تغییر، هر بار که تابع بدون آرگومان دوم فراخوانی شود، یک لیست تازه برای آن فراخوانی ساخته میشود و مشکل برطرف خواهد شد
توابع بازگشتی (Recursive Functions) در پایتون
یکی از مفاهیم مهم در توابع، قابلیت بازگشت (Recursion) است. تابع بازگشتی تابعی است که در بدنهی خود، خودش را فراخوانی میکند.
به عبارت دیگر، این تابع مسئلهای را با تقسیم به نمونهٔ کوچکتر خود مسئله حل میکند. هر تابع بازگشتی باید یک شرط پایانی (شرط پایه) داشته باشد تا در یک نقطه متوقف شود؛ در غیر این صورت، تابع به طور نامحدود خودش را صدا میزند و در نهایت با خطای پر شدن پشته (Stack Overflow) یا رسیدن به حداکثر عمق بازگشت در پایتون مواجه خواهیم شد.
تصویر شماتیک از یک تابع بازگشتی که خود را فراخوانی میکند.
در این دیاگرام، فلشها نشان میدهند که تابع ()recurse درون بدنه خود دوباره ()recurse را صدا میزند (پیکان آبی)
تابع بازگشتی فاکتوریل در پایتون
برای درک بهتر، مثال کلاسیک فاکتوریل را در نظر بگیرید (فاکتوریل چیست؟). فاکتوریل یک عدد طبیعی n (با نماد !n) برابر است با حاصلضرب همه اعداد ۱ تا n. میتوان فاکتوریل را به صورت بازگشتی تعریف کرد:
- شرط پایه: 1=!0 و 1 = !1 (فاکتوریل صفر یا یک، برابر ۱ است).
- فرمول بازگشتی: برای n! > 1: n! = n * (n-1)
تابع بازگشتی محاسبه فاکتوریل در پایتون به صورت زیر نوشته میشود
def factorial(n):
if n <= 1: # شرط پایه
return 1
else:
return n * factorial(n-1) # فراخوانی بازگشتی
print(factorial(5)) # محاسبه !5 که خروجی 120 خواهد داشت
زمان فراخوانی factorial(5) این اتفاق رخ میدهد:
- برای n=5 چون شرط پایه صدق نمیکند، باید 5 * factorial(4) محاسبه شود.
- برای محاسبه factorial(4)، تابع وارد سطح بازگشت بعدی میشود و نیاز به 4 * factorial(3) دارد.
- این روند ادامه پیدا میکند تا factorial(1) که شرط پایه است و مقدار 1 برمیگرداند.
- سپس محاسبات بازگشتی به ترتیب کامل میشوند: factorial(2) = 2 * 1 = 2، factorial(3) = 3 * 2 = 6، factorial(4) = 4 * 6 = 24 و در نهایت factorial(5) = 5 * 24 = 120.
این شیوه تفکر بازگشتی در ابتدا ممکن است پیچیده به نظر برسد، اما برای بسیاری از مسائل (به ویژه مسائل بازگشتی مانند پیمایش درختها، تقسیم مسئله به زیرمسئلههای مشابه و …) راهحلهای تمیز و کوتاهی ارائه میدهد. البته باید توجه داشت که پایتون به طور پیشفرض محدودیت عمق بازگشت دارد (معمولاً 1000 سطح) تا از بینهایت شدن و اشغال تمام حافظه جلوگیری کند
توابع لامبدا (Lambda) در پایتون
پایتون به ما امکان میدهد توابع کوچک و ساده را به صورت ناشناس (بدون نام) تعریف کنیم که به آنها توابع لامبدا یا lambda گفته میشود. برای تعریف یک تابع لامبدا از کلمهکلیدی lambda استفاده میشود. شکل کلی یک عبارت لامبدا چنین است:
lambda arguments: expression
ویژگیهای تابع لامبدا در پایتون عبارتاند از:
- یک تابع بدون نام است که در قالب یک عبارت (expression) نوشته میشود، نه یک بلوک کد چندخطی.
- میتواند هر تعداد آرگومان (ورودی) داشته باشد، اما فقط یک عبارت در بدنه آن میتواند قرار گیرد (نتیجه همان یک عبارت به عنوان خروجی برگردانده میشود).
- معمولاً برای انجام کارهای سریع و کوتاه به کار میرود و اغلب هنگام پاس دادن یک تابع به توابع مرتبه بالا (مثل map و filter) استفاده میشود.
- در واقع lambda نوعی تسهیل کننده (syntactic sugar) برای تعریف تابع است؛ هر چیزی که با lambda قابل انجام باشد، با def معمولی هم قابل انجام است، اما lambda کدنویسی را خلاصهتر میکند
به عنوان مثال، یک تابع لامبدا برای محاسبه مربع یک عدد به صورت زیر نوشته میشود:
square = lambda x: x**2
print(square(5)) # خروجی: 25
در اینجا یک شیء تابع ناشناس ساختیم و آن را به متغیر square نسبت دادیم. اکنون میتوانیم مانند توابع عادی، با square(5) آن را فراخوانی کنیم. این تابع لامبدا ورودی x را گرفته و نتیجه x2 (x به توان 2) را برمیگرداند.
یک نمونه ملموس و رایج از استفادهٔ لامبدا در پایتون، مرتب کردن لیست اشیائی (مثلاً کالاها) بر اساس یک ویژگی مشخص است. فرض کنید یک فهرست از کالاها با جزئیاتی مانند نام و قیمت دارید و میخواهید با مرتبسازی، ارزانترین تا گرانترین کالا را پیدا کنید
products = [
{"name": "Pen", "price": 3000},
{"name": "Notebook", "price": 15000},
{"name": "Pencil", "price": 2000},
{"name": "Marker", "price": 4000}
]
# مرتب کردن کالاها بر اساس قیمت با استفاده از تابع لامبدا
sorted_products = sorted(products, key=lambda item: item["price"])
print("لیست کالاها قبل از مرتبسازی:")
for p in products:
print(p)
print("\nلیست کالاها بعد از مرتبسازی بر اساس قیمت:")
for p in sorted_products:
print(p)
- از تابع داخلی sorted استفاده شده و به آرگومان key یک تابع لامبدا پاس دادهایم که معیار مرتبسازی را تعیین میکند (در اینجا item[“price”]).
- این کار موجب میشود کالاها از ارزانترین به گرانترین قیمت ردیف شوند، بدون اینکه خود کالا یا ساختار اصلی تغییر کند (فهرست products ثابت میماند و نتیجهٔ مرتبشده در sorted_products قرار میگیرد).
- نیازی به نوشتن تابع جداگانه با def برای مرتبسازی نیست؛ یک لامبدای کوچک کارمان را راه میاندازد.
توابع مرتبه بالا (Higher-Order Functions) و توابع داخلی map, filter, reduce
در پایتون، همانند بسیاری از زبانهای مدرن، توابع، اشیاء درجه یک (First Class Objects) هستند؛ به این معنا که میتوان آنها را در متغیر ذخیره کرد، به عنوان آرگومان به تابع دیگری فرستاد یا از تابع دیگر return شوند. این قابلیت زمینهساز مفهومی به نام توابع مرتبه بالا است.
تابع مرتبه بالا تابعی است که میتواند یک یا چند تابع را به عنوان آرگومان بپذیرد یا یک تابع را به عنوان خروجی بازگرداند.
این مفهوم از دنیای برنامهنویسی تابعی (functional programming) میآید و در پایتون به خوبی پشتیبانی میشود. به عبارتی دیگر، هر تابعی که با توابع کار کند (آنها را بگیرد یا return کند) یک تابع مرتبه بالا محسوب میشود.
پایتون چند تابع درونی بسیار پرکاربرد دارد که خود ماهیت مرتبه بالا دارند: یعنی توابع دیگر را به عنوان آرگومان میگیرند. سه مورد مشهورتر عبارتاند از: map, filter و reduce.
- تابع map: برای اعمال یک تابع بر روی تمامی عناصر یک مجموعه (iterable) استفاده میشود. map دو آرگومان میگیرد: یکی تابع و یکی یک مجموعه از موارد (مانند لیست یا تاپل). سپس آن تابع را بر روی تکتک عناصر iterable اعمال کرده و خروجی را به صورت یک آبجکت map (قابل تبدیل به لیست) برمیگرداند
به عنوان مثال:
numbers = [1, 2, 3, 4, 5]
# با استفاده از مپ همه اعداد را مربع میکنیم
squared_numbers = list(map(lambda x: x2, numbers))
print(squared_numbers) # خروجی: [1, 4, 9, 16, 25]
در اینجا تابع لامبدا lambda x: x2 بر روی هر عنصر لیست numbers اعمال شده است. نتیجه نهایی را با ()list به لیست تبدیل کردیم (زیرا map یک شیء قابل پیمایش برمیگرداند).
- تابع filter: برای فیلتر کردن عناصر یک iterable بر اساس یک شرط به کار میرود. این تابع دو آرگومان میگیرد: یک تابع شرط (بولی) و یک iterable. نتیجهی filter شامل آن عناصری از iterable اولیه است که تابع شرط برایشان True را برگرداند
مثال:
numbers = [1, 2, 3, 4, 5]
# انتخاب اعداد زوج از لیست با استفاده از فیلتر
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers) # خروجی: [2, 4]
در مثال بالا، لامبدا x % 2 == 0 تشخیص میدهد آیا هر عدد زوج است یا خیر؛ تابع filter تنها اعداد زوج لیست را در خروجی نگه میدارد.
Closure و محدودهی متغیرها (Scope)
وقتی در پایتون یک تابع داخل تابع دیگر تعریف میکنیم (تابع تودرتو)، تابع داخلی میتواند به متغیرهای موجود در حوزهی تابع بیرونی دسترسی داشته باشد. به ترکیب «تابع داخلی + متغیرهای محلی محفوظشده از تابع enclosing (بیرونی)» اصطلاحاً Closure گفته میشود
Closure این امکان را میدهد که تابع داخلی حتی پس از اتمام اجرای تابع خارجی، همچنان به آن متغیرهای محلی دسترسی داشته باشد و آنها را به خاطر داشته باشد
def discount_creator(discount_rate):
def apply_discount(price):
return price * (1 - discount_rate)
return apply_discount
# ایجاد تابع تخفیف 20 درصدی
apply_20_percent = discount_creator(0.2)
print(apply_20_percent(100)) # خروجی: 80
print(apply_20_percent(250)) # خروجی: 200
تابع discount_creator یک نرخ تخفیف (مثلاً 0.2 برای 20 درصد) میگیرد.
داخل آن، تابع apply_discount تعریف میشود که یک قیمت را میگیرد و قیمت نهایی بعد از اعمال تخفیف را محاسبه میکند.
وقتی discount_creator(0.2) را صدا میزنیم، مقدار 0.2 برای discount_rate ذخیره شده و تابع apply_discount به عنوان Closure بازگردانده میشود.
در نتیجه، متغیر apply_20_percent حالا یک تابع است که همیشه 20 درصد تخفیف را روی هر قیمتی اعمال میکند؛ به همین دلیل apply_20_percent(100) برابر با 80 و apply_20_percent(250) برابر با 200 میشود.
دکوراتورها (Decorators) در پایتون
دکوراتورها در پایتون روشی ساده برای تغییر یا گسترش عملکرد یک تابع بدون دست زدن به کد اصلی آن هستند. به زبان ساده، یک دکوراتور تابعی است که یک تابع دیگر را میگیرد و یک تابع جدید ایجاد میکند. این تابع جدید میتواند قبل یا بعد از اجرای تابع اصلی، کارهای اضافهای مانند چاپ پیام، ثبت log یا اشکالزدایی انجام دهد و سپس نتیجه نهایی تابع اصلی (یا نتیجه تغییر یافته) را برگرداند.
در این مثال یک دکوراتور ساده به نام require_admin تعریف میکنیم که قبل از اجرای تابع، بررسی میکند آیا کاربر دارای دسترسی ادمین است یا خیر. در اینجا اطلاعات کاربر در یک دیکشنری بصورت global ذخیره شده است:
# تعریف کاربر جاری (در اینجا کاربر عادی است)
current_user = {"name": "علی", "is_admin": False}
def require_admin(func):
def wrapper(*args, **kwargs):
# بررسی دسترسی ادمین
if not current_user.get("is_admin", False):
print("خطا: کاربر دسترسی کافی ندارد!")
return None
# در صورت داشتن دسترسی، تابع اصلی اجرا میشود
return func(*args, **kwargs)
return wrapper
@require_admin
def access_secret_area():
print("به بخش محرمانه خوش آمدید.")
# تلاش برای دسترسی به بخش محرمانه
access_secret_area()
# تغییر دسترسی کاربر به ادمین
current_user["is_admin"] = True
access_secret_area()
- ثبت فعالیت (Logging) و اشکالزدایی:دکوراتورها میتوانند قبل یا بعد از اجرای یک تابع، اطلاعاتی مانند نام تابع، آرگومانها یا زمان اجرا را چاپ و ذخیره کنند.
- اعتبارسنجی و کنترل دسترسی:با استفاده از دکوراتورها میتوان قبل از اجرای تابع بررسی کرد که آیا کاربر یا دادههای ورودی شرایط لازم را دارند یا خیر (مثلاً بررسی دسترسی ادمین).
- بهبود کارایی و مدیریت خطا:دکوراتورها میتوانند عملکرد تابع را بهینه کنند؛ مثلاً با ذخیره نتایج (caching) یا اجرای مجدد تابع در صورت بروز خطا (retry mechanism).







