Python-এর ফাংশন: def, return, আর্গুমেন্ট, *args ও **kwargs, lambda ও টাইপ অ্যানোটেশন
Python-এর ফাংশন হলো একটি নামযুক্ত কোড-ব্লক, যা def দিয়ে ঘোষণা করা হয়, নাম ও বন্ধনী দিয়ে ডাকা হয় এবং return দিয়ে ফল ফেরত দেয়; return না থাকলে ফল হয় None। আর্গুমেন্ট ক্রমে বা নাম ধরে পাঠানো যায়, *args বাড়তি পজিশনাল মানগুলো tuple-এ এবং **kwargs বাড়তি নামযুক্ত মানগুলো dictionary-তে জমায়, আর lambda একটি এক্সপ্রেশনের ছোট নামহীন ফাংশন বানায়।
Python-এর ফাংশন হলো নামযুক্ত একটি কোড-ব্লক, যাকে যতবার খুশি ভিন্ন ইনপুট দিয়ে ডাকা যায়। ঘোষণা হয় def শব্দ দিয়ে, মান নেয় প্যারামিটারের মাধ্যমে, আর ফল দেয় return দিয়ে। লম্বা স্ক্রিপ্টকে বোধগম্য, যাচাইযোগ্য ধাপে ভাঙার প্রধান উপায় এই ফাংশন।
ফাংশন ঘোষণা: def
ফাংশন ঘোষণা শুরু হয় def দিয়ে, তারপর নাম, বন্ধনীতে প্যারামিটার এবং কোলন। শরীর লেখা হয় ৪ স্পেস ইন্ডেন্টে — Python-এ ইন্ডেন্টেশন সাজসজ্জা নয়, সিনট্যাক্সের অংশ।
def greet(name):
"""ব্যবহারকারীর জন্য একটি স্বাগত বার্তা ফেরত দেয়।"""
return f"স্বাগতম, {name}!"
print(greet("Anika")) # স্বাগতম, Anika!
হেডারের পরে তিন উদ্ধৃতি চিহ্নের স্ট্রিং হলো docstring — ফাংশনের বর্ণনা। help(greet) এবং এডিটরের ইঙ্গিতে এটি দেখায়। ফাংশনের নাম PEP 8 অনুযায়ী snake_case-এ লেখা হয়: add_expense, get_rate।
def greet(name): লাইনকে বলা হয় ফাংশনের সিগনেচার: নাম ও প্যারামিটারের তালিকা (এবং অ্যানোটেশন থাকলে সেগুলোও)। সিগনেচার দেখে পাঠক শরীর না পড়েই ফাংশন কীভাবে ডাকতে হবে বুঝে যায়।
return-এর কাজ
return ফাংশন শেষ করে এবং মান ফেরত দেয় যিনি ডেকেছেন তাকে। return চললে তার পরের কোড আর চলে না। return না থাকলে, অথবা মান ছাড়া থাকলে, ফাংশন None ফেরত দেয়।
def area(w, h):
w * h # হিসাব হলো, কিন্তু ফেলে দেওয়া হলো
print(area(3, 4)) # None
def area(w, h):
return w * h
print(area(3, 4)) # 12
ভুলে return লেখা নতুনদের সবচেয়ে সাধারণ ভুলগুলোর একটি: ফাংশন «কাজ করে», কিন্তু None দেয়, এবং সমস্যাটি ধরা পড়ে এক লাইন পরে। একাধিক মান ফেরত দেওয়াও সম্ভব — আসলে সেটি একটি tuple, যা সহজেই আনপ্যাক করা যায়:
def min_max(numbers):
return min(numbers), max(numbers)
low, high = min_max([4, 1, 9])
print(low, high) # 1 9
আগেভাগে return লিখলে অতিরিক্ত নেস্টিং এড়ানো যায়: প্রথমে বিশেষ ক্ষেত্রগুলো বাদ দিন, তারপর মূল যুক্তি লিখুন।
def discount(price, is_member):
if price <= 0:
return 0
if not is_member:
return price
return price * 0.9
Python-এর ফাংশন আর্গুমেন্ট: পজিশনাল ও নামযুক্ত
প্যারামিটার হলো ফাংশন ঘোষণায় নাম, আর আর্গুমেন্ট হলো ডাকার সময় পাঠানো মান। আর্গুমেন্ট ক্রমে (পজিশনাল) বা নাম ধরে (নামযুক্ত) পাঠানো যায়। নামযুক্ত আর্গুমেন্ট ডাকাকে নিজেই ব্যাখ্যামূলক করে, এবং ক্রমের গুরুত্ব কমে যায়।
def add_expense(title, amount, category="other"):
return {"title": title, "amount": amount, "category": category}
add_expense("Coffee", 250) # ক্রমে
add_expense(amount=250, title="Coffee") # নাম ধরে
add_expense("Taxi", 600, category="transport") # মিশ্র
ডিফল্ট মানসহ প্যারামিটার ঐচ্ছিক। নিয়ম: ডিফল্ট মানের প্যারামিটার যায় বাধ্যতামূলকগুলোর পরে, আর ডাকার সময় পজিশনাল আর্গুমেন্ট যায় নামযুক্তগুলোর আগে। নইলে Python SyntaxError দেয়।
ফাঁদ: পরিবর্তনযোগ্য ডিফল্ট মান
ডিফল্ট মান একবারই হিসাব হয় — def লাইন চলার সময়, প্রতিটি ডাকার সময় নয়। সেটি তালিকা বা dictionary হলে সব ডাকার মধ্যে তা ভাগাভাগি হয়।
def add_item(item, acc=[]):
acc.append(item)
return acc
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] — তালিকা আগের ডাকটি মনে রাখে
সঠিক উপায় হলো ডিফল্ট None রাখা এবং ভেতরে নতুন অবজেক্ট বানানো:
def add_item(item, acc=None):
if acc is None:
acc = []
acc.append(item)
return acc
*args ও **kwargs: যেকোনো সংখ্যক আর্গুমেন্ট
প্যারামিটারের আগে একটি তারকাচিহ্ন বাড়তি পজিশনাল আর্গুমেন্টগুলো একটি tuple-এ জমায়, আর দুটি তারকাচিহ্ন বাড়তি নামযুক্ত আর্গুমেন্টগুলো একটি dictionary-তে। args ও kwargs নাম শুধু একটি রীতি — আসল কাজ করে তারকাচিহ্নগুলো।
def total(*args):
return sum(args)
print(total(100, 250, 40)) # 390
def make_user(name, **kwargs):
user = {"name": name}
user.update(kwargs)
return user
print(make_user("Anika", city="Dhaka", age=30))
# {'name': 'Anika', 'city': 'Dhaka', 'age': 30}
একই তারকাচিহ্ন উল্টো দিকেও কাজ করে — ডাকার সময় সেগুলো একটি ক্রম বা dictionary-কে আর্গুমেন্টে ছড়িয়ে দেয়:
values = [3, 4]
print(pow(*values)) # 81
opts = {"sep": " | ", "end": "\n"}
print("a", "b", **opts) # a | b
শুধু পজিশনাল ও শুধু নামযুক্ত প্যারামিটার
প্যারামিটার তালিকায় স্ল্যাশ / এবং তারকাচিহ্ন * ব্যারিয়ারের মতো কাজ করে: /-এর আগের সবকিছু শুধু ক্রমে পাঠানো যায় (সিনট্যাক্সটি Python 3.8-এ এসেছে, PEP 570), আর *-এর পরের সবকিছু শুধু নাম ধরে।
def convert(amount, /, *, rate, currency="USD"):
return round(amount / rate, 2), currency
convert(100, rate=90.5) # (1.1, 'USD')
convert(amount=100, rate=90.5) # TypeError: amount শুধু পজিশনাল হিসেবে পাঠানো যাবে
convert(100, 90.5) # TypeError: rate শুধু নামে পাঠাতে হবে
শুধু নামযুক্ত প্যারামিটার convert(100, 90.5, "EUR")-এর মতো বিভ্রান্তিকর ডাক থেকে রক্ষা করে, যেখানে বোঝা যায় না কোনটি কী।
দৃশ্যমানতার নিয়ম: LEGB
Python একটি নামকে খোঁজে এই ক্রমে: Local (ফাংশনের ভেতরে) → Enclosing (বাইরের ফাংশনে) → Global (মডিউল স্তরে) → Built-in (len-এর মতো অন্তর্নির্মিত নাম)। ফাংশনের ভেতর থেকে গ্লোবাল ভেরিয়েবল পড়া যায়, কিন্তু তাতে নতুন মান বসাতে লাগে global ঘোষণা।
limit = 1000
def check(amount):
return amount > limit # গ্লোবাল পড়া — চলে
def raise_limit():
global limit
limit = 2000 # global ছাড়া একটি লোকাল ভেরিয়েবল তৈরি হতো
বাস্তবে global কম ব্যবহার করা ভালো: বাইরের অবস্থা বদলায় এমন ফাংশন পরীক্ষা করা কঠিন। মান আর্গুমেন্ট হিসেবে নেওয়া এবং ফল ফেরত দেওয়া নিরাপদ।
ভেতরের ফাংশন ও closure
একটি ফাংশনের ভেতরে আরেকটি ফাংশন ঘোষণা করা যায়। ভেতরের ফাংশন বাইরের ফাংশনের ভেরিয়েবল দেখে, এমনকি বাইরের ফাংশন শেষ হয়ে যাওয়ার পরেও — একে বলে closure। এমন ভেরিয়েবল বদলাতে লাগে nonlocal।
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
c = make_counter()
print(c(), c(), c()) # 1 2 3
Closure-এর উপরেই দাঁড়িয়ে আছে decorator ও ফাংশন-ফ্যাক্টরি।
ফাংশনও একটি অবজেক্ট
Python-এ ফাংশন একটি সাধারণ অবজেক্ট: তাকে ভেরিয়েবলে রাখা যায়, অন্য ফাংশনে পাঠানো যায়, ফাংশন থেকে ফেরত দেওয়া যায়, dictionary-তে জমানো যায়। তার অ্যাট্রিবিউট আছে: __name__, __doc__, __defaults__, __annotations__।
def double(x):
return x * 2
f = double
print(f(5), f.__name__) # 10 double
print(list(map(double, [1, 2, 3]))) # [2, 4, 6]
Python-এর lambda: নামহীন ফাংশন
Lambda হলো একটি ছোট নামহীন ফাংশন, যার শরীর শুধু একটি এক্সপ্রেশন। এক্সপ্রেশনের ফল নিজে থেকেই ফেরত আসে, return লেখা যায় না।
square = lambda x: x ** 2
print(square(4)) # 16
expenses = [("Coffee", 250), ("Taxi", 600), ("Lunch", 450)]
expenses.sort(key=lambda e: e[1], reverse=True)
print(expenses) # [('Taxi', 600), ('Lunch', 450), ('Coffee', 250)]
print(list(filter(lambda n: n % 2 == 0, range(10)))) # [0, 2, 4, 6, 8]
Lambda ভালো মানায় sorted, max, map, filter-এর মতো ফাংশনে ছোট আর্গুমেন্ট হিসেবে। তবে lambda-কে ভেরিয়েবলে নাম দিয়ে বসানো, যেমন উপরের square, PEP 8 অনুমোদন করে না: নাম দরকার হলে def লিখুন — তখন ট্রেসব্যাকে নরমাল __name__ থাকে এবং docstring-এর জায়গা থাকে।
| def | lambda | |
|---|---|---|
| নাম | আছে | নেই (<lambda>) |
| শরীর | যতগুলো খুশি নির্দেশ | একটি এক্সপ্রেশন |
| return | স্পষ্ট | অন্তর্নিহিত |
| Docstring, অ্যানোটেশন | আছে | নেই |
| কোথায় উপযোগী | সর্বত্র | ছোট key বা callback-এ |
টাইপ অ্যানোটেশন: ইঙ্গিত, যাচাই নয়
অ্যানোটেশন প্যারামিটার ও ফলের প্রত্যাশিত ধরন বর্ণনা করে। Python চালানোর সময় এগুলো যাচাই করে না: x: int লেখা ফাংশন একটি স্ট্রিংও নীরবে নেবে। যাচাই করে বাইরের টুল — mypy, pyright, ty — এবং এডিটর, যা চালানোর আগেই ভুল দেখায়।
def add_expense(title: str, amount: float, category: str = "other") -> dict[str, object]:
return {"title": title, "amount": amount, "category": category}
def area(w: float, h: float) -> float:
w * h # mypy: Missing return statement
ইনস্টল না করে যাচাই চালানো যায় uvx mypy . দিয়ে। উপরের উদাহরণ দেখায় অ্যানোটেশনের উপকার: -> float-এর সঙ্গে ভুলে যাওয়া return বিশ্লেষক সঙ্গে সঙ্গে ধরে ফেলে।
Python 3.12 থেকে (PEP 695) জেনেরিক ফাংশন লেখা হয় TypeVar ছাড়াই: টাইপ প্যারামিটার বর্গাকার বন্ধনীতে দেওয়া হয়, আর টাইপের উপনাম ঘোষণা হয় type শব্দ দিয়ে।
def first[T](xs: list[T]) -> T:
return xs[0]
type Money = int | float
Python 3.14-এ (PEP 649) অ্যানোটেশন হয়েছে lazy: প্রথমবার প্রবেশের সময় হিসাব হয়, তাই পরে ঘোষিত ক্লাসের নাম উদ্ধৃতি ছাড়াই ব্যবহার করা যায়, আর from __future__ import annotations লাগে না।
«পাঠানো হয়নি» বনাম «None পাঠানো হয়েছে»: Python 3.15-এ sentinel
ডিফল্ট None দিয়ে বোঝা যায় না, আর্গুমেন্ট আদৌ পাঠানো হয়েছে কিনা, নাকি ইচ্ছে করে None পাঠানো হয়েছে। বছরের পর বছর এটি সমাধান হতো নিজের বানানো মার্কার দিয়ে:
_MISSING = object()
def get_rate(currency, default=_MISSING):
rates = {"USD": 90.5}
if currency in rates:
return rates[currency]
if default is _MISSING:
raise KeyError(currency)
return default
Python 3.15-এ এসেছে অন্তর্নির্মিত sentinel ফাংশন (PEP 661): MISSING = sentinel("MISSING") একটি অনন্য মার্কার বানায়, যার সুন্দর repr আছে, কপি করলেও নিজেই থাকে, pickle-এ সিরিয়ালাইজ হয় এবং float | MISSING-এর মতো অ্যানোটেশনে ব্যবহার করা যায়। পুরনো সংস্করণে object()-এর কৌশলটি চলতেই থাকবে।
ফাংশনের সাধারণ ভুল
- return ভুলে যাওয়া — ফাংশন
Noneদেয়। - বন্ধনী ভুলে যাওয়া —
print(greet)ফাংশন অবজেক্ট দেখায়, ফল নয়। - পরিবর্তনযোগ্য ডিফল্ট মান — সব ডাকের জন্য একটিই তালিকা।
- আর্গুমেন্টের ভুল সংখ্যা —
TypeError: greet() missing 1 required positional argument: 'name'। - নাম বিল্ট-ইন নামের সঙ্গে মেলা —
listবাsumনামের ফাংশন অন্তর্নির্মিতটিকে ঢেকে দেয়।
সারসংক্ষেপ
Python-এর ফাংশন def দিয়ে ঘোষিত হয়, return দিয়ে ফল দেয় এবং আর্গুমেন্ট ক্রমে বা নাম ধরে নেয়। *args ও **kwargs নমনীয়তা দেয়, / ও * কলের কড়াকড়ি, lambda একবারের প্রয়োজনের জন্য সংক্ষিপ্ততা, আর টাইপ অ্যানোটেশন mypy-র সঙ্গে চালানোর আগেই ভুল ধরে। কোর্সে এই বিষয়টি বাস্তবে শেখানো হয়: খরচের ট্র্যাকার অ্যানোটেশনসহ ছোট ফাংশনে ভাঙা হয় এবং mypy দিয়ে যাচাই করা হয়।
সাধারণ প্রশ্ন
প্যারামিটার আর আর্গুমেন্টের পার্থক্য কী?
প্যারামিটার হলো ফাংশন ঘোষণার নাম, আর আর্গুমেন্ট হলো ডাকার সময় দেওয়া নির্দিষ্ট মান। def greet(name)-এ name প্যারামিটার, আর greet("Anika")-য় "Anika" স্ট্রিংটি আর্গুমেন্ট।
ঘোষণার আগে কি ফাংশন ডাকা যায়?
মডিউল স্তরে না: def লাইনটি আগে চলতে হবে, নইলে NameError। তবে অন্য একটি ফাংশনের ভেতর থেকে পরে ঘোষিত ফাংশন ডাকা যায়, যদি সেই ডাকটি ঘোষণার পরে ঘটে।
একটি ফাংশন কতগুলো মান ফেরত দিতে পারে?
আনুষ্ঠানিকভাবে একটি, তবে সেই একটি মান tuple-ও হতে পারে: return a, b দুটি উপাদানের একটি tuple ফেরত দেয়, যেটি x, y = f() লিখে আনপ্যাক করা যায়।
টাইপ অ্যানোটেশন লেখা কি বাধ্যতামূলক?
না, অ্যানোটেশন ছাড়াও Python চলে। তবে অন্যদের পড়ার কোডে অ্যানোটেশন রক্ষণাবেক্ষণ সহজ করে এবং mypy বা pyright-কে চালানোর আগেই ভুল ধরতে দেয়।
lambda-র ভেতরে if লেখা যায় কি?
শুধু শর্তসূচক এক্সপ্রেশন যায়: lambda x: "জোড়" if x % 2 == 0 else "বিজোড়"। lambda-র ভেতরে নির্দেশ, লুপ বা অ্যাসাইনমেন্ট চলে না।
“Python 3.15: от новичка до профи” কোর্সে বিষয়টি অনুশীলনসহ বিস্তারিত শেখানো হয়েছে।
কোর্সে যান