অধ্যায় 20

ফাংশনের আর্গুমেন্ট — নাম, ডিফল্ট আর তারকা

নাম ধরে আর্গুমেন্ট পাঠানো, ডিফল্ট মান ও তাদের ক্রমের নিয়ম, *args আর **kwargs, আর কল করার সময় লিস্ট বা ডিকশনারি খুলে পাঠানো।

32 মিনিটPython 3.12
  1. 1সমস্যা
  2. 2বোঝা
  3. 3উদাহরণ
  4. 4অনুমান
  5. 5নিজে করা
  6. 6কঠিন করা

যে সমস্যাটা আমরা সমাধান করছি

গত অধ্যায়ের ফাংশনগুলো একটিই আকারে ডাকা যেত: যতগুলো প্যারামিটার, ঠিক ততগুলো আর্গুমেন্ট, ঠিক সেই ক্রমে।

বাস্তবে সেটা যথেষ্ট নয়। একটি অর্ডারের ফাংশন ভাবুন:

python
def order(item, quantity):
    return f"{quantity} x {item}"


print(order("pen", 3))
print(order(3, "pen"))
text
3 x pen
pen x 3

দ্বিতীয় লাইনটি কোনো ত্রুটি দেয়নি — শুধু অর্থহীন উত্তর দিয়েছে। প্যারামিটার দুটি একই ধরনের হলে পাইথনের বলার কোনো উপায়ই নেই যে ক্রম উল্টে গেছে।

আর প্রায় সব অর্ডারই একটিমাত্র জিনিসের হলে? প্রতিবার 1 লিখতে হবে। ফাংশনে চারটি ঐচ্ছিক সেটিং থাকলে? প্রতিবার চারটিই লিখতে হবে, এমনকি যেগুলো বদলাচ্ছেন না সেগুলোও।

আজকের অধ্যায় আর্গুমেন্ট পাঠানোর বাকি উপায়গুলো — নাম ধরে পাঠানো, ডিফল্ট মান, আর অনির্দিষ্ট সংখ্যক আর্গুমেন্ট।

এই অধ্যায় শেষে আপনি পারবেন

  • নাম ধরে আর্গুমেন্ট পাঠাতে, আর বলতে কখন সেটা লেখাই উচিত
  • ডিফল্ট মান দিতে, আর তাদের ক্রমের নিয়মটি মানতে
  • *args দিয়ে যেকোনো সংখ্যক আর্গুমেন্ট নিতে
  • **kwargs দিয়ে যেকোনো নামের আর্গুমেন্ট নিতে
  • একটি লিস্ট বা ডিকশনারি খুলে আর্গুমেন্ট হিসেবে পাঠাতে

আগে যা জানা লাগবে: ফাংশন — একটি সিদ্ধান্ত, একটি নাম।


নাম ধরে পাঠানো

ক্রম মনে রাখার বদলে নামটাই লিখে দিন:

python
def order(item, quantity):
    return f"{quantity} x {item}"


print(order(quantity=3, item="pen"))
text
3 x pen

নাম দিলে ক্রম আর গুরুত্বপূর্ণ নয় — quantity আগে লেখা সত্ত্বেও ঠিক জায়গায় গেছে।

কখন লিখবেন? আর্গুমেন্টটি দেখে বোঝা না গেলে। order("pen", 3) পড়লেই বোঝা যায়। কিন্তু send(True, False) পড়ে কিছুই বোঝা যায় না — send(retry=True, verbose=False) লিখলে যায়। নিয়ম হিসেবে: True/False বা খালি সংখ্যা পাঠানোর সময় নাম লিখুন।

ডিফল্ট মান

python
def order(item, quantity=1):
    return f"{quantity} x {item}"


print(order("pen"))
print(order("pen", 5))
print(order("pen", quantity=5))
text
1 x pen
5 x pen
5 x pen

quantity=1 বলছে «না দিলে এক ধরে নাও»। সাধারণ ক্ষেত্রটা ছোট হয়ে যায়, আর অসাধারণ ক্ষেত্রটাও লেখা যায়।

একটি নিয়ম মানতেই হবে: ডিফল্টওয়ালা প্যারামিটার সবসময় পরে বসে।

text
def order(quantity=1, item):
    return item
text
SyntaxError: parameter without a default follows parameter with a default

কারণটা ভাবলেই বোঝা যায়: order("pen") লিখলে পাইথন কীভাবে বুঝবে "pen" কোনটির জন্য? আবশ্যকগুলো আগে থাকলে সেই দ্ব্যর্থতা থাকে না।

যেকোনো সংখ্যক আর্গুমেন্ট — *args

python
def total(*prices):
    return sum(prices)


print(total(15, 60))
print(total(15, 60, 850))
print(total())
text
75
925
0

*prices-এর তারকাটি বলছে «বাকি সব পজিশনাল আর্গুমেন্ট এখানে জড়ো করো»। কয়টি আসবে তা আগে থেকে ঠিক করার দরকার নেই, আর একটিও না এলে সমস্যা নেই।

জড়ো করা জিনিসটি আসলে কী?

python
def show(*things):
    print(type(things))
    print(things)


show(1, "two")
text
<class 'tuple'>
(1, 'two')

একটি টাপল — চতুর্দশ অধ্যায়ের সেই টাপল। যুক্তিটা সুন্দর: আর্গুমেন্টের সংখ্যা কলের সময়ই ঠিক হয়ে যায় আর তারপর বদলানোর কথা নয়, তাই অপরিবর্তনীয় পাত্রই মানানসই।

prices নামটা বাধ্যতামূলক নয়। প্রচলিত নাম *args, আর সেটাই বেশিরভাগ কোডে দেখবেন। কিন্তু অর্থপূর্ণ নাম থাকলে সেটাই ভালো — *prices পড়ে বোঝা যায় ভেতরে কী আসছে, *args বোঝায় না।

যেকোনো নামের আর্গুমেন্ট — **kwargs

python
def describe(**fields):
    print(type(fields))
    for key, value in fields.items():
        print(f"{key}: {value}")


describe(item="pen", quantity=3)
text
<class 'dict'>
item: pen
quantity: 3

দুটি তারকা নাম-ধরে-পাঠানো আর্গুমেন্টগুলো জড়ো করে, আর ফলটি একটি ডিকশনারি — নাম আর মানের জোড়া, ঠিক যা হওয়ার কথা।

একটি তারকা মানে টাপল, দুটি মানে ডিকশনারি। এই জোড়াটা মনে রাখলে বাকিটা অনুমান করা যায়।

ক্রমটা নির্দিষ্ট

একসাথে লিখলে ক্রম হবে: *সাধারণ, তারপর `args, তারপর ডিফল্টওয়ালা, তারপর kwargs`।

text
def f(a, *args, b=1, **kwargs):

প্রথম দেখায় জটিল লাগে, কিন্তু কারণটা সরল: পাইথনকে জানতে হবে কোন আর্গুমেন্টটি কোথায় যাবে, আর *args যেহেতু «বাকি সব» নেয়, তার পরে আর কোনো পজিশনাল আর্গুমেন্ট থাকতে পারে না।

খুলে পাঠানো

তারকাগুলো কল করার সময়ও ব্যবহার করা যায়, আর তখন তারা উল্টো কাজ করে:

python
def order(item, quantity):
    return f"{quantity} x {item}"


values = ["pen", 3]
fields = {"item": "bag", "quantity": 2}

print(order(*values))
print(order(**fields))
text
3 x pen
2 x bag

*values লিস্টটিকে খুলে দেয় — order(values[0], values[1]) লেখার সমান। **fields ডিকশনারিটিকে খোলে, আর কী-গুলো প্যারামিটারের নামের সাথে মেলাতে হয়।

সংজ্ঞায় তারকা মানে জড়ো করা, কলে তারকা মানে খোলা — একই চিহ্ন, দুই দিক।


একটা সম্পূর্ণ উদাহরণ

invoice.py:

python
# 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"))
text
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: লাইনটি অষ্টম অধ্যায়ের সত্যতার নিয়ম ব্যবহার করছে — 0.0 মিথ্যা, তাই ছাড় না থাকলে সেই সারিটি ছাপাই হয় না।

`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))` দিয়ে দেখে নিন।