ফাংশনের আর্গুমেন্ট — নাম, ডিফল্ট আর স্টার
নাম ধরে আর্গুমেন্ট পাঠানো, ডিফল্ট ভ্যালু ও তাদের ক্রমের নিয়ম, *args আর **kwargs, আর কল করার সময় লিস্ট বা ডিকশনারি খুলে পাঠানো।
কীভাবে শিখতে চান?
যে সমস্যাটা আমরা সমাধান করছি
গত অধ্যায়ের ফাংশনগুলো একটিই আকারে ডাকা যেত: যতগুলো প্যারামিটার, ঠিক ততগুলো আর্গুমেন্ট, ঠিক সেই ক্রমে।
বাস্তবে সেটা যথেষ্ট নয়। একটি অর্ডারের ফাংশন ভাবুন:
def order(item, quantity):
return f"{quantity} x {item}"
print(order("pen", 3))
print(order(3, "pen"))3 x pen
pen x 3দ্বিতীয় লাইনটি কোনো এরর দেয়নি — শুধু অর্থহীন উত্তর দিয়েছে। পাইথন আর্গুমেন্টের ধরন যাচাই করে না: এখানে একটি স্ট্রিং আর একটি সংখ্যা জায়গা বদলেছে, আর f-string দুটোকেই ছাপতে পারে বলে কোথাও কিছু আটকায়নি। ক্রম উল্টে গেছে কি না, সেটা পাইথনের জানার কোনো উপায় নেই।
আর প্রায় সব অর্ডারই একটিমাত্র জিনিসের হলে? প্রতিবার 1 লিখতে হবে। ফাংশনে চারটি ঐচ্ছিক সেটিং থাকলে? প্রতিবার চারটিই লিখতে হবে, এমনকি যেগুলো বদলাচ্ছেন না সেগুলোও।
আজকের অধ্যায় আর্গুমেন্ট পাঠানোর বাকি উপায়গুলো — নাম ধরে পাঠানো, ডিফল্ট ভ্যালু, আর অনির্দিষ্ট সংখ্যক আর্গুমেন্ট।
এই অধ্যায় শেষে আপনি পারবেন
- নাম ধরে আর্গুমেন্ট পাঠাতে, আর বলতে কখন সেটা লেখাই উচিত
- ডিফল্ট ভ্যালু দিতে, আর তাদের ক্রমের নিয়মটি মানতে
- কিছু প্যারামিটারকে শুধু নাম ধরে পাঠানোর মতো বানাতে
*argsদিয়ে যেকোনো সংখ্যক আর্গুমেন্ট নিতে**kwargsদিয়ে যেকোনো নামের আর্গুমেন্ট নিতে- একটি লিস্ট বা ডিকশনারি খুলে আর্গুমেন্ট হিসেবে পাঠাতে
আগে যা জানা লাগবে: ফাংশন — একটি সিদ্ধান্ত, একটি নাম।
নাম ধরে পাঠানো
ক্রম মনে রাখার বদলে নামটাই লিখে দিন:
def order(item, quantity):
return f"{quantity} x {item}"
print(order(quantity=3, item="pen"))3 x penনাম দিলে ক্রম আর গুরুত্বপূর্ণ নয় — quantity আগে লেখা সত্ত্বেও ঠিক জায়গায় গেছে।
কখন লিখবেন? আর্গুমেন্টটি দেখে বোঝা না গেলে। order("pen", 3) পড়লেই বোঝা যায়। কিন্তু send(True, False) পড়ে কিছুই বোঝা যায় না — send(retry=True, verbose=False) লিখলে যায়। নিয়ম হিসেবে: True/False বা খালি সংখ্যা পাঠানোর সময় নাম লিখুন।
ডিফল্ট ভ্যালু
def order(item, quantity=1):
return f"{quantity} x {item}"
print(order("pen"))
print(order("pen", 5))
print(order("pen", quantity=5))1 x pen
5 x pen
5 x penquantity=1 বলছে “না দিলে এক ধরে নাও”। সাধারণ ক্ষেত্রটা ছোট হয়ে যায়, আর অসাধারণ ক্ষেত্রটাও লেখা যায়।
একটি নিয়ম মানতেই হবে: ডিফল্টওয়ালা প্যারামিটার সবসময় পরে বসে।
def order(quantity=1, item):
return itemSyntaxError: parameter without a default follows parameter with a defaultকারণটা ভাবলেই বোঝা যায়: order("pen") লিখলে পাইথন কীভাবে বুঝবে "pen" কোনটির জন্য? আবশ্যকগুলো আগে থাকলে সেই দ্ব্যর্থতা থাকে না।
যেকোনো সংখ্যক আর্গুমেন্ট — *args
def total(*prices):
return sum(prices)
print(total(15, 60))
print(total(15, 60, 850))
print(total())75
925
0*prices-এর স্টারটি (*) বলছে “বাকি সব পজিশনাল আর্গুমেন্ট এখানে জড়ো করো”। কয়টি আসবে তা আগে থেকে ঠিক করার দরকার নেই, আর একটিও না এলে সমস্যা নেই।
জড়ো করা জিনিসটি আসলে কী?
def show(*things):
print(type(things))
print(things)
show(1, "two")<class 'tuple'>
(1, 'two')একটি টাপল — ১৪ নম্বর অধ্যায়ের সেই টাপল। যুক্তিটা সুন্দর: আর্গুমেন্টের সংখ্যা কলের সময়ই ঠিক হয়ে যায় আর তারপর বদলানোর কথা নয়, তাই অপরিবর্তনীয় পাত্রই মানানসই।
pricesনামটা বাধ্যতামূলক নয়। প্রচলিত নাম*args, আর সেটাই বেশিরভাগ কোডে দেখবেন। কিন্তু অর্থপূর্ণ নাম থাকলে সেটাই ভালো —*pricesপড়ে বোঝা যায় ভেতরে কী আসছে,*argsবোঝায় না।
যেকোনো নামের আর্গুমেন্ট — **kwargs
def describe(**fields):
print(type(fields))
for key, value in fields.items():
print(f"{key}: {value}")
describe(item="pen", quantity=3)<class 'dict'>
item: pen
quantity: 3দুটি স্টার নাম-ধরে-পাঠানো আর্গুমেন্টগুলো জড়ো করে, আর ফলটি একটি ডিকশনারি — নাম (key) আর ভ্যালুর জোড়া, ঠিক যা হওয়ার কথা।
একটি স্টার মানে টাপল, দুটি মানে ডিকশনারি। এই জোড়াটা মনে রাখলে বাকিটা অনুমান করা যায়।
শুধু নাম ধরে পাঠানো যায় এমন প্যারামিটার
*args-এর পরে যে প্যারামিটার বসে, তাকে অবস্থান ধরে পাঠানোর কোনো উপায় নেই — কারণ *args বাকি সব পজিশনাল আর্গুমেন্ট নিজেই নিয়ে নেয়। তাই সেটি কেবল নাম ধরে পাঠানো যায়। একে বলে keyword-only প্যারামিটার:
def total(*prices, discount=0.0):
return sum(prices) * (1 - discount)
print(total(100, 50))
print(total(100, 50, discount=0.1))
print(total(100, 50, 0.1))150.0
135.0
150.1তৃতীয় লাইনটি খেয়াল করুন। 0.1 ছাড় হিসেবে যায়নি — সেটি আরেকটি দাম হিসেবে prices-এ ঢুকে গেছে, আর কোনো এরর ছাড়াই ভুল উত্তর এসেছে। discount পাঠাতে হলে discount=0.1 লিখতেই হবে।
*args না চাইলেও কোনো প্যারামিটারকে শুধু-নাম-ধরে বানানো যায়: প্যারামিটারের তালিকায় একা একটি * বসিয়ে দিন। তার পরের সবাই keyword-only:
def send(message, *, retry=False):
return f"{message} (retry={retry})"
print(send("hi", retry=True))
print(send("hi", True))hi (retry=True)
TypeError: send() takes 1 positional argument but 2 were given(দ্বিতীয় লাইনের আগে পাইথন traceback-এর কয়েকটি লাইনও ছাপে; এখানে শুধু শেষ লাইনটি দেখানো হয়েছে।)
True/False ধরনের সেটিং-এর জন্য এটি খুব কাজের: যে ডাকছে তাকে retry=True লিখতেই হবে, তাই কল পড়লেই বোঝা যায় True-টা কিসের।
ক্রমটা নির্দিষ্ট
একসাথে লিখলে পুরো নিয়মটা এরকম:
- প্রথমে সাধারণ প্যারামিটার। এদের মধ্যে ডিফল্ট ছাড়াগুলো আগে, ডিফল্টওয়ালাগুলো পরে।
- তারপর
*args(অথবা একা একটি*)। - তারপর keyword-only প্যারামিটার — এখানে ডিফল্ট থাকতেও পারে, না-ও পারে, যেকোনো ক্রমে।
- সবশেষে
**kwargs।
def f(a, b=1, *args, c, d=2, **kwargs):
print(a, b, args, c, d, kwargs)
f(10, c=3)
f(10, 20, 30, 40, c=3, e=5)10 1 () 3 2 {}
10 20 (30, 40) 3 2 {'e': 5}c-এর কোনো ডিফল্ট নেই, তবু সে ডিফল্টওয়ালা b=1-এর পরে বসেছে, আর এটা বৈধ — কারণ *args-এর পরের সবাই নাম ধরে আসে, তাই কোনটি কোথায় যাবে তা নিয়ে কোনো দ্ব্যর্থতা নেই। “ডিফল্টওয়ালা পরে” নিয়মটা কেবল *args-এর আগের অংশে খাটে।
প্রথম দেখায় জটিল লাগে, কিন্তু কারণটা সরল: পাইথনকে জানতে হবে কোন আর্গুমেন্টটি কোথায় যাবে, আর *args যেহেতু “বাকি সব” নেয়, তার পরে আর কোনো পজিশনাল প্যারামিটার থাকতে পারে না।
খুলে পাঠানো
স্টারগুলো কল করার সময়ও ব্যবহার করা যায়, আর তখন তারা উল্টো কাজ করে:
def order(item, quantity):
return f"{quantity} x {item}"
values = ["pen", 3]
fields = {"item": "bag", "quantity": 2}
print(order(*values))
print(order(**fields))3 x pen
2 x bag*values লিস্টটিকে খুলে দেয় — order(values[0], values[1]) লেখার সমান। **fields ডিকশনারিটিকে খোলে, আর key-গুলো প্যারামিটারের নামের সাথে মেলাতে হয়।
সংজ্ঞায় স্টার মানে জড়ো করা, কলে স্টার মানে খোলা — একই চিহ্ন, দুই দিক।
একটা সম্পূর্ণ উদাহরণ
invoice.py:
# Arguments, in the order Python expects them
def invoice(customer, *items, currency="unit", discount=0.0, **extra):
total = sum(price for _, price in items)
after = total * (1 - discount)
lines = [f"Invoice for {customer}"]
for name, price in items:
lines.append(f" {name:<10} {price:>8.2f} {currency}")
lines.append(f" {'subtotal':<10} {total:>8.2f} {currency}")
if discount:
lines.append(f" {'discount':<10} {discount * 100:>7.0f}%")
lines.append(f" {'total':<10} {after:>8.2f} {currency}")
for key, value in extra.items():
lines.append(f" note {key}: {value}")
return "\n".join(lines)
print(invoice("rafi", ("pen", 15.0), ("bag", 850.0)))
print()
print(invoice("dia", ("ink", 120.0), discount=0.1, reference="A-77"))Invoice for rafi
pen 15.00 unit
bag 850.00 unit
subtotal 865.00 unit
total 865.00 unit
Invoice for dia
ink 120.00 unit
subtotal 120.00 unit
discount 10%
total 108.00 unit
note reference: A-77চারটি জিনিস লক্ষ করার মতো।
customer আবশ্যক, বাকি সব ঐচ্ছিক। একটি চালান গ্রাহক ছাড়া অর্থহীন, তাই সেটি সাধারণ প্যারামিটার। পণ্য শূন্যটিও হতে পারে, তাই *items।
discount=0.0 কেবল ডিফল্ট নয়, একটি শর্তও সহজ করেছে। if discount: লাইনটি ৮ নম্বর অধ্যায়ের truthiness-এর নিয়ম ব্যবহার করছে — 0.0 false, তাই ছাড় না থাকলে সেই সারিটি ছাপাই হয় না। আর discount *items-এর পরে বসেছে বলে keyword-only — তাকে discount=0.1 লিখেই পাঠাতে হয়।
`extra অজানা তথ্য সামলাচ্ছে।** reference="A-77" কোনো প্যারামিটারের সাথে মেলে না, তাই সে extra`-তে গিয়ে পড়ে আর নোট হিসেবে ছাপা হয়। এভাবে ফাংশনটি ভবিষ্যতের অজানা ফিল্ডগুলোও নিতে পারে।
for _, price in items — ১৪ নম্বর অধ্যায়ের আনপ্যাকিং, আর আন্ডারস্কোরটি বলছে নামটা এখানে লাগবে না। items টাপলের একটি টাপল, আর প্রতিটি ভেতরের টাপল আনপ্যাক হচ্ছে।
কিছু ভাঙা অবস্থা ও তার সমাধান
SyntaxError: parameter without a default follows parameter with a default ডিফল্টওয়ালা প্যারামিটার আবশ্যকটির আগে বসেছে। আবশ্যকগুলো আগে, ডিফল্টওয়ালাগুলো পরে।
TypeError: order() takes 2 positional arguments but 3 were given বেশি আর্গুমেন্ট পাঠানো হয়েছে। বার্তাটি কয়টি নেয় আর কয়টি দেওয়া হয়েছে দুটোই বলে।
TypeError: order() got an unexpected keyword argument 'qty' নাম ধরে পাঠানো হয়েছে, কিন্তু সেই নামে কোনো প্যারামিটার নেই। বানান মিলিয়ে দেখুন — অথবা ফাংশনটিতে **kwargs থাকলে এটি ঘটত না, সে চুপচাপ নিয়ে নিত।
TypeError: order() got multiple values for argument 'item' একই প্যারামিটার একবার অবস্থান ধরে আর একবার নাম ধরে পাঠানো হয়েছে — order("pen", item="bag")।
ফাংশনটি ঠিক চলছে, কিন্তু উত্তর উল্টো আর্গুমেন্টের ক্রম উল্টে গেছে, আর ফাংশনের ভেতরের কোড দুই ধরনের ভ্যালু নিয়েই চলতে পারে বলে কোনো এরর আসেনি। নাম ধরে পাঠান — এটিই সেই নীরব ভুল যা এই অধ্যায়ের শুরুতে দেখানো হয়েছিল।
*` দিয়ে পাঠাচ্ছি, কিন্তু সংখ্যা মিলছে না** order(*values)-এ values-এ যতগুলো জিনিস, ফাংশনের ততগুলো প্যারামিটার লাগবে। print(len(values))` দিয়ে দেখে নিন।
ধাপ ৪ / ৬ — অনুমান
যাচাই করুন
quantity-এর একটি ডিফল্ট আছে। দুটি লাইন কী ছাপবে?
def order(item, quantity=1):
return f"{quantity} x {item}"
print(order("pen"))
print(order("pen", 5))- A1 x pen 5 x pen
- B1 x pen 1 x pen
- Cএকটি `TypeError`
- Dpen x 1 pen x 5
*things কী ধরনের জিনিস জড়ো করে?
def show(*things):
print(type(things))
print(len(things))
show(1, "two", 3.0)- A<class 'tuple'> 3
- B<class 'tuple'> 1
- C<class 'list'> 3
- D<class 'dict'> 3
ডিফল্টওয়ালা প্যারামিটারটি আগে বসানো হয়েছে। কী হবে?
def order(quantity=1, item):
return item- Aকাজ করে স্বাভাবিকভাবেই
- Bকাজ করে, কিন্তু `order("pen")` লিখলে এরর হয়
- C`SyntaxError` — ডিফল্টওয়ালা প্যারামিটার আবশ্যকটির আগে বসতে পারে না
- D`TypeError`
উত্তর দিতে অ্যাকাউন্ট লাগবে
উত্তর মিলিয়ে দেখতে সাইন ইন করুন
প্রশ্নগুলো উপরে আছে, আর মাথায় মাথায় উত্তর ভেবে নেওয়াই আসল কাজ। সঠিক উত্তর, ব্যাখ্যা আর তিন ধাপের ইঙ্গিত দেখতে সাইন ইন করুন।
নিজে করুন
message.py নামে একটি ফাইল লিখুন, যাতে একটি ফাংশন থাকবে:
def notify(recipient, *lines, subject="Notice", urgent=False, **details)ফাংশনটি একটি পড়ার মতো বার্তা ফেরত দেবে (ছাপবে না), যেখানে থাকবে প্রাপক, বিষয়, প্রতিটি লাইন আলাদা সারিতে, আর details-এর প্রতিটি জোড়া নিচে নোট হিসেবে। urgent True হলে বিষয়ের আগে কিছু একটা যোগ করুন।
তারপর ফাংশনটিকে চারভাবে ডাকুন:
- শুধু প্রাপক দিয়ে — কোনো লাইন ছাড়াই
- তিনটি লাইন দিয়ে, বাকি সব ডিফল্টে
- নাম ধরে
urgent=Trueআর একটিsubjectদিয়ে - একটি লিস্ট আর একটি ডিকশনারি বানিয়ে,
*আর**দিয়ে খুলে পাঠিয়ে
তারপর ইচ্ছে করে তিনবার ভাঙুন, আর প্রতিবার কী হয় দেখুন:
subject="Notice"প্যারামিটারটিকেrecipient-এর আগে সরিয়ে দিন। ফাইলটি চলার আগেইSyntaxError: parameter without a default follows parameter with a defaultআসবে।notify("rafi", subjekt="hi")লিখে বানান ভুল করে পাঠান। কোনো এরর আসবে না:**detailsভুল নামটাও চুপচাপ নিয়ে নেয়, তাইsubjektএকটি নোট হয়ে যায় আর বিষয় থেকে যায়"Notice"। ফেরত আসা বার্তাটি ছেপে নিজের চোখে দেখুন।notify("rafi", recipient="dia")লিখুন।TypeError: notify() got multiple values for argument 'recipient'আসবে।
দুটি ভুল পাইথন সাথে সাথে ধরে, একটি ধরে না — আর সেই একটিই সবচেয়ে বিপজ্জনক। **kwargs যত সুবিধা দেয়, তার দাম এটাই: বানান ভুল আর কেউ ধরে না। বার্তাগুলো একবার চিনে রাখলে পরে সেকেন্ডে ধরতে পারবেন, আর নীরব ভুলটা কোথায় লুকায় সেটাও জানা থাকবে।
ধাপ ৬ / ৬
কঠিন করা — অধ্যায়ের কুইজ
সহজ থেকে কঠিন — দশটি প্রশ্ন, শেষেরগুলো ইচ্ছে করেই কঠিন।
সাইন ইন করে কুইজ দিন