آرگومانهای *args و **kwargs در پایتون

🗓 آخرین بهروزرسانی:
*args و **kwargs به توابع پایتون اجازه میدهند تعداد متغیری از آرگومانهای موقعیتی و کلیدی را دریافت کنند. این قابلیت زمانی مفید است که هنگام تعریف تابع، تعداد دقیق ورودیها را نمیدانیم یا میخواهیم رابطی انعطافپذیر برای توابع عمومی، ابزارهای گزارشگیری و تنظیمات اختیاری بسازیم.
این مطلب، درس هفتم از دوره رایگان پایتون سطح متوسط است. در درس قبلی با محدوده متغیرها، Local و Global و قانون LEGB آشنا شدیم. اکنون یاد میگیریم چگونه توابعی طراحی کنیم که بتوانند تعداد نامحدودی ورودی دریافت کنند.
*args و **kwargs چه مشکلی را حل میکنند؟
فرض کنید تابعی برای جمعکردن اعداد داریم:
def add_three_numbers(a, b, c):
return a + b + cاین تابع فقط سه عدد میپذیرد. اگر بخواهیم دو، پنج یا ده عدد را جمع کنیم، باید تابع دیگری بسازیم یا ورودیها را در یک لیست قرار دهیم. با *args میتوان تعداد آرگومانهای موقعیتی را انعطافپذیر کرد:
def add_numbers(*args):
return sum(args)
print(add_numbers(10, 20))
print(add_numbers(10, 20, 30, 40))برای دریافت تعداد متغیری از آرگومانهای کلیدی نیز از **kwargs استفاده میکنیم.
معنای ستارهها در تعریف تابع
*آرگومانهای موقعیتی اضافی را جمعآوری میکند.**آرگومانهای کلیدی اضافی را جمعآوری میکند.- دادههای جمعآوریشده توسط
*argsداخل یک تاپل قرار میگیرند. - دادههای جمعآوریشده توسط
**kwargsداخل یک دیکشنری قرار میگیرند.
*args در پایتون چیست؟
*args برای دریافت تعداد نامشخصی از آرگومانهای موقعیتی استفاده میشود:
def show_items(*args):
print(args)
print(type(args))
show_items("Python", "Git", "SQL")خروجی:
('Python', 'Git', 'SQL')
<class 'tuple'>پایتون تمام آرگومانهای موقعیتی ارسالشده را داخل یک تاپل قرار میدهد.
نام args اجباری نیست
عامل اصلی ستاره است، نه نام args. کد زیر نیز معتبر است:
def show_names(*names):
for name in names:
print(name)با این حال استفاده از نام args یک قرارداد رایج است. وقتی نام تخصصیتری مانند numbers یا products خوانایی را بهتر میکند، میتوان از آن استفاده کرد:
def calculate_total(*prices):
return sum(prices)پیمایش آرگومانهای *args
def show_scores(*scores):
for score in scores:
print(score)
show_scores(18, 16, 20, 15)چون scores یک تاپل است، میتوان از len()، اندیسگذاری، برش و حلقه روی آن استفاده کرد.
دسترسی با اندیس در *args
def show_first_and_last(*items):
if not items:
return "هیچ مقداری ارسال نشده است"
return items[0], items[-1]
print(show_first_and_last("A", "B", "C"))فراخوانی تابع بدون آرگومان
اگر تابع فقط *args داشته باشد، میتوان آن را بدون آرگومان فراخوانی کرد. در این حالت یک تاپل خالی دریافت میشود:
def count_items(*args):
return len(args)
print(count_items())خروجی برابر صفر است.
مثال: محاسبه مجموع و میانگین با *args
def calculate_report(*numbers):
if not numbers:
return {
"count": 0,
"sum": 0,
"average": None
}
return {
"count": len(numbers),
"sum": sum(numbers),
"average": sum(numbers) / len(numbers)
}
print(calculate_report(10, 20, 30, 40))ترکیب پارامتر معمولی و *args
پارامترهای معمولی میتوانند پیش از *args قرار بگیرند:
def introduce(title, *names):
print(title)
for name in names:
print("-", name)
introduce("شرکتکنندگان دوره", "مهران", "سارا", "علی")آرگومان موقعیتی نخست به title اختصاص داده میشود و آرگومانهای باقیمانده داخل names قرار میگیرند.
پارامترهای پس از *args فقط کلیدی هستند
پارامترهایی که پس از *args تعریف میشوند، باید با نام ارسال شوند:
def calculate_total(*prices, discount=0):
total = sum(prices)
return total - (total * discount / 100)
print(calculate_total(100000, 250000, discount=10))در این مثال، discount یک پارامتر فقط کلیدی یا Keyword-only است.
چرا آرگومان کلیدی پس از *args لازم است؟
اگر مقدار تخفیف را بدون نام ارسال کنیم، پایتون آن را یکی از قیمتها در نظر میگیرد:
calculate_total(100000, 250000, 10)در این فراخوانی هر سه عدد داخل prices قرار میگیرند و مقدار پیشفرض تخفیف استفاده میشود. بنابراین برای پارامترهای پس از *args باید نام آرگومان نوشته شود.
بازکردن لیست و تاپل هنگام فراخوانی با *
ستاره فقط در تعریف تابع کاربرد ندارد. هنگام فراخوانی میتواند عناصر یک لیست یا تاپل را به آرگومانهای موقعیتی جداگانه تبدیل کند:
def add(a, b, c):
return a + b + c
numbers = [10, 20, 30]
print(add(*numbers))این فراخوانی معادل کد زیر است:
add(10, 20, 30)تعداد عناصر داده بازشده باید هماهنگ باشد
def create_point(x, y):
return (x, y)
values = [10, 20, 30]
create_point(*values)چون تابع دو پارامتر دارد اما لیست سه عنصر دارد، خطای TypeError ایجاد میشود.
ترکیب آرگومانهای مستقیم و داده بازشده
def show_values(a, b, c, d):
print(a, b, c, d)
more_values = [30, 40]
show_values(10, 20, *more_values)**kwargs در پایتون چیست؟
**kwargs تعداد نامشخصی از آرگومانهای کلیدی را دریافت میکند و آنها را در یک دیکشنری قرار میدهد:
def show_profile(**kwargs):
print(kwargs)
print(type(kwargs))
show_profile(name="مهران", city="استانبول", active=True)خروجی:
{'name': 'مهران', 'city': 'استانبول', 'active': True}
<class 'dict'>نام kwargs نیز اجباری نیست
مانند args، نام kwargs قراردادی است. عامل اصلی دو ستاره است:
def create_settings(**options):
return optionsپیمایش **kwargs
برای دریافت همزمان کلید و مقدار از متد items() استفاده میکنیم:
def show_details(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
show_details(name="سارا", age=27, city="تهران")دسترسی امن به مقدارهای kwargs
def create_user(**kwargs):
name = kwargs.get("name", "بدون نام")
city = kwargs.get("city", "ثبت نشده")
active = kwargs.get("active", True)
return {
"name": name,
"city": city,
"active": active
}
print(create_user(name="مهران", city="استانبول"))استفاده از get() مانع ایجاد KeyError برای گزینههای اختیاری میشود.
فراخوانی **kwargs بدون آرگومان
اگر هیچ آرگومان کلیدی ارسال نشود، دیکشنری خالی دریافت میشود:
def count_options(**kwargs):
return len(kwargs)
print(count_options())مثال: ساخت محصول با تنظیمات اختیاری
def create_product(name, price, **options):
product = {
"name": name,
"price": price
}
product.update(options)
return product
product = create_product(
"هدفون",
850000,
color="مشکی",
in_stock=True,
warranty=12
)
print(product)نام و قیمت الزامی هستند و بقیه ویژگیها بهصورت انعطافپذیر دریافت میشوند.
بازکردن دیکشنری هنگام فراخوانی با **
دو ستاره هنگام فراخوانی، جفتهای کلید–مقدار یک دیکشنری را به آرگومانهای کلیدی تبدیل میکند:
def introduce(name, city, age):
return f"{name}، {age} ساله، ساکن {city}"
user = {
"name": "مهران",
"city": "استانبول",
"age": 30
}
print(introduce(**user))کلیدهای دیکشنری باید با نام پارامترهای تابع هماهنگ باشند.
خطای کلید اضافی هنگام بازکردن دیکشنری
def introduce(name, city):
return f"{name} - {city}"
user = {
"name": "مهران",
"city": "استانبول",
"age": 30
}
introduce(**user)چون تابع پارامتری به نام age ندارد، خطای زیر ایجاد میشود:
TypeError: introduce() got an unexpected keyword argument 'age'حل کلیدهای اضافی با **kwargs
def introduce(name, city, **extra):
print(f"{name} - {city}")
print("اطلاعات اضافی:", extra)
introduce(**user)ترکیب *args و **kwargs در یک تابع
def inspect_inputs(*args, **kwargs):
print("آرگومانهای موقعیتی:", args)
print("آرگومانهای کلیدی:", kwargs)
inspect_inputs(10, 20, 30, name="مهران", active=True)خروجی:
آرگومانهای موقعیتی: (10, 20, 30)
آرگومانهای کلیدی: {'name': 'مهران', 'active': True}ترتیب استاندارد پارامترها در تعریف تابع
ترتیب کامل پارامترها معمولاً به شکل زیر است:
def example(required, default=0, *args, keyword_only=True, **kwargs):
pass- پارامترهای موقعیتی یا کلیدی الزامی
- پارامترهای دارای مقدار پیشفرض
*args- پارامترهای فقط کلیدی
**kwargs
نمونه کامل ترتیب پارامترها
def create_order(
customer,
currency="TRY",
*items,
discount=0,
send_email=True,
**metadata
):
subtotal = sum(items)
total = subtotal - (subtotal * discount / 100)
return {
"customer": customer,
"currency": currency,
"items": items,
"subtotal": subtotal,
"discount": discount,
"total": total,
"send_email": send_email,
"metadata": metadata
}
order = create_order(
"مهران",
"TRY",
250,
400,
150,
discount=10,
send_email=False,
source="website",
campaign="summer"
)
print(order)ترکیب * و ** هنگام فراخوانی
def register_user(name, age, city="نامشخص", active=True):
return {
"name": name,
"age": age,
"city": city,
"active": active
}
main_data = ["مهران", 30]
options = {
"city": "استانبول",
"active": True
}
user = register_user(*main_data, **options)
print(user)ارسال دوباره مقدار برای یک پارامتر
نباید یک پارامتر را هم مستقیم و هم از دیکشنری مقداردهی کنیم:
def greet(name):
return f"سلام {name}"
options = {"name": "سارا"}
greet("مهران", **options)این کد خطای زیر را ایجاد میکند:
TypeError: greet() got multiple values for argument 'name'تفاوت جمعآوری و بازکردن آرگومانها
- در تعریف تابع،
*argsچند آرگومان را داخل یک تاپل جمعآوری میکند. - در فراخوانی تابع،
*dataعناصر یک داده قابل پیمایش را باز میکند. - در تعریف تابع،
**kwargsآرگومانهای کلیدی را داخل یک دیکشنری جمعآوری میکند. - در فراخوانی تابع،
**dataجفتهای دیکشنری را به آرگومانهای کلیدی تبدیل میکند.
آیا همیشه باید از *args و **kwargs استفاده کنیم؟
خیر. اگر ورودیهای تابع مشخصاند، پارامترهای نامگذاریشده معمولاً خواناتر هستند:
def calculate_rectangle_area(width, height):
return width * heightنسخه زیر انعطافپذیر است، اما هدف تابع را مبهم میکند:
def calculate_rectangle_area(*args):
return args[0] * args[1]*args و **kwargs زمانی مناسباند که تعداد ورودیها واقعاً متغیر باشد یا تابع نقش واسط عمومی داشته باشد.
کاربرد در توابع واسط یا Wrapper
یکی از کاربردهای حرفهای، دریافت آرگومانها و انتقال آنها به تابع دیگر است:
def log_call(function, *args, **kwargs):
print("در حال اجرای تابع:", function.__name__)
result = function(*args, **kwargs)
print("نتیجه:", result)
return result
def calculate_price(price, quantity=1, discount=0):
total = price * quantity
return total - (total * discount / 100)
log_call(
calculate_price,
500,
quantity=3,
discount=10
)کاربرد در توابع داخلی و کتابخانهها
بسیاری از توابع و کلاسهای کتابخانهها گزینههای متعددی دارند. استفاده از **kwargs اجازه میدهد تنظیمات اختیاری بدون طولانیکردن رابط اصلی دریافت یا به تابع دیگری منتقل شوند. با این حال باید گزینههای معتبر مستندسازی و اعتبارسنجی شوند.
اعتبارسنجی گزینههای kwargs
پذیرفتن هر کلید بدون بررسی ممکن است خطای تایپی را پنهان کند. بهتر است کلیدهای مجاز مشخص باشند:
def create_report(**options):
allowed_options = {
"title",
"show_date",
"show_total"
}
unknown_options = set(options) - allowed_options
if unknown_options:
unknown_text = ", ".join(unknown_options)
raise ValueError(f"گزینه نامعتبر: {unknown_text}")
return {
"title": options.get("title", "گزارش"),
"show_date": options.get("show_date", True),
"show_total": options.get("show_total", True)
}Type Hint برای *args و **kwargs
میتوان نوع عناصر *args و مقدارهای **kwargs را مشخص کرد:
def add_numbers(*numbers: float) -> float:
return sum(numbers)
def show_options(**options: str) -> None:
for key, value in options.items():
print(key, value)در مثال دوم انتظار میرود تمام مقدارهای دیکشنری رشته باشند. Type Hint به خوانایی و ابزارهای تحلیل کد کمک میکند، اما بهصورت خودکار مانع ارسال نوع دیگر نمیشود.
خطاهای رایج در *args و **kwargs
۱. فراموشکردن ستاره
def show_items(args):
print(args)
show_items(1, 2, 3)این تابع فقط یک آرگومان میپذیرد. برای دریافت چند مقدار باید بنویسیم *args.
۲. تصور اینکه args یک لیست است
args تاپل است و متد append() ندارد:
def add_item(*args):
args.append("new")برای تغییر باید آن را به لیست تبدیل کرد:
items = list(args)۳. استفاده از اندیس بدون بررسی
def show_first(*args):
return args[0]فراخوانی بدون آرگومان باعث IndexError میشود. ابتدا خالیبودن تاپل را بررسی کنید.
۴. دسترسی مستقیم به کلید اختیاری
def show_name(**kwargs):
return kwargs["name"]اگر name ارسال نشود، KeyError ایجاد میشود. برای گزینه اختیاری از get() استفاده کنید.
۵. ترتیب نادرست پارامترها
**kwargs باید در انتهای فهرست پارامترها باشد:
def invalid(**kwargs, value):
passاین تعریف از نظر نحوی نادرست است.
۶. استفاده بیدلیل و کاهش خوانایی
تابعی که فقط با *args و **kwargs تعریف شده، ممکن است مشخص نکند چه ورودیهایی لازماند. پارامترهای اصلی را صریح تعریف کنید و فقط بخش متغیر را با args یا kwargs دریافت کنید.
پروژه اول: گزارش نمرات با *args
در این پروژه، نام دانشآموز الزامی است و تعداد نمرات میتواند متغیر باشد:
def create_grade_report(student_name, *scores):
if not scores:
return {
"student": student_name,
"count": 0,
"average": None,
"highest": None,
"lowest": None,
"status": "بدون نمره"
}
for score in scores:
if not isinstance(score, (int, float)):
raise TypeError("تمام نمرات باید عدد باشند")
if score < 0 or score > 20:
raise ValueError("هر نمره باید بین صفر تا بیست باشد")
average = sum(scores) / len(scores)
status = "قبول" if average >= 10 else "مردود"
return {
"student": student_name,
"count": len(scores),
"average": round(average, 2),
"highest": max(scores),
"lowest": min(scores),
"status": status
}
report = create_grade_report(
"مهران",
18,
16.5,
20,
14
)
for key, value in report.items():
print(f"{key}: {value}")پروژه دوم: سیستم ساخت سفارش با args و kwargs
در این پروژه قیمت کالاها بهصورت موقعیتی و تنظیمات سفارش بهصورت کلیدی دریافت میشوند:
def create_order(customer, *prices, **options):
if not customer.strip():
raise ValueError("نام مشتری نمیتواند خالی باشد")
if not prices:
raise ValueError("حداقل یک قیمت باید ارسال شود")
for price in prices:
if not isinstance(price, (int, float)):
raise TypeError("تمام قیمتها باید عدد باشند")
if price < 0:
raise ValueError("قیمت نمیتواند منفی باشد")
allowed_options = {
"discount",
"tax",
"shipping",
"currency",
"paid"
}
unknown_options = set(options) - allowed_options
if unknown_options:
invalid = ", ".join(unknown_options)
raise ValueError(f"گزینه نامعتبر: {invalid}")
discount = options.get("discount", 0)
tax = options.get("tax", 0)
shipping = options.get("shipping", 0)
currency = options.get("currency", "TRY")
paid = options.get("paid", False)
if not 0 <= discount <= 100:
raise ValueError("تخفیف باید بین صفر تا صد باشد")
if tax < 0 or shipping < 0:
raise ValueError("مالیات و هزینه ارسال نمیتوانند منفی باشند")
subtotal = sum(prices)
discounted_total = subtotal - (subtotal * discount / 100)
tax_amount = discounted_total * tax / 100
final_total = discounted_total + tax_amount + shipping
return {
"customer": customer,
"items_count": len(prices),
"subtotal": subtotal,
"discount": discount,
"tax": tax,
"shipping": shipping,
"final_total": round(final_total, 2),
"currency": currency,
"paid": paid
}
order = create_order(
"مهران",
350,
480,
120,
discount=10,
tax=20,
shipping=50,
currency="TRY",
paid=True
)
for key, value in order.items():
print(f"{key}: {value}")تمرینهای درس
- تابعی با
*argsبسازید که بزرگترین و کوچکترین عدد را برگرداند. - تابعی بنویسید که فقط رشتههای ارسالشده در
*argsرا به یک متن تبدیل کند. - تابعی با
**kwargsبسازید که یک پروفایل کاربر تولید کند. - تابعی بنویسید که کلیدهای ناشناخته در
kwargsرا گزارش دهد. - یک لیست سهعنصری را با
*به تابعی با سه پارامتر ارسال کنید. - یک دیکشنری را با
**به تابعی با آرگومانهای کلیدی ارسال کنید. - تابعی بسازید که یک عنوان الزامی، چند مقدار موقعیتی و چند گزینه کلیدی دریافت کند.
- تابعی برای محاسبه فاکتور چند کالا با تخفیف و مالیات اختیاری بنویسید.
- تابعی طراحی کنید که تابع دیگری را همراه با تمام args و kwargs اجرا کند.
- پروژه سفارش را توسعه دهید تا کد تخفیف و روش ارسال را نیز مدیریت کند.
📌 همچنین بخوانید: بازار کار پایتون در ایران، دستورهای break، continue و pass در پایتون و ساخت ربات تلگرام با پایتون
پرسشهای متداول
آیا نام args و kwargs اجباری است؟
خیر. ستارهها رفتار را تعیین میکنند. با این حال نامهای args و kwargs قراردادهای شناختهشده و خوانا هستند.
*args چه نوع دادهای میسازد؟
آرگومانهای موقعیتی اضافی را داخل یک تاپل قرار میدهد.
**kwargs چه نوع دادهای میسازد؟
آرگومانهای کلیدی اضافی را داخل یک دیکشنری قرار میدهد.
آیا میتوان *args و **kwargs را همزمان استفاده کرد؟
بله. ترتیب معمول این است که *args پیش از پارامترهای فقط کلیدی و **kwargs در انتها قرار گیرد.
تفاوت ستاره در تعریف و فراخوانی چیست؟
در تعریف، دادهها را جمعآوری میکند. در فراخوانی، عناصر یک مجموعه داده را باز و به آرگومانهای جداگانه تبدیل میکند.
چه زمانی نباید از kwargs استفاده کنیم؟
وقتی ورودیهای تابع مشخص و محدود هستند، پارامترهای صریح خواناتر، قابل مستندسازیتر و کمخطاترند.
جمعبندی
*args ابزار دریافت تعداد متغیری از آرگومانهای موقعیتی است و دادهها را به شکل تاپل در اختیار تابع قرار میدهد. **kwargs آرگومانهای کلیدی متغیر را در یک دیکشنری جمعآوری میکند. همین ستارهها هنگام فراخوانی برای بازکردن لیست، تاپل و دیکشنری به کار میروند.
استفاده درست از این قابلیتها به ساخت توابع عمومی و انعطافپذیر کمک میکند؛ اما استفاده بیدلیل میتواند رابط تابع را مبهم کند. پارامترهای اصلی را صریح تعریف کنید، گزینههای اختیاری را اعتبارسنجی کنید و از args و kwargs فقط زمانی استفاده کنید که تعداد ورودیها واقعاً متغیر است.
درس بعدی: توابع Lambda در پایتون — در حال آمادهسازی
ادامه مسیر یادگیری پایتون
برای یادگیری منظم، آموزشهای مرتبط زیر را بهترتیب دنبال کنید:
برای ذخیره پیشرفت در همه دستگاهها و دریافت گواهی پایان مسیر، رایگان ثبتنام کن یا وارد شو.