এই পর্বে যা শিখবেন
- লুপ কেন লাগে, আর Python-এর for কেন C-র for-এর মতো নয় — “প্রতিটির জন্য” ভাবনা
range()-এর তিন রূপ, উল্টো গোনা, আর range কেন অলসenumerate(),zip(),reversed(),sorted()দিয়ে পরিষ্কার লুপ লেখা- while, সেন্টিনেল লুপ,
break,continueআর লুপেরelse— কখন কোনটি - লুপের তিনটি পরিচিত ভুল ধরা, আর কখন লুপের বদলে built-in ব্যবহার করবেন
১ · লুপ কেন — একই কাজ বারবার
আগের পর্বে আপনি প্রোগ্রামকে সিদ্ধান্ত নিতে শিখিয়েছেন — if দিয়ে এই পথ, নইলে ওই পথ। কিন্তু সিদ্ধান্তের কোড একবারই চলে। ভাবুন, একটি স্কুলের ৬০ জন শিক্ষার্থীর নম্বর দেখে প্রত্যেকের ফল লিখতে হবে। একই তিন লাইনের কোড ৬০ বার কপি করবেন? ৬০০ জন হলে? একজন নতুন শিক্ষার্থী ভর্তি হলে আবার কোড বদলাবেন?
এখানেই loop-এর দরকার। লুপ মানে একই কাজ বারবার করানো — কাজটি আপনি একবার লেখেন, কম্পিউটার যতবার দরকার ততবার চালায়। লুপের প্রতিটি ঘোরাকে বলে একটি iteration. কম্পিউটারের আসল শক্তি এখানেই: মানুষ ৬০টি খাতা দেখতে ক্লান্ত হয়, কম্পিউটার ৬০ লাখটি দেখেও ক্লান্ত হয় না।
Python-এ লুপ দুই রকম, আর দুটির ভাবনা আলাদা:
🔁 for — “প্রতিটির জন্য”
হাতে একটি সংগ্রহ আছে (নামের তালিকা, একটি লেখা, ১ থেকে ১২ মাস)। তার প্রতিটি জিনিসের জন্য একবার কাজটি করো। কতবার ঘুরবে, তা সংগ্রহটিই ঠিক করে।
for name in names:⏳ while — “যতক্ষণ সত্য”
একটি শর্ত যতক্ষণ সত্য, ততক্ষণ কাজটি করতে থাকো। কতবার ঘুরবে আগে জানা নেই — ব্যবহারকারী কখন “done” লিখবেন, কেউ জানে না।
while entry != "done":ক্লাসের রোল কলের কথা ভাবুন। শিক্ষক হাজিরা খাতা খুলে প্রতিটি নামের জন্য একবার ডাকেন — খাতায় যতজন, ততবার। এটা for. আবার চায়ের দোকানে কেতলি চুলায় বসিয়ে দোকানি দেখেন — পানি ফোটেনি? আরেকটু অপেক্ষা। ফুটলেই নামিয়ে ফেলেন। কতবার উঁকি দেবেন আগে জানেন না, শুধু শর্তটা জানেন। এটা while.
বেশিরভাগ লুপে আসল প্রশ্ন “কতবার?” নয় — “কোন জিনিসগুলোর ওপর?” Python এই প্রশ্নটিকে ভাষার কেন্দ্রে বসিয়েছে। তাই Python-এ for সবচেয়ে বেশি ব্যবহৃত লুপ, আর while রাখা হয় সেই কাজের জন্য, যেখানে থামার মুহূর্ত শুধু একটি শর্ত দিয়ে বলা যায়।
২ · for লুপ — প্রতিটি জিনিসের জন্য একবার
for লুপের গঠন একটি ইংরেজি বাক্যের মতো: for letter in letters: — “letters-এর প্রতিটি letter-এর জন্য”। কোলনের পরের ঢুকিয়ে লেখা (indentation দেওয়া) লাইনগুলো হলো লুপের body — প্রতিটি ঘোরায় এগুলো চলে। আর letter হলো loop variable: প্রতিটি ঘোরার শুরুতে Python সংগ্রহের পরের জিনিসটি এই নামে লাগিয়ে দেয়।
পর্ব ০৫-এর লেবেল-ছবিটি মনে আছে? Loop variable-ও একটি লেবেল। প্রতি ঘোরায় লেবেলটি খুলে পরের object-এর গায়ে লাগানো হয় — কোনো কপি হয় না। নিচের ধাপগুলোতে দেখুন, letter লেবেলটি কীভাবে এক বর্ণ থেকে আরেক বর্ণে সরে যায়, আর লুপ শেষেও কোথায় থেকে যায়:
1letters = ["ক", "খ", "গ"]2for letter in letters:3 print(letter, "পড়লাম")4print("শেষে letter =", letter)
| letters | |
| পরের জিনিস কোনটি | |
| letter |
- শুধু একটি list তৈরি। তিনটি বর্ণের একটি list. লুপ এখনো শুরু হয়নি। List নিয়ে পুরো আলোচনা পর্ব ১৪-এ; এখানে শুধু জানুন — এটি ক্রম মেনে রাখা কয়েকটি জিনিসের সারি।
- প্রথম ঘোরা। for লুপ list-কে জিজ্ঞেস করল “প্রথম জিনিস কী?” — পেল
"ক".letterলেবেলটি"ক"-এর গায়ে লাগল, তারপর body চলল। লক্ষ করুন — কোথাওi = 0বাi += 1নেই। - দ্বিতীয় ঘোরা। “পরের জিনিস?” —
"খ". লেবেলটি"ক"থেকে খুলে"খ"-তে লাগল। Body আবার চলল। একই দুই লাইনের কোড, ভিন্ন object. - তৃতীয় ঘোরা।
"গ"এল, body চলল। List-এ যতগুলো জিনিস, লুপ ঠিক ততবার ঘুরল — আপনাকে সংখ্যাটি কোথাও লিখতে হয়নি। List-এ আরেকটি বর্ণ যোগ করলে লুপ নিজেই চারবার ঘুরত। - জিনিস ফুরাল, লুপ শেষ। “পরের জিনিস?” — উত্তর “আর নেই”. লুপ থেমে পরের লাইনে গেল। চমকের বিষয়:
letterনামটি মুছে যায়নি, শেষ মান"গ"-তেই লেগে আছে। Python-এ loop variable লুপের বাইরেও টিকে থাকে — অংশ ৭-এ এর ফাঁদ দেখবেন।
for লুপ শুধু list-এ নয়, যেকোনো iterable-এ চলে — অর্থাৎ এমন যেকোনো object, যার কাছে “জিনিসগুলো একটি একটি করে দাও” বলা যায়। কয়েকটি উদাহরণ:
| যার ওপর লুপ | প্রতি ঘোরায় কী পান | বিস্তারিত কোথায় |
|---|---|---|
list — ["ক", "খ", "গ"] | একটি করে উপাদান, ক্রম মেনে | পর্ব ১৪ |
string — "ঢাকা" | একটি করে অক্ষর (Unicode code point) | পর্ব ০৯ |
range(1, 13) | একটি করে সংখ্যা: 1, 2, … 12 | অংশ ৩ |
| dictionary | একটি করে চাবি (key) | পর্ব ১৬ |
| খোলা ফাইল | একটি করে লাইন | পর্ব ২৫ |
বাংলা string-এ লুপ চালালে একটু সাবধান। for ch in "কি" দুটি জিনিস দেয় — "ক" আর "ি" (কার চিহ্ন আলাদা code point). চোখে যা একটি “অক্ষর”, Python-এর কাছে তা একাধিক হতে পারে। এর কারণ পর্ব ০৯-এ Unicode আলোচনায় দেখেছেন।
“প্রতিটির জন্য” ধাঁচের লুপ Python নতুন আনেনি। Guido van Rossum Python বানানোর আগে নেদারল্যান্ডসের CWI-তে ABC নামের একটি শিক্ষণ-ভাষার দলে কাজ করতেন, আর ABC-তে লুপ লেখা হতো সংগ্রহের ওপর — FOR name IN collection ধাঁচে। Unix shell-এও বহু আগে থেকে for f in *.txt লেখা যেত। Python এই ধারা নিয়েছে, আর C-র তিন-অংশের for(;;) নেয়নি। পরে অন্য ভাষাগুলোও একই দিকে হেঁটেছে: Java 5 (২০০৪)-এ “enhanced for”, C++11-এ range-based for, JavaScript-এ ২০১৫ সালে for...of.
৩ · range() — সংখ্যার সারি, কিন্তু অলস
কখনো সত্যিই সংখ্যা গুনতে হয় — ১২ মাসের হিসাব, ১ থেকে ১০-এর নামতা। তখন ব্যবহার করুন range. এর তিনটি রূপ:
| লেখা | যে সংখ্যাগুলো দেয় | মনে রাখার উপায় |
|---|---|---|
range(5) | 0, 1, 2, 3, 4 | ০ থেকে শুরু, ৫টি সংখ্যা, 5 নিজে নেই |
range(1, 13) | 1, 2, … 12 | start আছে, stop নেই — ১২ মাস |
range(2, 11, 2) | 2, 4, 6, 8, 10 | তৃতীয় সংখ্যা হলো step (লাফ) |
range(10, 0, -3) | 10, 7, 4, 1 | ঋণাত্মক step-এ উল্টো গোনা; 0 নেই |
range(5, 1) | (কিছুই না) | step ধনাত্মক অথচ start বড় — খালি |
stop কখনো ভেতরে থাকে না। range(a, b)-এ ঠিক b - aটি সংখ্যা থাকে। শুরুতে অদ্ভুত লাগে, কিন্তু এর সুবিধা বড়: range(0, 5) আর range(5, 10) জোড়া দিলে ঠিক range(0, 10) — মাঝখানে কোনো সংখ্যা দুবার আসে না, বাদও পড়ে না। string-এর slicing (s[0:5])-ও ঠিক একই নিয়ম মানে।
এবার একটি বাস্তব হিসাব। ধরুন একজন গার্মেন্টস কর্মী প্রতি মাসে ১,৫০০ টাকা সঞ্চয় করবেন। ১২ মাসে কত জমবে, আর প্রতি তিন মাস পরপর অবস্থা কেমন — দেখা যাক:
savings = 1500
total = 0
for month in range(1, 13): # 1 থেকে 12 — 13 নেই
total += savings
if month % 3 == 0: # প্রতি তিন মাস পরপর
print(f"মাস {month:2}: মোট {total} টাকা")মাস 3: মোট 4500 টাকা
মাস 6: মোট 9000 টাকা
মাস 9: মোট 13500 টাকা
মাস 12: মোট 18000 টাকা
লুপের আগে total = 0, ভেতরে total += savings — এই জুটির নাম accumulator প্যাটার্ন: একটি “জমার খাতা” খুলে প্রতি ঘোরায় তাতে কিছু যোগ করা। অংশ ৯-এ দেখবেন, এমন অনেক খাতার কাজ Python-এর built-in নিজেই করে দেয়।
range কেন অলস
range(1, 13) লিখলে মেমরিতে ১২টি সংখ্যার কোনো list তৈরি হয় না। range object শুধু তিনটি জিনিস মনে রাখে — start, stop, step. লুপ যখন পরের সংখ্যা চায়, তখনই সেটি হিসাব করে দেয়। একে বলে lazy আচরণ: দরকারের আগে কোনো কাজ নয়। ফলাফল?
range(10**12)— এক লাখ কোটি সংখ্যার range — তৈরি হয় চোখের পলকে, আর মেমরি নেয় ছোট্ট এক range-এর সমান।- তবু এটি একটি পূর্ণ sequence:
len(range(1, 13))দেয় 12,range(1, 13)[0]দেয় 1, আর100 in range(0, 10**12, 2)গুনে গুনে না দেখেই সঙ্গে সঙ্গেTrueবলে — কারণ এটি সূত্র দিয়ে হিসাব করে। - সংখ্যাগুলো সত্যিই list হিসেবে চাইলে
list(range(5))লিখুন — তখন[0, 1, 2, 3, 4]তৈরি হবে।
Off-by-one — লুপের সবচেয়ে পুরোনো ভুল। “১ থেকে ১০ পর্যন্ত” চাইলে range(1, 10) লিখলে 10 বাদ পড়বে; লিখতে হবে range(1, 11). আবার উল্টো গুনতে গিয়ে range(10, 0, -1) লিখলে 0 আসবে না — 0 পর্যন্ত চাইলে range(10, -1, -1). সন্দেহ হলে REPL-এ list(range(...)) লিখে এক সেকেন্ডে দেখে নিন — অনুমানের চেয়ে দেখা ভালো।
৪ · enumerate, zip, reversed, sorted — index না গুনেই
C বা অন্য ভাষা থেকে আসা অনেকে (আর অনেক AI-ও) Python-এ এভাবে লেখেন: for i in range(len(names)):, তারপর ভেতরে names[i]. কাজ করে, কিন্তু এটি Python-এর ভাষায় “বিদেশি উচ্চারণ”। প্রায় সব ক্ষেত্রেই এর চেয়ে পরিষ্কার একটি উপায় আছে।
enumerate() — জিনিসের সাথে তার ক্রমিক নম্বর
রোল নম্বরসহ নাম ছাপতে চান? enumerate প্রতিটি জিনিসের সাথে একটি গণনা জুড়ে দেয়: for roll, name in enumerate(names, start=1):. start=1 না দিলে গণনা শুরু হয় 0 থেকে — মানুষের জন্য তালিকা ছাপতে start=1 প্রায় সবসময় ভালো।
zip() — দুটি সারি পাশাপাশি
একটি list-এ নাম, আরেকটিতে নম্বর। দুটিকে জোড়ায় জোড়ায় নিতে zip: for name, mark in zip(names, marks):. নামটা এসেছে জ্যাকেটের চেইন (zipper) থেকে — দুই পাশের দাঁত একটা একটা করে মিলে যায়।
দুটি একসাথে মেলালে কী হয়, ধাপে ধাপে দেখুন। (name, mark)-এর মতো বন্ধনী দিয়ে একটি জোড়াকে দুটি নামে ভাগ করাকে বলে unpacking — পর্ব ১৫-এ বিস্তারিত।
1names = ["রহিম", "সুমি", "করিম"]2marks = [78, 91, 29]3for roll, (name, mark) in enumerate(zip(names, marks), start=1):4 print(roll, name, mark)
| zip দিল | |
| enumerate দিল | |
| roll | |
| name | |
| mark |
- দুটি আলাদা list. একই ক্রমে সাজানো — প্রথম নাম মানে প্রথম নম্বর। এই “একই ক্রম” চুক্তিটিই zip-এর ভিত্তি। এখনো কোনো জোড়া তৈরি হয়নি — zip আর enumerate দুটোই অলস, চাওয়ার আগে কিছু বানায় না।
- প্রথম ঘোরা — তিন স্তরে। for লুপ enumerate-কে পরের জিনিস চাইল। enumerate চাইল zip-এর কাছে; zip দুই list থেকে প্রথম জিনিস নিয়ে জোড়া বানাল
("রহিম", 78). enumerate তার সাথে গণনা 1 জুড়ে দিল। শেষে unpacking:roll= 1,name= “রহিম”,mark= 78. - দ্বিতীয় ঘোরা। একই শিকল আবার — zip দিল
("সুমি", 91), enumerate জুড়ল 2. আপনি কোথাওnames[1]বাmarks[1]লেখেননি, তাই ভুল index-এ হাত দেওয়ার সুযোগই নেই। - তৃতীয় ঘোরা।
("করিম", 29)আর 3. একটি লাইনে দুটি কাজ হলো: পাশাপাশি হাঁটা (zip) আর গোনা (enumerate). শিকলের ক্রম খেয়াল করুন —enumerate(zip(...)), উল্টো নয়। - Zip ফুরাল, সব থামল। list-এ আর কিছু নেই, তাই zip “শেষ” জানাল; enumerate-ও থামল; লুপ শেষ। একটি list যদি আরেকটির চেয়ে লম্বা হতো, zip ছোটটি ফুরানো মাত্রই থামত — লম্বাটির বাড়তি জিনিস চুপচাপ বাদ পড়ত।
Zip চুপচাপ ছেঁটে দেয়। চারজনের নাম আর তিনটি নম্বর দিলে zip তিনটি জোড়া দেবে, চতুর্থ নামটি কোনো সতর্কবার্তা ছাড়াই বাদ। ডেটা ভুল থাকলে এটি বিপজ্জনক — একজন শিক্ষার্থী ফলাফল থেকে উধাও! Python 3.10 থেকে zip(names, marks, strict=True) লিখলে দৈর্ঘ্য না মিললে ValueError আসে। যেখানে দুই সারির দৈর্ঘ্য সমান হওয়ারই কথা, সেখানে strict=True দেওয়া ভালো অভ্যাস।
reversed() আর sorted()
reversed(names) উল্টো দিক থেকে হাঁটে — নতুন list বানায় না, শুধু পেছন থেকে একটি একটি করে দেয়। sorted(marks) ছোট থেকে বড় সাজানো একটি নতুন list দেয়; মূল list যেমন ছিল তেমনই থাকে। সাজানোর আরও কৌশল (বড় থেকে ছোট, নিজের নিয়মে সাজানো) পর্ব ১৪-এ দেখবেন। এবার সব একসাথে চালিয়ে দেখুন:
names = ["রহিম", "সুমি", "করিম", "নাদিয়া"]
marks = [78, 91, 29, 65]
for roll, (name, mark) in enumerate(zip(names, marks), start=1):
status = "পাস" if mark >= 33 else "ফেল"
print(f"{roll}. {name}: {mark} ({status})")
print("মোট নম্বর:", sum(marks))
print("সর্বোচ্চ:", max(marks))
print("সবাই পাস?", all(m >= 33 for m in marks))
print("কেউ ৯০+ পেয়েছে?", any(m >= 90 for m in marks))
extra = ["তানভীর"] # নম্বর নেই — zip একে বাদ দেবে
print(list(zip(names + extra, marks))[-1])
for name in reversed(names):
print(name, end=" ")
print()
print(sorted(marks), marks)প্রথম চার লাইনের আউটপুট রোলসহ ফলাফল — করিম 29 পেয়ে “ফেল”. এরপর sum দেয় 263, max দেয় 91 — কোনো লুপ লিখতে হয়নি। all(...) জিজ্ঞেস করে “সবাই কি ৩৩ বা তার বেশি?” — করিমের জন্য False. any(...) জিজ্ঞেস করে “অন্তত একজন কি ৯০+?” — সুমির জন্য True. (বন্ধনীর ভেতরের m >= 33 for m in marks হলো একটি ছোট generator expression — পর্ব ১৮-এ শিখবেন; আপাতত পড়ুন “প্রতিটি m-এর জন্য, m কি ৩৩ বা বেশি”।) তারপর পাঁচটি নাম আর চারটি নম্বর zip করলে শেষ জোড়া ('নাদিয়া', 65) — তানভীর নিঃশব্দে বাদ। reversed নামগুলো উল্টো ছাপে, আর শেষ লাইন দেখায় sorted নতুন সাজানো list দিয়েছে, মূল marks অপরিবর্তিত।
৫ · while লুপ — শর্ত যতক্ষণ সত্য
while condition: — প্রতিটি ঘোরার আগে Python শর্তটি দেখে। সত্য হলে body চালায়, তারপর আবার শর্ত দেখে। মিথ্যা হলেই লুপ শেষ। শুরুতেই মিথ্যা হলে body একবারও চলে না।
একটি ছোট উদাহরণ — সংখ্যার অঙ্ক গোনা। কতবার ঘুরবে, তা সংখ্যার ওপর নির্ভর করে; আগে থেকে বলা যায় না:
n = 20261017
digits = 0
while n > 0:
n //= 10 # শেষ অঙ্কটি ফেলে দাও
digits += 1
print(digits) # 8while-এর একটি অলিখিত চুক্তি আছে: body-র ভেতরে এমন কিছু ঘটতেই হবে, যা একসময় শর্তটিকে মিথ্যা করবে। এখানে n //= 10 সেই কাজ করছে। এই লাইনটি ভুলে গেলে n কখনো ছোট হবে না, আর লুপ চিরকাল ঘুরবে — একে বলে infinite loop. প্রোগ্রাম আটকে গেলে টার্মিনালে Ctrl+C চাপুন; Python KeyboardInterrupt দিয়ে থেমে যাবে।
সেন্টিনেল লুপ — “done” না লেখা পর্যন্ত
একটি দোকানের দিনশেষের হিসাব ভাবুন। দোকানি একটি একটি করে বিক্রির টাকা লিখবেন, শেষ হলে লিখবেন done. কয়টি বিক্রি, কেউ আগে জানে না। এমন একটি বিশেষ “থামার সংকেত”-কে বলে sentinel (পাহারাদার)। Python-এ এর প্রচলিত গঠন হলো while True: দিয়ে চিরকালের লুপ শুরু করা, আর ভেতরে সংকেত পেলেই break দিয়ে বেরিয়ে যাওয়া:
total = 0
count = 0
biggest = 0
while True:
entry = input() # আসল প্রোগ্রামে: input("বিক্রি: ")
if entry == "done":
break # সেন্টিনেল পেলেই লুপ শেষ
if not entry.isdigit():
print("সংখ্যা নয়, বাদ দিলাম:", entry)
continue # বাকি অংশ বাদ, পরের ইনপুটে যাও
amount = int(entry)
total += amount
count += 1
if amount > biggest:
biggest = amount
print(f"মোট {count}টি বিক্রি, {total} টাকা")
print(f"সবচেয়ে বড় বিক্রি: {biggest} টাকা")এই কোড চালানোর সময় পাঁচ লাইনের ইনপুট দেওয়া হয়েছে: 250, 1200, abc, 480 আর done. প্রথম দুটি যোগ হলো। abc সংখ্যা নয়, তাই একটি সতর্কবার্তা ছেপে continue সরাসরি পরের ঘোরায় চলে গেল — নিচের যোগ-গণনা বাদ। 480 যোগ হলো। done পেয়ে break লুপ থেকে বের করে দিল। আউটপুট: “মোট 3টি বিক্রি, 1930 টাকা” আর “সবচেয়ে বড় বিক্রি: 1200 টাকা”. তিনটি accumulator একসাথে চলেছে — যোগফল (total), গণনা (count), সর্বোচ্চ (biggest). এখানে max() ব্যবহার করা যায়নি, কারণ সংখ্যাগুলো কোনো list-এ জমা নেই — একটি একটি করে আসছে। Prompt-টি সরানো হয়েছে, কারণ আগে থেকে দেওয়া ইনপুটে prompt-গুলো এক লাইনে জট পাকিয়ে যেত; নিজের কম্পিউটারে input("বিক্রি: ") লিখুন।
Python-এ C-র মতো do { … } while (…); নেই — যে লুপ অন্তত একবার চলবেই। এর কাজ while True: আর ভেতরে শর্তসহ break দিয়েই করা হয়, ঠিক উপরের মতো। Python 3.8 থেকে walrus অপারেটর দিয়ে (পর্ব ০৮) লেখা যায় while (entry := input()) != "done": — ছোট, তবে নতুনদের জন্য while True রূপটি পড়তে সহজ।
৬ · break, continue আর লুপের else
দুটি কীওয়ার্ড লুপের স্বাভাবিক চলার পথ বদলায়। দুটিই কাজ করে শুধু সবচেয়ে ভেতরের লুপে:
break— “এখনই লুপ থেকে বের হও।” বাকি ঘোরাগুলো আর হবে না। খোঁজার কাজে উত্তর পেয়ে গেলে, বা সেন্টিনেল পেলে।continue— “এই ঘোরার বাকিটা বাদ, পরের ঘোরায় যাও।” ভুল ইনপুট বা অপ্রাসঙ্গিক জিনিস এড়াতে।
এবার Python-এর একটি বিরল বৈশিষ্ট্য — লুপের else. for বা while-এর পরে else: লিখলে সেই অংশ চলে শুধু তখন, যখন লুপ break ছাড়া স্বাভাবিকভাবে শেষ হয়। এর আসল ব্যবহার খোঁজার কাজে: “খুঁজে পেলে break; পুরো তালিকা দেখেও না পেলে else.” একজন শিক্ষার্থীর ফলাফলে প্রথম যে বিষয়ে ফেল, সেটি খোঁজা যাক — একবার ফেল আছে এমন ফলাফলে, একবার সব পাস এমন ফলাফলে:
1for subject, mark in results:2 if mark < 33:3 print("প্রথম ফেল:", subject)4 break5else:6 print("সব বিষয়ে পাস!")
| results | |
| subject, mark | |
| mark < 33? | |
| else চলবে? |
- ফলাফল ক, প্রথম ঘোরা। বাংলা 72 — ৩৩-এর কম নয়, তাই
if-এর ভেতরে ঢোকা হলো না। লুপ পরের বিষয়ে গেল। - গণিত 28 — পাওয়া গেল! শর্ত সত্য, “প্রথম ফেল: গণিত” ছাপা হলো। আমরা “প্রথম” ফেল খুঁজছি, তাই এখানেই থামা উচিত।
break— else বাদ। লুপ থেকে সরাসরি বেরিয়ে গেল। ইংরেজি 20 আর দেখাই হলো না (দরকারও নেই)। আর যেহেতু লুপ break দিয়ে শেষ হয়েছে,elseঅংশ চলল না — “সব বিষয়ে পাস!” ভুল করে ছাপা হলো না।- ফলাফল খ, প্রথম ঘোরা। এবার নতুন একজন শিক্ষার্থী। বাংলা 72 — পাস, পরের বিষয়ে।
- গণিত 64 — এটিও পাস।
if-এর ভেতরে ঢোকা হলো না, break-ও হলো না। তালিকা এখানেই শেষ। - লুপ নিজে নিজে শেষ — else চলল। সব বিষয় দেখা হয়েছে, একবারও break হয়নি, তাই
elseচলল: “সব বিষয়ে পাস!”. else-কে পড়ুন “no break” হিসেবে — তাহলে আর গুলিয়ে যাবে না।
for/else ছাড়া এই কাজ করতে সাধারণত একটি “পতাকা” ভ্যারিয়েবল লাগে — found = False, খুঁজে পেলে found = True, লুপের পরে if not found:. for/else সেই বাড়তি ভ্যারিয়েবল বাঁচায়। এটি আসলে linear search-এর একটি পরিষ্কার রূপ।
সৎ পরামর্শ: for/else নিয়ে অভিজ্ঞ Python প্রোগ্রামারদের মধ্যেও মতভেদ আছে। “else” শব্দটি শুনে বেশিরভাগ মানুষ ভাবেন “লুপ একবারও না ঘুরলে” — যা ভুল। তাই অনেকে মনে করেন এটি কোড পড়াকে কঠিন করে। ব্যবহার করলে else: # no break লিখে একটি comment দিন। আর অনেক সময় এর চেয়েও পরিষ্কার উপায় আছে — খোঁজার কাজটি একটি ফাংশনে রেখে পেলেই return করা (পর্ব ১৩), বা শুধু “আছে কি না” জানতে any().
৭ · লুপের ভেতরে লুপ — নামতা আর loop variable-এর ফাঁদ
একটি লুপের body-তে আরেকটি লুপ থাকতে পারে — একে বলে nested loop. বাইরের লুপের প্রতিটি ঘোরায় ভেতরের লুপ পুরোটা ঘোরে। স্কুলের নামতার কথা ভাবুন: ১-এর ঘর, ২-এর ঘর, ৩-এর ঘর (বাইরের লুপ); প্রতিটি ঘরে ১ থেকে ৫ পর্যন্ত গুণ (ভেতরের লুপ)।
for i in range(1, 4): # বাইরের লুপ: কোন ঘরের নামতা
for j in range(1, 6): # ভেতরের লুপ: কত দিয়ে গুণ
print(f"{i}x{j}={i*j:<2}", end=" ")
print() # এক ঘর শেষ, নতুন লাইন1x1=1 1x2=2 1x3=3 1x4=4 1x5=5
2x1=2 2x2=4 2x3=6 2x4=8 2x5=10
3x1=3 3x2=6 3x3=9 3x4=12 3x5=15
print()-এর end=" " বলে দেয় — লাইন শেষে নতুন লাইন নয়, দুটি ফাঁকা জায়গা দাও। তাই একটি ঘরের পাঁচটি গুণ এক লাইনে আসে। বাইরের লুপের print() (ভেতরের লুপের বাইরে, কিন্তু বাইরের লুপের ভেতরে — indentation দেখুন) প্রতিটি ঘর শেষে নতুন লাইন দেয়।
মোট কতবার ভেতরের body চলল? ৩ × ৫ = ১৫ বার। এটাই nested loop-এর গুরুত্বপূর্ণ হিসাব: বাইরে n বার, ভেতরে m বার হলে মোট n × m. ১,০০০ শিক্ষার্থীর প্রত্যেককে প্রত্যেকের সাথে তুলনা করলে ১০ লাখ তুলনা — ছোট ডেটায় নিরীহ, বড় ডেটায় প্রোগ্রাম ধীর হয়ে যাওয়ার সবচেয়ে সাধারণ কারণ।
nested loop থেকে একবারে পুরো বেরোনোর কোনো break 2 Python-এ নেই — break শুধু ভেতরের লুপ থামায়। দুই স্তর থেকে বেরোতে হলে সবচেয়ে পরিষ্কার উপায় হলো লুপ দুটিকে একটি ফাংশনে রেখে return করা (পর্ব ১৩)।
Loop variable লুপের পরেও বেঁচে থাকে
C বা Java-তে লুপের ভেতরে ঘোষণা করা ভ্যারিয়েবল লুপ শেষ হলেই হারিয়ে যায়। Python-এ তা নয়। for i in range(5): শেষ হওয়ার পর i-এর মান 4 — শেষ যে মান পেয়েছিল। প্রথম স্টেপারে letter-এর ক্ষেত্রেও তাই দেখেছেন। এর দুটি ফাঁদ:
- পুরোনো মান ভুল করে ব্যবহার: লুপের পরে একই নাম আবার ব্যবহার করলে আপনি ভাবতে পারেন এটি নতুন, অথচ এতে লুপের শেষ মান রয়ে গেছে। নামতার পরে
iআরj-এর মান 3 আর 5. - খালি লুপে নামই তৈরি হয় না:
for x in []:একবারও ঘোরে না, তাইxকখনো তৈরি হয় না। লুপের পরেprint(x)লিখলেNameError— কিন্তু শুধু তখনই, যখন তালিকা খালি ছিল। এমন বাগ পরীক্ষায় লুকিয়ে থাকে, আসল ডেটায় ফাটে।
পরামর্শ: loop variable-কে লুপের ভেতরেই ব্যবহার করুন। লুপের পরে কোনো মান লাগলে আলাদা, স্পষ্ট নামের একটি ভ্যারিয়েবলে রাখুন (যেমন last_month). পর্ব ১৮-এর comprehension-এ কিন্তু loop variable বাইরে আসে না — Python 3 থেকে সেখানে নিয়ম আলাদা।
৮ · লুপের তিনটি পরিচিত ভুল
ভুল ১: যে list-এর ওপর লুপ চলছে, তাকেই বদলানো
ধরুন নম্বরের list থেকে ফেল করা (৩৩-এর কম) নম্বরগুলো মুছে দিতে চান। সবচেয়ে স্বাভাবিক মনে হয় এটা: লুপ চালাও, ৩৩-এর কম পেলে remove করো। দেখুন কী ঘটে:
1for m in marks:2 if m < 33:3 marks.remove(m)4print(marks)
| ভেতরের গোপন index | |
| m | |
| marks |
- index 0 → 30. List-এর ওপর for লুপ ভেতরে একটি গোপন index রাখে — “এখন কত নম্বর ঘরে আছি”. ঘর 0-তে 30, তা ৩৩-এর কম, তাই মুছে ফেলা হলো। মোছার সাথে সাথে বাকি সবাই এক ঘর বাঁয়ে সরে এল: 25 এখন ঘর 0-তে।
- index 1 → 80. লুপ জানে না যে list সরে গেছে। সে নিয়মমতো ঘর 1-এ গেল — সেখানে এখন 80. যে 25 ঘর 0-তে সরে গিয়েছিল, তাকে কখনো দেখাই হলো না. 80 পাস, কিছু হলো না।
- index 2 → 20. 20 মুছল, 90 এক ঘর বাঁয়ে সরল। List এখন তিন ঘরের।
- index 3 — কেউ নেই, লুপ শেষ। 90-কেও আর দেখা হলো না (ভাগ্য ভালো, ওটা পাস)। ফলাফল
[25, 80, 90]— 25 ফেল করা নম্বর হয়েও রয়ে গেছে। কোনো error নেই, শুধু ভুল উত্তর — সবচেয়ে বিপজ্জনক ধরনের বাগ। - সমাধান — পুরোনোটি বদলাবেন না, নতুন বানান। সবচেয়ে পরিষ্কার উপায়: যা রাখতে চান তা দিয়ে নতুন list —
marks = [m for m in marks if m >= 33](comprehension, পর্ব ১৮)। অথবা একটি কপির ওপর লুপ চালান:for m in marks[:]:বাfor m in marks.copy():, আর মুছুন আসলটি থেকে। দুটিই দেয়[80, 90].
Dictionary-র ক্ষেত্রে Python আরও কড়া: লুপ চলার সময় dict-এ নতুন চাবি যোগ বা মোছা করলে সরাসরি RuntimeError আসে (পর্ব ১৬)। List-এ এমন কোনো সতর্কতা নেই — তাই নিয়মটি মনে রাখুন: যার ওপর হাঁটছেন, তার আকার বদলাবেন না।
ভুল ২: off-by-one
অংশ ৩-এ দেখেছেন — stop ভেতরে থাকে না। আরেকটি রূপ হলো for i in range(1, len(names)): — যা প্রথম জিনিসটিই (index 0) বাদ দেয়। “বেড়া আর খুঁটি”-র পুরোনো ধাঁধাও এই পরিবারের: ১০ মিটার বেড়ায় ১ মিটার পরপর খুঁটি দিলে খুঁটি লাগে ১১টি, ১০টি নয়। সন্দেহ হলে সবচেয়ে ছোট উদাহরণ (০টি, ১টি, ২টি জিনিস) হাতে dry run করে দেখুন।
ভুল ৩: থামতে না পারা while
তিনটি পরিচিত কারণ: (ক) শর্তের ভ্যারিয়েবল body-তে বদলানোই হয় না; (খ) বদলায়, কিন্তু ভুল দিকে (count += 1 লিখতে হতো count -= 1); (গ) শর্ত “ঠিক সমান” দিয়ে লেখা, অথচ মান লাফিয়ে পার হয়ে যায় — n = 5 থেকে প্রতিবার ২ কমালে while n != 0 কখনো মিথ্যা হয় না (5, 3, 1, −1, …)। নিরাপদ অভ্যাস: শর্তে !=-এর বদলে > বা < ব্যবহার করুন, আর while True লিখলে প্রথমেই ঠিক করুন break কোথায়।
৯ · যে লুপ লিখতেই হয় না — accumulator বনাম built-in
লুপে বারবার কয়েকটি প্যাটার্ন ফিরে আসে: যোগফল, গণনা, সর্বোচ্চ-সর্বনিম্ন, “কেউ কি…?”, “সবাই কি…?”. প্রতিটির জন্য Python-এ একটি তৈরি built-in function আছে:
| কাজ | হাতে লেখা accumulator | Built-in |
|---|---|---|
| যোগফল | total = 0, লুপে total += x | sum(xs) |
| সর্বোচ্চ / সর্বনিম্ন | প্রথমটি ধরে, লুপে তুলনা | max(xs), min(xs) |
| শর্ত মেনে গণনা | count = 0, if হলে += 1 | sum(1 for x in xs if x >= 33) |
| অন্তত একটি কি সত্য? | flag, পেলে break | any(...) |
| সবগুলো কি সত্য? | flag, না মিললে break | all(...) |
Built-in কেন ভালো? তিনটি কারণ। পড়া সহজ: sum(marks) দেখেই উদ্দেশ্য বোঝা যায়; চার লাইনের লুপ পড়ে বুঝতে হয়। ভুলের সুযোগ কম: total = 0 ভুলে লুপের ভেতরে লিখে ফেলা, বা biggest = 0 দিয়ে শুরু করা (সব নম্বর ঋণাত্মক হলে ভুল উত্তর) — এমন বাগ built-in-এ হয় না। দ্রুত: পর্ব ০৫-এ দেখেছেন, বিশুদ্ধ Python লুপের প্রতিটি ঘোরায় virtual machine-কে কয়েকটি bytecode নির্দেশ চালাতে হয়। sum()-এর লুপটি চলে CPython-এর ভেতরে, C-তে লেখা — তাই একই কাজ সাধারণত কয়েক গুণ দ্রুত হয়।
any() আর all()-এর আরেকটি গুণ: উত্তর জানা মাত্র থেমে যায়। any() প্রথম সত্য পেলেই True দেয়, বাকিগুলো দেখে না; all() প্রথম মিথ্যা পেলেই False. একে বলে short-circuit — পর্ব ০৮-এর and/or-এর মতোই।
না থাকলে তবেই লুপ লিখুন
লাখ লাখ সংখ্যা নিয়ে হিসাব — যেমন ছবির পিক্সেল বা সেন্সরের ডেটা — সেখানে built-in-ও যথেষ্ট নয়। তখন NumPy পুরো array-এর ওপর একবারে কাজ করে, লুপটি পুরোপুরি C-তে চালিয়ে; পর্ব ৪৬-এ দেখবেন। তবে আগেভাগে চিন্তা করবেন না: ১২ মাস, ৬০ জন শিক্ষার্থী, একটি দোকানের দিনের বিক্রি — এমন কাজে সাধারণ for লুপই যথেষ্ট দ্রুত। গতির প্রশ্ন আসে মেপে দেখার পর (পর্ব ৪৩), অনুমানে নয়।
C-র for (int i = 0; i < n; i++)-এর সরাসরি প্রতিরূপ Python-এ নেই; সবচেয়ে কাছের রূপ for i in range(n):, তবে Python-এ index-এর চেয়ে জিনিসের ওপর সরাসরি হাঁটাই প্রচলিত — অনেকটা C++11-এর for (auto x : v)-এর মতো। while একই রকম, কিন্তু do-while, i++ আর goto নেই। আরেকটি পার্থক্য: লুপের ভেতরে i বদলালে পরের ঘোরায় তার প্রভাব থাকে না, কারণ range প্রতিবার নিজের পরের সংখ্যা দেয়। C-র তিন ধরনের লুপ আর তাদের ভেতরের ছবি দেখতে C কোর্সের লুপ পর্ব দেখুন।
list(range(2, 11, 3))কী দেয়? —[2, 5, 8]; 11 stop, তাই ভেতরে নেই।breakআরcontinue-এর পার্থক্য? —breakপুরো লুপ থামায়;continueশুধু চলতি ঘোরার বাকিটা বাদ দিয়ে পরের ঘোরায় যায়।- লুপের
elseকখন চলে? — লুপbreakছাড়া স্বাভাবিকভাবে শেষ হলে; break হলে চলে না। forআরwhileকখন কোনটি? — কোনো সংগ্রহের প্রতিটি জিনিসের ওপর বা জানা সংখ্যকবার হলেfor; থামা একটি শর্তের ওপর নির্ভর করলেwhile.- Python 3-এর
rangeআর Python 2-এরrange-এর পার্থক্য? — Python 2-এrangeপুরো list বানাত (অলস রূপটি ছিলxrange); Python 3-এরrangeঅলস sequence.
১০ · AI-এর যুগে — AI-এর লেখা লুপ যাচাই
লুপ লিখতে বললে AI প্রায় সবসময় এমন কোড দেয় যা চলে. সমস্যা হলো, AI শিখেছে কোটি কোটি লাইনের কোড থেকে, যার বড় অংশ C, Java বা পুরোনো Python ধাঁচে লেখা। তাই AI-এর লুপে প্রায়ই “বিদেশি উচ্চারণ” থাকে — আর মাঝে মাঝে লুকানো বাগ।
| AI প্রায়ই যা লেখে | সমস্যা | Python-এর পরিষ্কার রূপ |
|---|---|---|
for i in range(len(names)): | অপ্রয়োজনীয় index, off-by-one-এর সুযোগ | for name in names: বা enumerate |
names[i], marks[i] একসাথে | দুই list-এর দৈর্ঘ্য না মিললে IndexError | zip(names, marks, strict=True) |
total = 0 + লুপ, biggest = 0 + লুপ | লম্বা, আর ঋণাত্মক ডেটায় biggest ভুল | sum(), max() |
লুপের ভেতরে items.remove(x) | উপাদান বাদ পড়ে, কোনো error ছাড়াই | নতুন list বা কপির ওপর লুপ |
while True: যার থামার পথ অস্পষ্ট | কোনো ইনপুটে infinite loop | স্পষ্ট শর্ত বা নিশ্চিত break |
AI-কে লুপের কোড লিখতে বা রিভিউ করতে বলার সময় নিয়মগুলো আগেই বলে দিন। নিচের কাঠামোটি কপি করে ব্যবহার করুন:
AI-এর লেখা লুপ পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলো আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।
প্র ০১C, C++, Java, JavaScript — প্রায় সব জনপ্রিয় ভাষায় for (i = 0; i < n; i++) আছে। Python ইচ্ছে করে এটি রাখেনি কেন? এতে কী হারাল, কী পেল?
C-র তিন-অংশের for আসলে একটি while-এর সংক্ষিপ্ত রূপ: শুরুর কাজ, প্রতিবারের শর্ত, প্রতিবারের শেষ কাজ — তিনটি এক লাইনে। এটি অত্যন্ত নমনীয়; যেকোনো রকম গোনা, লাফ, এমনকি একসাথে দুটি ভ্যারিয়েবল চালানো যায়। কিন্তু বাস্তবে এর বেশিরভাগ ব্যবহার একটিই কাজের জন্য — একটি সংগ্রহের প্রতিটি জিনিস একবার করে দেখা. সেই কাজের জন্য তিনটি অংশ লেখা মানে তিনটি জায়গায় ভুলের সুযোগ: শুরু 0 না 1, শর্ত < না <=, বাড়ানো ঠিক ভ্যারিয়েবলে কি না।
Python-এর পূর্বসূরি ABC ভাষাটি তৈরি হয়েছিল নতুন শিক্ষার্থীদের কথা ভেবে, আর সেখানে লুপ লেখা হতো সরাসরি সংগ্রহের ওপর। Guido van Rossum সেই দলে কাজ করেছিলেন, আর Python-এ এই ধারণাটি রেখে দিলেন। Unix shell-এর for f in *.txt-ও একই ভাবনা। ফলে Python-এর for একটি “for each” — তার কাজ শুধু “পরের জিনিস দাও”। গোনা দরকার হলে সংখ্যাগুলোও একটি সংগ্রহ: range.
কী হারাল? কিছু অদ্ভুত লুপ — যেমন একসাথে দুই প্রান্ত থেকে ভেতরে আসা, বা ঘোরার মাঝে index লাফিয়ে দেওয়া — Python-এ while দিয়ে লিখতে হয়, যা একটু লম্বা। কী পেল? প্রথমত, off-by-one আর ভুল index-এর একটি বড় শ্রেণি প্রায় অদৃশ্য। দ্বিতীয়ত, একই for লুপ list, string, ফাইল, ডেটাবেসের ফলাফল, এমনকি অসীম সংখ্যার ধারায়ও চলে — কারণ লুপ শুধু “পরের জিনিস” চায়, “কত নম্বর ঘর” নয়। তৃতীয়ত, কোড পড়তে ইংরেজি বাক্যের মতো।
ইতিহাস বলছে Python ঠিক দিকেই হেঁটেছিল: Java ২০০৪ সালে (Java 5) “enhanced for” যোগ করে, C++ ২০১১ সালে range-based for, JavaScript ২০১৫ সালে for...of. সবাই পুরোনো লুপ রেখে দিয়েছে পেছনের কোডের জন্য, কিন্তু নতুন কোডে “for each” রূপটিকেই উৎসাহ দেয়। Go ভাষা আবার উল্টো পথে গিয়ে সব লুপকে একটি for কীওয়ার্ডে মিলিয়েছে, যার একটি রূপ range দিয়ে সংগ্রহের ওপর হাঁটে।
মূল উপলব্ধি: ভাষার নকশায় “কম” কখনো “বেশি” — সবচেয়ে প্রচলিত কাজটিকে সবচেয়ে সহজ আর সবচেয়ে কম ভুলপ্রবণ করে দিলে লাখো প্রোগ্রামারের বাগ কমে। নমনীয়তার দাম দিতে হয় ভুলের সুযোগে।
প্র ০২Python 2-এ range আর xrange নামে দুটি জিনিস ছিল। Python 3 কেন range-কেই অলস করে দিল, আর এই “অলসতা”-র ভাবনা Python-এর আর কোথায় ছড়িয়ে পড়ল?
Python 2-এ range(1000000) লিখলে সত্যিই দশ লাখ সংখ্যার একটি list মেমরিতে তৈরি হতো — শুধু একটি লুপ চালানোর জন্য। প্রতিটি সংখ্যা একটি পূর্ণ object, আর list রাখে তাদের ঠিকানা; ফলে কয়েক কোটির range-এ মেমরি ফুরিয়ে যেতে পারত। সমাধান হিসেবে ছিল xrange, যা সংখ্যাগুলো চাহিদামতো একটি একটি করে বানাত। কিন্তু দুটি নাম থাকায় নতুনরা বিভ্রান্ত হতেন, আর অনেকেই অভ্যাসবশত range লিখে অকারণে মেমরি খরচ করতেন।
২০০৮ সালে প্রকাশিত Python 3 ছিল ইচ্ছাকৃতভাবে পেছনের সাথে না-মেলা একটি বড় পরিষ্কারের সুযোগ। সেখানে পুরোনো list-বানানো range বাদ গেল, xrange-এর অলস আচরণটি নাম পেল range, আর xrange নামটি মুছে দেওয়া হলো। সাথে আরও উন্নতি এল: Python 3-এর range একটি পূর্ণ sequence — len(), index, slicing (যা আরেকটি range দেয়), আর পূর্ণসংখ্যার জন্য in যাচাই সূত্র দিয়ে সঙ্গে সঙ্গে।
একই দর্শন ছড়িয়ে পড়ল পুরো ভাষায়। Python 2-এ zip, map, filter পুরো list ফেরত দিত; Python 3-এ এরা অলস — চাওয়া মাত্র একটি একটি করে দেয়। dictionary-র keys(), values(), items() Python 2-এ নতুন list বানাত; Python 3-এ দেয় হালকা “view”. এর ফল: একটি বিশাল লগ ফাইলের লাইনগুলো, বা একটি অসীম সংখ্যার ধারা, পুরোটা মেমরিতে না এনেই প্রক্রিয়া করা যায়।
অলসতার একটি দামও আছে, যা নতুনদের অবাক করে: অনেক অলস object (যেমন zip-এর ফল) একবার হেঁটে ফেললে ফুরিয়ে যায় — দ্বিতীয়বার লুপ চালালে কিছুই পাবেন না। range এর ব্যতিক্রম; সেটি যতবার খুশি হাঁটা যায়, কারণ এটি একটি sequence, একবার-ব্যবহারের iterator নয়। এই পার্থক্য পর্ব ৩৪-এ iterator আর generator শেখার সময় পরিষ্কার হবে।
মূল উপলব্ধি: “আগে সব বানিয়ে রাখো” বনাম “দরকার হলে বানাও” — এই সিদ্ধান্ত মেমরি, গতি আর সরলতার ভারসাম্য। Python 3 ডিফল্ট হিসেবে অলসতা বেছে নিয়েছে, কারণ বড় ডেটার যুগে সেটিই নিরাপদ পছন্দ।
প্র ০৩লুপের else কেন “else” নামে, আর কেন অনেকে মনে করেন এটি Python-এর সবচেয়ে বিভ্রান্তিকর বৈশিষ্ট্যগুলোর একটি?
নামটির একটি যুক্তি আছে, যা while থেকে সবচেয়ে ভালো বোঝা যায়। while cond: ... else: ... — প্রতিবার শর্ত দেখা হয়; সত্য হলে body, আর শর্ত যখন মিথ্যা হয় তখন লুপ শেষ হয়ে else চলে। অর্থাৎ else যুক্ত আছে শর্ত-পরীক্ষার মিথ্যা হওয়ার সাথে, ঠিক if cond: ... else: ...-এর মতো। break সেই পরীক্ষা এড়িয়ে সরাসরি বেরিয়ে যায়, তাই else-ও বাদ পড়ে। for-এর ক্ষেত্রে “শর্ত মিথ্যা” মানে “আর কোনো জিনিস নেই”. ভাষা-নকশাকারের চোখে এটি পুরোপুরি সামঞ্জস্যপূর্ণ।
কিন্তু সাধারণ পাঠকের চোখে নয়। ইংরেজিতে “else” মানে “নইলে” — তাই for x in items: ... else: ... পড়ে বেশিরভাগ মানুষ ধরে নেন “items খালি হলে else চলবে”। আসলে খালি তালিকাতেও else চলে, ভরা তালিকাতেও চলে — শুধু break হলে চলে না। এই ফাঁক এতটাই প্রচলিত যে অভিজ্ঞ Python প্রোগ্রামারদের মধ্যেও অনেকে মনে করেন বৈশিষ্ট্যটি থাকা নিয়ে দ্বিধা আছে। Python core developer Raymond Hettinger তাঁর একটি জনপ্রিয় বক্তৃতায় বলেছিলেন, এর নাম nobreak হলে কেউ বিভ্রান্ত হতো না — আর সেই পরামর্শ থেকেই # no break comment লেখার প্রচলন। Python-এর আলোচনা-তালিকায় নাম বদলানো বা বৈশিষ্ট্যটি সরানোর প্রস্তাবও এসেছে বিভিন্ন সময়, কিন্তু পুরোনো কোড ভাঙার ভয়ে তা হয়নি।
তাহলে কী করবেন? তিনটি বিকল্প আছে। (১) for/else ব্যবহার করুন, সাথে # no break comment — কোড ছোট, কিন্তু পাঠককে নিয়মটি জানতে হবে। (২) একটি flag ভ্যারিয়েবল — লম্বা, কিন্তু সবাই বোঝে। (৩) খোঁজাটি একটি ছোট ফাংশনে রাখুন: পেলে return, লুপের পরে “পাওয়া যায়নি”-র মান ফেরত — প্রায়ই সবচেয়ে পরিষ্কার, আর পরীক্ষা করাও সহজ। শুধু “আছে কি না” প্রশ্নে any() সবচেয়ে ছোট।
দলের কোডে কোনটি ব্যবহার করবেন, তা নির্ভর করে পাঠকদের ওপর। নতুনদের দলে flag বা ফাংশন ভালো; অভিজ্ঞ দলে for/else-ও স্বাভাবিক।
মূল উপলব্ধি: কোড লেখা হয় একবার, পড়া হয় বহুবার। একটি বৈশিষ্ট্য যুক্তিসংগত হলেই যথেষ্ট নয় — পাঠক প্রথম দেখায় ঠিক বুঝবেন কি না, সেটাও নকশার অংশ।
প্র ০৪১৯৬৮ সালে Edsger Dijkstra-র “Go To Statement Considered Harmful” চিঠি প্রোগ্রামিংকে কীভাবে বদলে দিল? Python-এ goto নেই কেন, অথচ break আর continue আছে কেন?
প্রথম দিকের প্রোগ্রামিং ভাষায় (প্রাথমিক FORTRAN, BASIC, assembly) লুপ বলে আলাদা কিছু প্রায় ছিল না। ছিল GOTO — “অমুক লাইনে লাফ দাও”. লুপ বানানো হতো পেছনের দিকে লাফ দিয়ে, সিদ্ধান্ত নেওয়া হতো শর্তসহ লাফ দিয়ে। বড় প্রোগ্রামে লাফগুলো এমন জট পাকাত যে এর নাম হয়ে যায় “spaghetti code” — একটি সুতো টানলে কোথায় গিয়ে শেষ হবে বোঝা যায় না।
১৯৬৮ সালের মার্চে ডাচ কম্পিউটার বিজ্ঞানী Edsger W. Dijkstra-র একটি ছোট চিঠি ছাপা হয় Communications of the ACM-এ। শিরোনাম “Go To Statement Considered Harmful” — শিরোনামটি দিয়েছিলেন পত্রিকার সম্পাদক Niklaus Wirth. Dijkstra-র যুক্তি ছিল: প্রোগ্রামের লেখা আর চলার ক্রমের মধ্যে যত কম দূরত্ব, প্রোগ্রাম বোঝা আর ঠিক প্রমাণ করা তত সহজ। অবাধ goto সেই দূরত্ব অসীম করে দেয়। এর দুই বছর আগে, ১৯৬৬ সালে, Corrado Böhm ও Giuseppe Jacopini দেখিয়েছিলেন যে যেকোনো অ্যালগরিদম মাত্র তিনটি গঠন দিয়ে লেখা যায় — ক্রম (একটার পর একটা), নির্বাচন (if) আর পুনরাবৃত্তি (লুপ)। এই দুই ভাবনা মিলে শুরু হয় structured programming আন্দোলন।
বিতর্কও কম হয়নি। ১৯৭৪ সালে Donald Knuth “Structured Programming with go to Statements” প্রবন্ধে দেখালেন, কিছু ক্ষেত্রে নিয়ন্ত্রিত লাফ কোডকে আসলে পরিষ্কার করে — যেমন লুপের মাঝখান থেকে বের হওয়া। আধুনিক ভাষাগুলো এই মধ্যপথই নিয়েছে: অবাধ goto নয়, কিন্তু নিয়ন্ত্রিত লাফ — break (এই লুপ থেকে বের হও), continue (এই লুপের পরের ঘোরায় যাও), return (এই ফাংশন থেকে বের হও), আর exception. এদের প্রত্যেকের গন্তব্য গঠন দিয়েই নির্দিষ্ট — আপনি যেকোনো লাইনে লাফাতে পারবেন না।
C-তে goto এখনো আছে, আর Linux kernel-এর মতো বড় C প্রকল্পে ভুল হলে পরিষ্কারের কোডে লাফানোর জন্য এর ব্যবহার প্রচলিত। Python-এ সেই কাজের জন্য আছে try/finally আর with (পর্ব ২৪, ২৫) — তাই goto-র দরকারই পড়ে না।
মূল উপলব্ধি: আজ যে for, while, break স্বাভাবিক মনে হয়, তা একটি বুদ্ধিবৃত্তিক লড়াইয়ের ফল। নিয়ন্ত্রণ-প্রবাহকে কয়েকটি পরিচিত গঠনে বেঁধে দিলে মানুষ প্রোগ্রাম নিয়ে যুক্তি করতে পারে — আর সেটিই বড় সফটওয়্যারের ভিত্তি।
প্র ০৫for x in something: লিখলে ভেতরে আসলে কী ঘটে? একই for লুপ list, string, range, ফাইল — সবকিছুর ওপর চলে কীভাবে?
for লুপের রহস্য একটি ছোট চুক্তি, যার নাম iterator protocol. for লুপ আসলে জানে না সে list-এর ওপর চলছে না ফাইলের ওপর। সে শুধু দুটি প্রশ্ন করে:
- প্রথমে, একবার:
iter(something)— “আমাকে একটি iterator দাও।” Iterator হলো একটি ছোট object, যে মনে রাখে “কতদূর হাঁটা হলো”. List-এর iterator মনে রাখে একটি index — অংশ ৮-এর “গোপন index” এটিই। ফাইলের iterator মনে রাখে ফাইলের কোন জায়গা পর্যন্ত পড়া হয়েছে। - তারপর, বারবার:
next(iterator)— “পরের জিনিস দাও।” প্রতিটি উত্তর loop variable-এ বসে, body চলে। জিনিস ফুরালে iterator একটি বিশেষ সংকেত দেয় —StopIterationexception. for লুপ সেটি ধরে চুপচাপ থেমে যায় (আর break না হওয়ায় else থাকলে চালায়)।
অর্থাৎ for x in items: body মোটামুটি এর সমান: it = iter(items), তারপর while True: — next(it) চেষ্টা করো, StopIteration এলে break, নইলে body. পর্ব ০৫-এর dis দিয়ে যেকোনো for লুপের bytecode দেখলে দুটি নির্দেশ পাবেন — GET_ITER আর FOR_ITER; ঠিক এই দুটি ধাপ।
এই চুক্তি Python-এ এসেছে ২০০১ সালের ডিসেম্বরে, Python 2.2-এ (PEP 234). তার আগে for লুপ object-কে [0], [1], [2] করে index দিয়ে চাইত, যতক্ষণ না IndexError আসে — যা ফাইল বা নেটওয়ার্কের ধারার মতো জিনিসে খাপ খায় না, কারণ সেখানে “৫ নম্বর লাইন”-এ সরাসরি যাওয়া যায় না। পুরোনো পদ্ধতিটি এখনো একটি বিকল্প পথ হিসেবে কাজ করে, যাতে পুরোনো কোড না ভাঙে। একই version Python 2.2-তে এল generator (PEP 255; প্রথমে from __future__ import generators দিয়ে, 2.3 থেকে সবার জন্য চালু) — yield দিয়ে নিজের iterator বানানোর সহজ উপায়।
এই নকশার শক্তি হলো আলাদা করা: কে হাঁটবে (for লুপ, sum, zip, list()) আর কীসের ওপর হাঁটবে (list, ফাইল, ডেটাবেস) — একে অপরকে চেনে না, শুধু চুক্তিটি চেনে। তাই আপনি নিজের class বানিয়ে দুটি method যোগ করলেই সেটি for লুপে, sum()-এ, zip()-এ চলতে শুরু করে। পুরো বিষয়টি হাতে-কলমে দেখবেন পর্ব ৩৪-এ।
মূল উপলব্ধি: একটি ছোট, স্পষ্ট চুক্তি (“পরের জিনিস দাও, ফুরালে জানাও”) পুরো ভাষাকে একসূত্রে বাঁধে। ভালো নকশা প্রায়ই এমন — বড় ক্ষমতা আসে ছোট interface থেকে।
অনুশীলন
এগুলোর বেশিরভাগে নতুন কোড লিখতে হবে না — অনুমান করা, হাতে চালানো আর মিলিয়ে নেওয়াই আসল কাজ।
-
হাতে চালান: চালানোর আগে খাতায় লিখুন কী ছাপা হবে — (ক)
for i in range(10, 0, -3): print(i); (খ)for i in range(3): pass, তারপরprint(i); (গ)for ch in "Py": print(ch * 2). তারপর REPL-এ মিলিয়ে নিন।(ক) 10, 7, 4, 1 — প্রতিবার ৩ কমে; পরের মান −2 হতো, যা stop 0-এর ওপারে, তাই থামে। 0 কখনো আসে না। (খ) 2 — loop variable লুপের পরেও শেষ মানটি ধরে রাখে। (গ)
PPআরyy— string-এর ওপর লুপ একটি একটি অক্ষর দেয়, আর* 2অক্ষরটি দুবার লেখে। -
বাড়তে থাকা সঞ্চয়: অংশ ৩-এর সঞ্চয়ের কোডটি বদলান: প্রথম মাসে ১,০০০ টাকা, তারপর প্রতি মাসে আগের মাসের চেয়ে ১০০ টাকা বেশি। ১২ মাসে মোট কত? আগে হাতে হিসাব করুন, তারপর কোড লিখে মেলান।
মাসের জমা 1000, 1100, … 2100 — মোট 18600 টাকা (প্রথম আর শেষের গড় 1550, গুণ ১২)। একটি উপায়:
total = 0 for month in range(12): # 0 থেকে 11 total += 1000 + month * 100 print(total) # 18600আরও ছোট রূপ:
sum(range(1000, 2101, 100))— লুপ ছাড়াই। stop 2101 খেয়াল করুন; 2100 লিখলে শেষ মাসটি বাদ পড়ত (off-by-one). -
থামবে কি? নিচের তিনটি লুপের কোনটি কখনো থামবে না, আর কেন? (ক)
n = 10,while n > 0: n -= 3; (খ)n = 5,while n != 0: n -= 2; (গ)count = 0,while count < 5: print(count).(ক) থামবে: 10 → 7 → 4 → 1 → −2, তখন
n > 0মিথ্যা। (খ) থামবে না: 5 → 3 → 1 → −1 → … —nকখনো ঠিক 0 হয় না, শূন্যকে লাফিয়ে পার হয়ে যায়।n > 0লিখলে সমস্যা মিটত। (গ) থামবে না:countbody-তে কখনো বাড়ানো হয়নি, তাই 0 অনন্তকাল ছাপা হবে।count += 1দরকার। এমন হলে Ctrl+C. -
AI অডিট: একটি AI এই কোড দিয়েছে:
total = 0, তারপরfor i in range(len(marks)):ভেতরেprint(i + 1, names[i], marks[i])আরtotal = total + marks[i]. এতে কী কী উন্নতি করা যায়?names-এ যদিmarks-এর চেয়ে কম জিনিস থাকে, কী হবে?উন্নতি: (১)
for roll, (name, mark) in enumerate(zip(names, marks, strict=True), start=1):— index নেই,i + 1নেই। (২) মোট আলাদা করেsum(marks); হাতে accumulator লাগে না।দৈর্ঘ্যের প্রশ্ন: মূল কোডে
namesছোট হলে শেষ দিকেnames[i]একটিIndexErrorদেবে — অন্তত চোখে পড়বে। সাধারণzipব্যবহার করলে এই ভুল চুপচাপ লুকিয়ে যেত (বাড়তি নম্বর বাদ পড়ত), তাইstrict=Trueদিলে দৈর্ঘ্য না মিললেValueErrorপাবেন। শিক্ষা: “পরিষ্কার” কোডেও ডেটার ভুল ধরার ব্যবস্থা রাখুন।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমPython-এ for x in items: লুপ ভেতরে যে ধাপে চলে, তা সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 📦
iter(items)দিয়ে একটি iterator নেওয়া - 👉
next()দিয়ে পরের জিনিসটি চাওয়া - 🏷️ জিনিসটি
xনামে লাগানো - ⚙️ লুপের body চালানো, তারপর আবার পরের জিনিস চাওয়া
- 🛑 জিনিস ফুরালে
StopIteration— লুপ শেষ (break না হলে else চলে)
Python-এ list(range(2, 11, 3))-এর মান কী?
- [2, 5, 8, 11]
- [2, 5, 8]
- [3, 6, 9]
- [2, 4, 6, 8, 10]
Python-এ for লুপের পরে লেখা else অংশটি কখন চলে?
- শুধু তখন, যখন তালিকাটি খালি
- শুধু তখন, যখন লুপ break দিয়ে থামে
- যখন লুপ break ছাড়া স্বাভাবিকভাবে শেষ হয়
- লুপের প্রতিটি ঘোরার শেষে একবার করে
for i in range(3): pass চলার ঠিক পরে print(i) লিখলে কী ছাপা হবে?
- 2
- 3
- NameError
- 0
Python-এ list(zip(["ক", "খ", "গ"], [1, 2]))-এর ফল কী?
- ValueError
- [("ক", 1), ("খ", 2), ("গ", None)]
- ["ক", "খ", "গ", 1, 2]
- [("ক", 1), ("খ", 2)]
marks = [30, 25, 80]. এরপর for m in marks: লুপের ভেতরে if m < 33: marks.remove(m) চালালে শেষে marks কী হবে?
- [80]
- [25, 80]
- RuntimeError
- [30, 25, 80]
একটি নামের list-এর প্রতিটি নাম 1 থেকে শুরু করা ক্রমিক নম্বরসহ ছাপার সবচেয়ে Python-সম্মত উপায় কোনটি?
- for i in range(len(names)): print(i + 1, names[i])
- count = 1, তারপর for name in names: print(count, name) এবং count += 1
- for n, name in enumerate(names, start=1): print(n, name)
- for n, name in zip(names, names): print(n, name)
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- for মানে “প্রতিটির জন্য” — যেকোনো iterable-এর জিনিস একটি একটি করে দেয়; সংখ্যা গুনতে
range, যা অলস আর যার stop কখনো ভেতরে থাকে না। - Index লাগলে
enumerate(x, start=1), দুটি সারি একসাথেzip(a, b, strict=True)—range(len(x))প্রায় কখনো নয়। - while চলে শর্ত যতক্ষণ সত্য; body-তে শর্ত বদলাতেই হবে। সেন্টিনেলের জন্য
while True+break. breakলুপ থামায়,continueএক ঘোরা বাদ দেয়, লুপেরelseচলে শুধু break না হলে (“no break”). Loop variable লুপের পরেও বেঁচে থাকে।- যার ওপর হাঁটছেন তাকে বদলাবেন না; আর লুপ লেখার আগে জিজ্ঞেস করুন —
sum,max,any,allদিয়ে কি হয়ে যায়?
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ১৩ · ফাংশন — def, return, default আর keyword argumentপরবর্তী পর্বকাজকে নাম দিয়ে আলাদা করা, আর লুপসহ কোডকে পুনর্ব্যবহারযোগ্য করা।
- Python প্রোগ্রামিং · কোর্স হোম৫৫টি পর্বপুরো কোর্সের রোডম্যাপ — ৮টি মডিউল, কোন পর্ব কখন।
- C প্রোগ্রামিং · লুপ — একই কাজ বারবারতুলনা করুনC-র for, while আর do-while — তিন অংশের for ভেতরে কীভাবে কাজ করে।