অধ্যায় 19

ফাংশন — একটি সিদ্ধান্ত, একটি নাম

def দিয়ে ফাংশন লেখা, return আর print-এর পার্থক্য, শুরুতেই গার্ড দিয়ে প্রান্ত সামলানো, আর ডকস্ট্রিং।

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

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

দুটি দলের গড় নম্বর বের করতে হবে:

python
marks_a = [72, 45]
marks_b = [90, 33, 61]

print(round(sum(marks_a) / len(marks_a), 2))
print(round(sum(marks_b) / len(marks_b), 2))
text
58.5
61.33

দুটি লাইন প্রায় একই, কেবল নামটা আলাদা। তৃতীয় দল এলে তৃতীয় লাইন, চতুর্থ দলে চতুর্থ।

সমস্যাটা লেখার পরিশ্রম নয় — কপি করা সস্তা। সমস্যাটা হলো একই সিদ্ধান্ত এখন কয়েক জায়গায় লেখা আছে। গড়কে দুই ঘরের বদলে এক ঘরে দেখাতে বললে প্রতিটি লাইন খুঁজে বদলাতে হবে, আর একটি বাদ পড়লে প্রোগ্রামটি দুই জায়গায় দুই রকম আচরণ করবে — কোনো ত্রুটি ছাড়াই।

আর একটি খালি লিস্ট এলে? sum([]) / len([]) মানে শূন্য দিয়ে ভাগ, আর প্রোগ্রাম থেমে যায়। সেই পরীক্ষাটা প্রতিটি লাইনে আলাদা করে বসাতে হবে।

ফাংশন হলো একটি সিদ্ধান্তকে একবার লিখে একটি নাম দেওয়া। এরপর সেই নামটা ডাকলেই কাজ হয়, আর বদলাতে হলে একটিমাত্র জায়গা।

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

  • def দিয়ে ফাংশন লিখতে আর ডাকতে
  • return আর print-এর পার্থক্য বলতে — এই অধ্যায়ের সবচেয়ে জরুরি জিনিস
  • একটি ফাংশনকে ছোট আর একটি কাজেই সীমিত রাখতে
  • প্রান্তিক অবস্থা সামলাতে শুরুতেই একটি গার্ড লিখতে
  • ডকস্ট্রিং লিখতে

আগে যা জানা লাগবে: কম্প্রিহেনশন।


প্রথম ফাংশন

python
def average(values):
    return round(sum(values) / len(values), 2)


print(average([72, 45]))
print(average([90, 33, 61]))
text
58.5
61.33

গঠনটা:

  • def — «আমি একটি ফাংশন তৈরি করছি»
  • average — নাম, যে নিয়মে ভেরিয়েবলের নাম দেন সেই নিয়মেই
  • (values) — প্যারামিটার, অর্থাৎ ভেতরে ডেটা আসার দরজা
  • : আর ইন্ডেন্ট করা ব্লক — শরীর, ঠিক if বা for-এর মতো
  • return — ফল ফেরত পাঠানো

average([72, 45]) লেখাকে বলে কল করা। [72, 45] হলো আর্গুমেন্ট — যা সত্যিই পাঠানো হচ্ছে; values হলো প্যারামিটার — ভেতরের নাম। দুটো আলাদা শব্দ, আর ত্রুটির বার্তায় দুটোই আসে, তাই পার্থক্যটা মনে রাখা কাজে দেয়।

দুটি খালি লাইন। ফাংশনের সংজ্ঞার পর দুটি খালি লাইন রাখা পাইথনের প্রচলিত রীতি। পাইথন এক লাইনেও চলে, কিন্তু সব পাইথন কোড এভাবে লেখা, তাই অভ্যাস করে নেওয়াই ভালো।

return আর print — এক নয়

এটি এই অধ্যায়ের সবচেয়ে গুরুত্বপূর্ণ অংশ, আর নতুনদের সবচেয়ে বড় বিভ্রান্তি।

python
def with_return(x):
    return x * 2


def with_print(x):
    print(x * 2)


a = with_return(5)
b = with_print(5)

print(a)
print(b)
print(a + 1)
text
10
10
None
11

পর্দায় প্রথম দুটি 10 দেখে মনে হয় ফাংশন দুটি একই। তা নয়।

with_return মানটি ফেরত দিয়েছে, তাই a-তে 10 ধরা আছে, আর তার সাথে যোগ করা যাচ্ছে — শেষ লাইনে 11।

with_print মানটি দেখিয়েছে এবং কিছু ফেরত দেয়নি, তাই b হলো None। পর্দায় সংখ্যাটা দেখা গেছে ঠিকই, কিন্তু প্রোগ্রামের হাতে কিছু আসেনি। b + 1 লিখলে TypeError হতো।

নিয়মটি এভাবে মনে রাখুন: print মানুষের জন্য, return প্রোগ্রামের জন্য। হিসাব করার ফাংশন return করবে; দেখানোর কাজটা ডাকা জায়গায় হবে। এতে একই ফাংশন একবার পর্দায় ছাপার কাজে, আরেকবার ফাইলে লেখার কাজে ব্যবহার করা যায়।

return না লিখলে ফাংশন নীরবে None ফেরত দেয় — এটিই append আর .sort()-এর সেই None, যা দ্বাদশ ও ত্রয়োদশ অধ্যায়ে ভুগিয়েছিল। এখন বোঝা যায় ওরা কেন None দিত: ওদের কাজ ছিল কিছু বদলানো, কিছু ফেরত দেওয়া নয়।

return সাথে সাথে থামিয়ে দেয়

python
def check(mark):
    if mark >= 40:
        return "pass"
    return "fail"


print(check(72))
print(check(20))
text
pass
fail

return চলা মাত্র ফাংশন শেষ — বাকি লাইনগুলো আর চলে না। তাই এখানে else লাগেনি: প্রথম return চললে দ্বিতীয় লাইনে পৌঁছানোই যায় না।

এই ধরনটি — শর্ত মিললে সাথে সাথে return — নবম অধ্যায়ের গভীর if/else গাছগুলোকে সমতল করে দেয়, আর কোড পড়া অনেক সহজ হয়।

প্রান্ত সামলানো — গার্ড

এই অধ্যায়ের শুরুর সমস্যাটা মনে আছে? খালি লিস্ট:

python
def average(values):
    return sum(values) / len(values)


print(average([]))
text
ZeroDivisionError: division by zero

সমাধানটি ফাংশনের একেবারে শুরুতে বসে:

python
def average(values):
    if not values:
        return 0.0
    return round(sum(values) / len(values), 2)


print(average([72, 45]))
print(average([]))
text
58.5
0.0

if not values — অষ্টম অধ্যায়ের সত্যতার নিয়ম আবার: খালি লিস্ট মিথ্যা, তাই not সেটিকে সত্য করে। len(values) == 0 লেখার দরকার নেই।

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

0.0 কি ঠিক উত্তর? আলোচনার বিষয়। শূন্যটি «কারও নম্বর নেই» আর «সবাই শূন্য পেয়েছে» — দুটোকে এক করে ফেলে। বাস্তব প্রোগ্রামে অনেক সময় None ফেরত দেওয়া বা চতুর্বিংশ অধ্যায়ের raise ব্যবহার করাই সৎ। এখানে 0.0 রাখা হয়েছে সরলতার জন্য — কিন্তু সিদ্ধান্তটি যে একটি সিদ্ধান্ত, সেটা জেনে রাখুন।

ডকস্ট্রিং

ফাংশনের প্রথম লাইনে একটি লেখা রাখলে সেটি হয়ে যায় তার নথি:

python
def average(values):
    """Return the mean of values, or 0.0 when there are none."""
    if not values:
        return 0.0
    return round(sum(values) / len(values), 2)


print(average([1, 2, 3]))
print(average.__doc__)
text
2.0
Return the mean of values, or 0.0 when there are none.

কমেন্টের থেকে এর পার্থক্য হলো এটি প্রোগ্রামের ভেতরেই থেকে যায় — এডিটর এটি দেখায়, help(average) এটি ছাপে।

ভালো ডকস্ট্রিং বলে কী ফেরত আসে আর প্রান্তে কী হয়, কোডটা কীভাবে কাজ করে তা নয়। কোডটা তো নিচেই আছে।


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

report.py:

python
# Three small functions, each doing one thing
def average(values):
    """Return the mean of values, or 0.0 when there are none."""
    if not values:
        return 0.0
    return round(sum(values) / len(values), 2)


def grade(mark):
    """Turn a mark into a letter."""
    if mark >= 80:
        return "A"
    if mark >= 70:
        return "B"
    if mark >= 40:
        return "C"
    return "F"


def summarise(name, marks):
    """Build one report line for a person."""
    mean = average(marks)
    return f"{name:<8} {len(marks):>2} papers  avg {mean:>6}  grade {grade(mean)}"


people = {
    "rafi": [72, 88, 61],
    "ahmed": [45, 38],
    "dia": [],
}

for name, marks in people.items():
    print(summarise(name, marks))
text
rafi      3 papers  avg  73.67  grade B
ahmed     2 papers  avg   41.5  grade C
dia       0 papers  avg    0.0  grade F

চারটি জিনিস লক্ষ করার মতো।

প্রতিটি ফাংশন একটি কাজ করে। average গড় বের করে, grade অক্ষর দেয়, summarise একটি লাইন গড়ে। কোনোটিই ছাপে না — ছাপার কাজটা একেবারে নিচের লুপে, একটিমাত্র জায়গায়। তাই এই রিপোর্টটি ফাইলে লিখতে হলে কেবল ওই এক লাইন বদলাতে হবে।

ফাংশন ফাংশনকে ডাকছে। summarise ডাকছে average আর grade। এটিই ফাংশনের আসল লাভ: ছোট ছোট নির্ভরযোগ্য টুকরো জোড়া দিয়ে বড় কাজ।

grade-এ কোনো else নেই। প্রতিটি return নিজেই থামিয়ে দেয়, তাই শর্তগুলো সমতল থাকে। নবম অধ্যায়ের নিয়মটি এখানেও খাটে: সবচেয়ে কঠিন শর্ত আগে — 80 আগে না লিখে 40 আগে লিখলে সবাই C পেত।

dia-র খালি লিস্টটি প্রোগ্রাম ভাঙেনি। গার্ডটি 0.0 দিয়েছে, grade সেটিকে F বানিয়েছে, আর রিপোর্টের সারিটি স্বাভাবিকভাবে ছাপা হয়েছে। প্রান্তিক অবস্থা একবার সামলানো হয়েছে, একটি জায়গায়।


কিছু ভাঙা অবস্থা ও তার সমাধান

NameError: name 'greet' is not defined — অথচ ফাংশনটি লেখা আছে ফাংশনটি ডাকা হয়েছে তার def লাইনটি চলার আগে। পাইথন ফাইলটি উপর থেকে নিচে পড়ে, আর def চলার আগে নামটি তৈরি হয় না। সব def উপরে রাখুন, আর ডাকার কোডটা নিচে।

TypeError: average() missing 1 required positional argument: 'values' কল করার সময় আর্গুমেন্ট দেওয়া হয়নি — average() লেখা হয়েছে, average(marks) নয়। বার্তাটি কোন প্যারামিটারটি বাদ পড়েছে তার নামও বলে দেয়।

ফাংশনটি None ফেরত দিচ্ছে return লেখা হয়নি, অথবা return লেখা আছে কিন্তু ভুল ইন্ডেন্টে — যেমন একটি if-এর ভেতরে, যেখানে সেই শর্তটি মেলে না। আরও একটি সাধারণ কারণ: print লেখা হয়েছে যেখানে return দরকার ছিল।

ফাংশনের ভেতরের ভেরিয়েবল বাইরে পাওয়া যাচ্ছে না সেটাই স্বাভাবিক — কেন, তা একবিংশ অধ্যায়ের বিষয়। আপাতত: যা বাইরে দরকার তা return করুন।

SyntaxError — def লাইনের শেষে কোলন নেই if আর for-এর মতোই, def লাইনটিও কোলনে শেষ হয়।

IndentationError: expected an indented block after function definition def লাইনের পর অন্তত একটি ইন্ডেন্ট করা লাইন থাকতে হবে।