এই পর্বে যা পাবেন
- কাগজে-কলমে Python কোড চালানোর (dry run) পদ্ধতি — নাম আর object আলাদা করে দেখা, call stack আর “কখন মূল্যায়ন হয়” একসাথে
- মডিউল ১ থেকে ৭ পর্যন্ত ৫০টি প্রশ্ন — সহজ থেকে কঠিন, প্রতিটির পূর্ণ ব্যাখ্যা আর কোন পর্ব ঝালাবেন তার লিংক
- exception-এর ঠিক নাম, SyntaxError আর “ক্রম নিশ্চিত নয়” — “অদ্ভুত” উত্তরগুলো আলাদা করে চেনা ও কারণসহ লেখা
- ৮টি “নিজে চালিয়ে দেখুন” প্রোগ্রাম — একটি লাইন বদলে আগে অনুমান, তারপর চালিয়ে যাচাই
- AI-কে দিয়ে নতুন প্রশ্ন বানানো, আর তার উত্তর interpreter দিয়ে যাচাইয়ের নিয়ম
১ · মডিউল ৮ শুরু — পরীক্ষা ও ইন্টারভিউর প্রস্তুতি
অভিনন্দন! ৫১টি পর্বে আপনি Python-এর ভ্যারিয়েবল আর টাইপ থেকে শুরু করে list, dict, exception, class, generator, asyncio আর একটি পূর্ণ ক্যাপস্টোন প্রজেক্ট পর্যন্ত এসেছেন। এবার শেখা জিনিস এমনভাবে ঝালিয়ে নেওয়ার পালা, যাতে পরীক্ষার হলে বা ইন্টারভিউর টেবিলে চাপের মধ্যেও ঠিক উত্তর মাথায় আসে।
এই শেষ মডিউলে নতুন কোনো বিষয় নেই। আছে চারটি অনুশীলনের পর্ব, প্রতিটি আলাদা ধরনের প্রশ্নের জন্য:
পর্ব ৫২ · আউটপুট কী হবে?
এই পর্ব। ছোট কোড দেখে বলতে হবে কী ছাপা হবে — বা কোন exception উঠবে। লিখিত পরীক্ষা, MCQ আর টেকনিক্যাল ইন্টারভিউ, তিন জায়গাতেই আসে।
কৌশল: trace tableপর্ব ৫৩ · MCQ প্রশ্নব্যাংক
ধারণা, সংজ্ঞা আর তথ্যের দ্রুত প্রশ্ন — keyword, built-in, ডেটা স্ট্রাকচারের জটিলতা, OOP. চাকরির প্রাথমিক বাছাই পরীক্ষায় এ ধরনের প্রশ্ন প্রায়ই আসে।
কৌশল: সময় ধরে চর্চাপর্ব ৫৪ · লিখিত ও ভাইভা
“পার্থক্য লিখুন”, “উদাহরণসহ ব্যাখ্যা করুন”, “ফাংশন লিখুন” আর মুখোমুখি প্রশ্নের উত্তর কীভাবে সাজাবেন।
কৌশল: উত্তরের কাঠামোপর্ব ৫৫ · কোডিং ইন্টারভিউ
সমস্যা শুনে প্রশ্ন করা, পরিকল্পনা বলা, Python-এ কোড লেখা, টেস্ট করা — সফটওয়্যার কোম্পানির ইন্টারভিউর পুরো ধারা।
কৌশল: জোরে ভাবা“আউটপুট কী হবে?” প্রশ্ন এত বেশি আসে কেন
প্রশ্নকর্তা আসলে জানতে চান: আপনি কি interpreter-এর মতো করে কোড পড়তে পারেন? Python দেখতে সহজ, তাই অনেকে ধরে নেন এখানে চমক নেই। অথচ সবচেয়ে বেশি বাগ আসে ঠিক সহজ-দেখতে জায়গা থেকে: দুটি নাম একই list দেখাচ্ছে, default মান একবারই তৈরি হয়েছে, লুপের lambda শেষ মানটাই মনে রেখেছে, generator একবার ব্যবহারেই ফুরিয়ে গেছে। যিনি কোড পড়েই এগুলো দেখতে পান, তিনিই বাগ খুঁজে পান, AI-এর লেখা কোড যাচাই করতে পারেন, আর code review-তে কাজের মন্তব্য দিতে পারেন।
| আপনার লক্ষ্য | শুরু করুন | তারপর |
|---|---|---|
| বিশ্ববিদ্যালয়ের লিখিত পরীক্ষা | পর্ব ৫২ (আউটপুট) ও ৫৪ (লিখিত) | পর্ব ৫৩ দিয়ে দ্রুত ঝালাই |
| সরকারি বা ব্যাংকের IT/প্রোগ্রামার পদের পরীক্ষা | পর্ব ৫৩ (MCQ) ও ৫২ | পর্ব ৫৪-এর লিখিত ও ভাইভা অংশ |
| সফটওয়্যার কোম্পানির ইন্টারভিউ | পর্ব ৫৫ (কোডিং) | পর্ব ৫২ — অনেক টেকনিক্যাল রাউন্ড “এই কোড কী ছাপবে?” দিয়েই শুরু হয় |
| শুধু নিজের শেখা যাচাই | এই পর্ব, প্রথম থেকে শেষ | যে ভাগে বেশি ভুল, সেই পর্বে ফিরে যান |
এই পাতা কীভাবে ব্যবহার করবেন
- প্রথম দফা — অপশন ঢেকে রাখুন। প্রতিটি কোড পড়ে খাতায় নিজের উত্তর লিখুন, তারপর অপশনে চাপুন। প্রথম চেষ্টায় কতগুলো ঠিক হলো, অনুশীলনের শেষে স্কোর দেখাবে।
- দ্বিতীয় দফা — ভুলগুলোর কারণ খুঁজুন। প্রতিটি ভুল অপশনের ব্যাখ্যা পড়ুন। প্রতিটি ভাগের শেষে “রিভিশনের জন্য” অংশে লেখা আছে কোন পর্বগুলো ঝালাতে হবে।
- তৃতীয় দফা — নিজে চালান। প্রতিটি ভাগের “নিজে চালিয়ে দেখুন” কোড চালান। একটি লাইন বদলে আগে ফল অনুমান করুন, তারপর চালিয়ে মেলান।
সব উত্তর CPython 3.14-এর জন্য, কোড একটি .py ফাইল হিসেবে চালানো (REPL-এ লাইন ধরে লেখা নয়)। match লাগে Python 3.10 বা পরের সংস্করণ। কোডের টুকরোয় import না থাকলে ধরে নিন সেটি লেখা আছে। exception-এর উত্তরে শুধু traceback-এর শেষ লাইন দেখানো হয়েছে; পুরো traceback stderr-এ যায়। যে উত্তর সংস্করণ বা implementation-এর ওপর নির্ভর করে, প্রশ্নেই তা বলা আছে।
C/C++-এর আউটপুট প্রশ্নে “undefined behavior” একটি বড় উত্তর; Python-এ তা প্রায় নেই — প্রায় প্রতিটি প্রোগ্রামের হয় নির্দিষ্ট ফল, নয় নির্দিষ্ট নামের exception. তবে Python-এর নিজস্ব চমক আছে: প্রতিটি ভ্যারিয়েবল আসলে একটি object-এর reference, // ভাগ শূন্যের দিকে নয়, নিচের দিকে গোল করে (C-তে -7 / 2 হয় -3, Python-এ -7 // 2 হয় -4), আর default argument একবারই তৈরি হয়। তুলনার জন্য দেখুন C-এর আউটপুট প্রশ্ন আর C++-এর আউটপুট প্রশ্ন.
পরীক্ষা ও ইন্টারভিউতে এ ধরনের প্রশ্ন সাধারণত এই চেহারায় আসে:
- “নিচের প্রোগ্রামের আউটপুট লিখুন” — খাতায় trace table এঁকে দেখান; শুধু উত্তর নয়, ধাপগুলোও নম্বর পায়।
- “এই কোড চালালে কী হবে?” — উত্তর exception হলে তার ঠিক নাম আর কোন লাইনে উঠল তা লিখুন; তার আগে যা ছাপা হয়েছে, সেটাও।
- MCQ-তে চারটি আউটপুট থেকে বেছে নেওয়া — আগে নিজে হিসাব করুন, তারপর অপশন দেখুন।
- ইন্টারভিউতে “এই কোডে কী সমস্যা?” — mutable default, alias বা late binding আগে বলুন, সঙ্গে সমাধান; এটাই অভিজ্ঞতার লক্ষণ।
২ · Python কোড মাথায় চালানোর পদ্ধতি
আউটপুট বলার একটাই নির্ভরযোগ্য উপায়: interpreter যেভাবে চালায়, ঠিক সেভাবে এক লাইন এক লাইন করে চালানো, আর প্রতিটি পরিবর্তন লিখে রাখা। একে বলে dry run. Python-এ এর জন্য একটি সাধারণ ভ্যারিয়েবল-টেবিল যথেষ্ট নয়, কারণ Python-এ ভ্যারিয়েবল একটি বাক্স নয় — এটি একটি object-এর গায়ে লাগানো নামের স্টিকার।
একটি গার্মেন্টস কারখানার গুদামের কথা ভাবুন। প্রতিটি কাপড়ের বান্ডিল (object) একবারই আছে, কিন্তু তার গায়ে একাধিক ট্যাগ (নাম) ঝোলানো যায় — “অর্ডার-৭”, “লাল-স্টক”। একটি ট্যাগ ধরে বান্ডিলে কাপড় যোগ করলে, অন্য ট্যাগ ধরে দেখলেও সেই বাড়তি কাপড় দেখা যাবে, কারণ বান্ডিল একটিই। আবার একটি ট্যাগ খুলে অন্য বান্ডিলে লাগালে প্রথম বান্ডিল বদলায় না। Python-এ b = a মানে নতুন ট্যাগ লাগানো (alias), b.append(x) মানে বান্ডিলটাই বদলানো (mutation), আর b = [...] মানে ট্যাগ খুলে অন্য জায়গায় লাগানো (rebinding).
Python-এর trace table-এর ছয়টি নিয়ম
- নাম আর object আলাদা কলামে। বাঁ পাশে নাম, ডান পাশে object (L1, L2… নাম দিয়ে), মাঝে তীর। দুটি নাম একই object-এ তীর দিলে, একটির মাধ্যমে বদল অন্যটিতেও দেখা যাবে।
- প্রতিটি লাইনে জিজ্ঞেস করুন: নতুন বাঁধন, নাকি object বদল?
=সবসময় নাম বাঁধে, কপি করে না।append,+=(list-এ),x[i] = …object বদলায়। int, str, tuple immutable — এদের “বদল” আসলে নতুন object. - কখন মূল্যায়ন হয়, লিখে রাখুন।
defলাইন চলার সময় default মান তৈরি হয় আর decorator চলে; ফাংশনের শরীর চলে ডাকার সময়।print(a(), b())-এ সব argument আগে মূল্যায়ন হয়, তারপর ছাপা। - ফাংশন ডাকলে call stack-এ নতুন ঘর। নিজস্ব local নাম নিয়ে। নাম খোঁজা চলে LEGB ক্রমে; আর ফাংশনের কোথাও কোনো নামে assign থাকলে সেই নাম পুরো ফাংশনে local.
- অলস জিনিস আলাদা চিহ্ন দিন। generator,
map,zip,range— এরা মান তৈরি করে চাওয়ার সময়। generator,map,zip-এর মতো iterator একবার শেষ হলে আর কিছু দেয় না;rangeঅবশ্য বারবার চালানো যায়। - আউটপুট কলামে হুবহু লিখুন।
printমাঝে একটি space আর শেষে newline দেয়; list-এর ভেতরের string repr দিয়ে ছাপা হয় (['a'], উদ্ধৃতিসহ); float ছাপা হয় সবচেয়ে ছোট সঠিক রূপে।
নিচের ডায়াগ্রামে একটি পূর্ণ প্রোগ্রাম এই পদ্ধতিতে চালানো হয়েছে। এখানে দুটি ফাঁদ একসাথে: mutable default argument আর alias. “পরে” চাপার আগে নিজে পুরো আউটপুট লিখে নিন।
1def register(name, roster=[]):2 roster.append(name)3 return roster45a = register("Rina")6b = register("Karim")7c = register("Joy", [])8b.append("Mitu")9print(a)10print(a is b, c)
| default L1 | |
| a | |
| b | |
| c | |
| call stack |
- লাইন ১ — def চলল, default তৈরি হলো একবারই।
defনিজেই একটি statement. এটি চলার মুহূর্তে[]মূল্যায়ন হয়ে একটি খালি list (L1) তৈরি হয়, আর ফাংশনের সাথে জমা থাকে (register.__defaults__-এ)। পরে যতবার ডাকুন, নতুন খালি list তৈরি হবে না। - লাইন ৫ — প্রথম ডাক।
rosterদেওয়া হয়নি, তাই সেটি L1-কে দেখায়।appendL1-কেই বদলায়:['Rina']. ফেরত আসে L1 নিজেই — কপি নয় — আরaএখন L1-এর আরেক নাম। - লাইন ৬ — দ্বিতীয় ডাক। আবার default, অর্থাৎ আবার সেই L1. এবার L1 হলো
['Rina', 'Karim'], আরb-ও L1. লক্ষ করুন,a-কে আমরা ছুঁইনি, তবুaদিয়ে দেখলে এখন দুটি নাম। - লাইন ৭ — নিজের list দিয়ে ডাক।
[]এখানে ডাকার সময় নতুন তৈরি হলো (L2). default ব্যবহারই হলো না, তাই L1 অপরিবর্তিত।c→ L2 =['Joy']. - লাইন ৮ — alias দিয়ে বদল।
b.append("Mitu")L1-কে বদলায়।a,bআর ফাংশনের default — তিনটিই একই L1, তাই তিন জায়গাতেই Mitu চলে এল। পরেরregister()ডাক শুরু হবে তিনটি নাম নিয়ে! - লাইন ৯ — ছাপা।
print(a)L1 ছাপে: ['Rina', 'Karim', 'Mitu']. list-এর ভেতরের string উদ্ধৃতিসহ, কারণ list তার element-কে repr দিয়ে দেখায়। - লাইন ১০ — is আর c.
a is bজিজ্ঞেস করে: দুটি নাম কি একই object? হ্যাঁ — True. আরcআলাদা L2: ['Joy']. পুরো আউটপুট দুই লাইন। সমাধান: default হিসেবেNoneদিন, আর ভেতরেif roster is None: roster = [].
নিচের কোডটি হুবহু ডায়াগ্রামের প্রোগ্রাম, সঙ্গে ফাংশনের জমা থাকা default আর None দিয়ে সমাধান। চালানোর আগে শেষ দুই লাইনের আউটপুট অনুমান করুন।
def register(name, roster=[]):
roster.append(name)
return roster
a = register("Rina")
b = register("Karim")
c = register("Joy", [])
b.append("Mitu")
print(a)
print(a is b, c)
print(register.__defaults__)
def register_fixed(name, roster=None):
if roster is None:
roster = []
roster.append(name)
return roster
print(register_fixed("Rina"), register_fixed("Karim"))আউটপুট চার লাইন: ['Rina', 'Karim', 'Mitu'], True ['Joy'], (['Rina', 'Karim', 'Mitu'],), আর ['Rina'] ['Karim']. তৃতীয় লাইন প্রমাণ করে default list ফাংশনের গায়েই জমা আছে, আর তাতে Mitu-ও ঢুকে গেছে। register_fixed-এ None একটি immutable sentinel; প্রতিটি ডাকে শরীরের ভেতরে নতুন list তৈরি হয়, তাই দুটি ডাক আলাদা। পুরো ব্যাখ্যা: পর্ব ২০ · scope, closure আর default-এর ফাঁদ, পর্ব ১৯ · mutability, alias আর copy.
পাঁচ রকম উত্তর হতে পারে
পরীক্ষার্থীরা ধরে নেন প্রতিটি কোড একটি নির্দিষ্ট লেখা ছাপবে। বাস্তবে উত্তর পাঁচ রকম, আর ভালো প্রশ্নকর্তা ঠিক এটাই যাচাই করেন:
✅ নির্দিষ্ট আউটপুট
ভাষার নিয়ম ফল পুরোপুরি ঠিক করে দেয়। বেশিরভাগ প্রশ্ন এই ধরনের — dict-এর ক্রমও এখানে পড়ে, কারণ Python 3.7 থেকে dict ঢোকানোর ক্রম রাখার নিশ্চয়তা দেয়।
যেমন: -7 // 2 হলো -4⛔ নির্দিষ্ট নামের exception
প্রোগ্রাম চলতে চলতে একটি লাইনে থামে। ঠিক নাম লিখুন — TypeError, NameError, IndexError, KeyError, UnboundLocalError — আর মনে রাখুন, তার আগের লাইনগুলোর আউটপুট ছাপা হয়ে গেছে।
যেমন: "5" + 5🧱 SyntaxError
Python পুরো ফাইল আগে bytecode-এ compile করে, তারপর চালায়। তাই ফাইলের শেষ লাইনে SyntaxError থাকলেও প্রথম লাইনের print চলে না — কিছুই ছাপা হয় না।
🔀 ক্রম নিশ্চিত নয় / সংস্করণ-নির্ভর
string-এর set-এর ক্রম প্রতিবার চালালে বদলাতে পারে (hash randomization). আবার কিছু ফল ভাষার নিয়ম নয়, CPython-এর implementation detail — যেমন বড় int-এ is — বা সংস্করণভেদে বদলায় — যেমন error বার্তার ভাষা বা 3.14-এর নতুন warning.
(মাঝারি) একটি set-এ তিনটি string — CPython 3.14-এ একটি .py ফাইল হিসেবে চালালে কী ছাপা হবে?
print({"a", "b", "c"})
{'a', 'b', 'c'}{'a', 'b', 'c'}— কারণ set নিজে থেকে সাজায়{'c', 'b', 'a'}- তিনটি অক্ষরই থাকবে, কিন্তু ক্রম নিশ্চিত নয় — চালানো থেকে চালানোয় বদলাতে পারে
৩ · ভিত্তি — সংখ্যা, অপারেটর, string আর f-string
এই ভাগের কোড ছোট, কিন্তু ফাঁদ গভীর। প্রতিটি এক্সপ্রেশনে তিনটি প্রশ্ন করুন: ফলের টাইপ কী (int, float, নাকি str)? and/or কি আসলে True/False ফেরত দিচ্ছে, নাকি একটি operand? আর তুলনাটি কি chained comparison? মনে রাখুন, Python 3-এ / সবসময় float দেয়, আর // ও % নিচের দিকে (ঋণাত্মক অসীমের দিকে) গোল করে।
(সহজ) চার রকম ভাগ — কী ছাপা হবে?
print(7 / 2, 7 // 2, -7 // 2, -7 % 3)
3.5 3 -3 -13.5 3 -4 23 3 -4 23.5 3.5 -3.5 2
(মাঝারি) round() কোন দিকে গোল করে?
print(round(2.5), round(3.5), round(2.675, 2))
3 4 2.682 4 2.672 3 2.673 4 2.67
(সহজ) 0.1 + 0.2 কি 0.3?
print(0.1 + 0.2 == 0.3, 0.1 + 0.2)
True 0.3False 0.3False 0.30000000000000004True 0.30000000000000004
(মাঝারি) f-string-এর format spec — ঠিক কী দেখা যাবে?
x = 3.14159
n = 42
print(f"{x:.2f}|{n:>5}|{n:05}|{'ab':*^6}|")
3.14|42 |00042|**ab**|3.14| 42|00042|**ab**|3.14159| 42|42000|ab****|3.14| 42| 42|**ab**|
(মাঝারি) Chained comparison — কী ছাপা হবে?
print(5 > 3 > 1, 3 > 2 == True, 1 < 3 > 2)
True True TrueTrue False TrueFalse False TrueSyntaxError
(মাঝারি) and/or কী ফেরত দেয়?
print(0 or "x", repr("" and 5), [] or [0], 3 and 4)
True False True Truex 5 [0] 3x '' [0] 4x False [0] True
(কঠিন) is বনাম == — একটি .py ফাইল হিসেবে CPython 3.14-এ কী ছাপা হবে?
a = 1000
b = 1000
x = int("1000")
y = int("1000")
print(a == x, a is b, x is y)
True False FalseTrue True FalseTrue True TrueFalse True False
নিজে চালিয়ে দেখুন — সংখ্যা, string আর অপারেটর
ওপরের কয়েকটি প্রশ্ন আর দুটি নতুন, একটি প্রোগ্রামে। চালানোর আগে প্রতিটি লাইনের ফল খাতায় লিখুন, তারপর মিলিয়ে দেখুন।
import math
print("1)", 7 / 2, 7 // 2, -7 // 2, -7 % 3)
print("2)", round(2.5), round(3.5), round(2.675, 2))
print("3)", 0.1 + 0.2, 0.1 + 0.2 == 0.3, math.isclose(0.1 + 0.2, 0.3))
s = "Bangladesh"
print("4)", s[-4:], s[::-1][:3], s[2:100])
print("5)", 0 or "x", [] or [0], 3 and 4, repr("" and 5))
x, y = int("300"), int("300")
print("6)", x == y, x is y, 3 > 2 == True, 5 > 3 > 1)
print(f"7) [{3.14159:.2f}] [{42:>5}] [{42:05}] [{'ab':*^6}]")
print("8)", "2" * 3, int("2") * 3, True + True)১) 3.5 3 -4 2. ২) 2 4 2.67 — banker's rounding আর float-এর আসল মান। ৩) 0.30000000000000004 False True — math.isclose ছোট পার্থক্য মেনে নেয়। ৪) desh hse ngladesh — slicing সীমার বাইরে গেলেও error দেয় না, যতটুকু আছে ততটুকু দেয়; [::-1] উল্টো করে। ৫) x [0] 4 ''. ৬) True False False True — রানটাইমে তৈরি দুটি 300 আলাদা object. ৭) width আর alignment-এর হুবহু রূপ। ৮) 222 6 2 — str × int মানে পুনরাবৃত্তি, আর bool আসলে int-এর subclass (True = 1). একটি পরীক্ষা: print("5" + 5) যোগ করে চালান — TypeError: can only concatenate str (not "int") to str.
রিভিশনের জন্য: পর্ব ০৫ · Python কীভাবে চলে, পর্ব ০৬ · ভ্যারিয়েবল ও টাইপ, পর্ব ০৭ · সংখ্যা ও হিসাব, পর্ব ০৮ · অপারেটর, পর্ব ০৯ · string, পর্ব ১০ · ইনপুট-আউটপুট আর f-string.
৪ · নিয়ন্ত্রণ আর ফাংশন — লুপ, default, scope আর recursion
এই ভাগে দুটি প্রশ্ন সবসময় মাথায় রাখুন। এক: জিনিসটি কখন মূল্যায়ন হয় — def লাইনে (default মান), নাকি ডাকার সময় (শরীর)? দুই: নামটি কোন scope-এর? ফাংশনের ভেতরে কোনো নামে assign থাকলেই নামটি পুরো ফাংশনে local — assign-এর লাইনের আগেও। আর লুপের else চলে তখনই, যখন লুপ break ছাড়া শেষ হয়।
(সহজ) for-এর পরে else — কী ছাপা হবে?
for n in [3, 5, 8, 9]:
if n % 2 == 0:
print("even", n)
break
else:
print("none")
for n in [3, 5]:
if n % 2 == 0:
break
else:
print("none")
- শুধু
even 8 even 8, তারপরnone(দুই লাইনে)none, তারপরnoneeven 8,none,none
(মাঝারি) Mutable default, এক print-এ তিন ডাক — কী ছাপা হবে?
def add(x, box=[]):
box.append(x)
return box
print(add(1), add(2), add(3, []))
[1] [2] [3][1] [1, 2] [3][1, 2] [1, 2] [3][1] [1, 2] [1, 2, 3]
(মাঝারি) Default মান কখন ঠিক হয়?
n = 10
def f(x=n):
return x
n = 20
print(f())
1020NameErrorNone
(মাঝারি) Keyword-only parameter — কী ঘটবে?
def f(a, b=2, *, c=3):
return a + b * c
print(f(1), f(1, c=10))
print(f(1, 2, 3))
7 21, তারপর97 21, তারপরTypeError- শুধু
TypeError— কিছুই ছাপা হয় না 7 33, তারপরTypeError
(মাঝারি) Recursion-এ ডাকের আগে আর পরে print
def show(n):
if n == 0:
return
print(n, end=" ")
show(n - 1)
print(n, end=" ")
show(3)
3 2 13 2 1 1 2 33 2 1 0 1 2 31 2 3 3 2 1
(কঠিন) Global ভ্যারিয়েবল ফাংশনের ভেতরে বাড়ানো
count = 0
def inc():
count += 1
inc()
print(count)
10UnboundLocalErrorNameError
(কঠিন) match-এ একটি constant-এর নাম — কী ছাপা হবে?
RED = "red"
color = "blue"
match color:
case RED:
print("matched", RED)
- কিছুই ছাপা হয় না
matched redmatched blueSyntaxError
নিজে চালিয়ে দেখুন — default, recursion আর closure
def add(x, box=[]):
box.append(x)
return box
print("1)", add(1), add(2), add(3, []))
def show(n):
if n == 0:
return
print(n, end=" ")
show(n - 1)
print(n, end=" ")
print("2)", end=" ")
show(3)
print()
def counter():
c = 0
def inc():
nonlocal c
c += 1
return c
return inc
a, b = counter(), counter()
print("3)", a(), a(), b())
print("4)", list(range(5, 0, -2)), list(range(3, 3)), len(range(0, 10, 3)))
print("5)", print("inner"))১) [1, 2] [1, 2] [3]. ২) 3 2 1 1 2 3. ৩) 1 2 1 — প্রতিটি counter() ডাক নিজের আলাদা c তৈরি করে, আর closure সেটি ধরে রাখে; nonlocal ছাড়া c += 1 আবার UnboundLocalError দিত। ৪) [5, 3, 1] [] 4 — range-এর শেষ মান বাদ যায়; শুরু আর শেষ সমান হলে খালি; 0, 3, 6, 9 মিলে চারটি। ৫) আগে inner, তারপর 5) None — ভেতরের print আগে চলে (argument মূল্যায়ন), আর print নিজে None ফেরত দেয়। return ছাড়া প্রতিটি ফাংশনই None ফেরত দেয়।
রিভিশনের জন্য: পর্ব ১১ · if/elif/else আর match, পর্ব ১২ · লুপ, পর্ব ১৩ · ফাংশন, পর্ব ২০ · scope, closure আর default-এর ফাঁদ.
৫ · ডেটা স্ট্রাকচার — list, tuple, dict, set আর alias
মডিউল ৩-এর প্রায় সব আউটপুট প্রশ্নের চাবি একটি: কতগুলো object আছে, আর কোন নাম কোনটিকে দেখায়? b = a কপি করে না। a[:], list(a), a.copy() একটি shallow copy বানায় — বাইরের list নতুন, ভেতরের object পুরোনো। sort(), append(), remove()-এর মতো যে method object বদলায়, তারা None ফেরত দেয়।
(সহজ) Alias আর slice-কপি
a = [1, 2, 3]
b = a
b.append(4)
c = a[:]
c.append(5)
print(a, len(c))
[1, 2, 3] 4[1, 2, 3, 4] 5[1, 2, 3, 4, 5] 5[1, 2, 3] 5
(মাঝারি) * 3 দিয়ে বানানো grid
grid = [[0] * 3] * 3
grid[0][0] = 1
print(grid)
[[1, 0, 0], [1, 0, 0], [1, 0, 0]][[1, 0, 0], [0, 0, 0], [0, 0, 0]][[1, 1, 1], [0, 0, 0], [0, 0, 0]]TypeError
(সহজ) sort() আর sorted()
nums = [3, 1, 2]
r = nums.sort()
print(r, nums, sorted("bca"))
None [1, 2, 3] ['a', 'b', 'c'][1, 2, 3] [1, 2, 3] ['a', 'b', 'c']None [3, 1, 2] abc[1, 2, 3] [3, 1, 2] ['a', 'b', 'c']
(মাঝারি) একই key দুইবার
d = {"b": 1, "a": 2, "b": 3}
print(d, list(d))
{'b': 3, 'a': 2} ['b', 'a']{'a': 2, 'b': 3} ['a', 'b']{'b': 1, 'a': 2, 'b': 3} ['b', 'a', 'b']SyntaxError
(মাঝারি) dict.get আর [] একই print-এ
d = {"x": 1}
print(d.get("y"), d.get("y", 0), d["y"])
None 0, তারপরKeyErrorNone 0 NoneKeyError: 'y'— কিছুই ছাপা হয় নাNone 0 KeyError
(কঠিন) set-এ 1, True আর 1.0
print({1, True, 1.0}, len({3, 1, 3, 2, 1}))
{1, True, 1.0} 5{1} 3{1, 1.0} 3{True} 3
(কঠিন) tuple-এর ভেতরের list-এ +=
t = (1, [2])
try:
t[1] += [3]
except TypeError:
print("TypeError")
print(t)
- শুধু
(1, [2, 3]) TypeError, তারপর(1, [2, 3])TypeError, তারপর(1, [2])(1, [2], [3])
(মাঝারি) লুপ চলার সময় list থেকে মোছা
nums = [2, 2, 3]
for n in nums:
if n == 2:
nums.remove(n)
print(nums)
[2, 3][3][2, 2, 3]RuntimeError
নিজে চালিয়ে দেখুন — alias, copy, comprehension আর unpacking
import copy
a = [1, 2, 3]
b = a
b.append(4)
c = a[:]
c.append(5)
print("1)", a, c)
grid = [[0] * 3] * 3
grid[0][0] = 1
safe = [[0] * 3 for _ in range(3)]
safe[0][0] = 1
print("2)", grid[2], safe[2])
nested = [[1], [2]]
shallow = list(nested)
deep = copy.deepcopy(nested)
nested[0].append(9)
print("3)", shallow, deep)
x = 10
sq = [x * x for x in range(3)]
print("4)", x, sq)
first, *mid, last = [1, 2, 3, 4, 5]
print("5)", first, mid, last)
nums = [2, 2, 3]
print("6)", [n for n in nums if n != 2], {1, True, 1.0})১) [1, 2, 3, 4] [1, 2, 3, 4, 5]. ২) [1, 0, 0] [0, 0, 0] — comprehension প্রতি সারিতে নতুন list বানায়। ৩) [[1, 9], [2]] [[1], [2]] — shallow copy-র ভেতরের list পুরোনোটাই, তাই 9 দেখা যায়; deep copy-তে ভেতরের list-ও নতুন। ৪) 10 [0, 1, 4] — Python 3-এ comprehension-এর লুপ ভ্যারিয়েবল নিজের scope-এ থাকে, বাইরের x ছোঁয় না (Python 2-তে list comprehension বাইরের নাম বদলে দিত)। ৫) 1 [2, 3, 4] 5 — starred assignment-এ *mid সবসময় একটি list পায়, কিছু না থাকলে খালি list. ৬) [3] {1}.
রিভিশনের জন্য: পর্ব ১৪ · list, পর্ব ১৫ · tuple আর unpacking, পর্ব ১৬ · dictionary, পর্ব ১৭ · set, পর্ব ১৮ · comprehension, পর্ব ১৯ · mutability, alias আর copy.
৬ · বড় প্রোগ্রাম গোছানো — import, exception আর ফাইল
এই ভাগের প্রশ্নে সময়ের ক্রমই আসল। import একটি module-এর কোড প্রথমবার পুরোটা চালায়, তারপর sys.modules-এ রেখে দেয় — পরের import শুধু সেই object ফেরত দেয়। try-এর পরে except চলে exception হলে, else চলে না হলে, আর finally চলে সবসময় — এমনকি return-এর পথেও।
(মাঝারি) একই module তিনবার import — python main.py চালালে কী ছাপা হবে?
# helper.py
print("loading", __name__)
VALUE = 42
# main.py
import helper
import helper
from helper import VALUE
print(__name__, VALUE)
loading helper, তারপর__main__ 42loading helperতিনবার, তারপর__main__ 42loading __main__, তারপরmain 42loading helperদুইবার, তারপর__main__ 42
(সহজ) ওপরের helper.py ফাইলটি সরাসরি python helper.py দিয়ে চালালে কী ছাপা হবে?
# helper.py
print("loading", __name__)
VALUE = 42
loading helperloading __main__loading mainloading helper.py
(মাঝারি) try, except, else, finally আর return
def f(x):
try:
r = 10 // x
except ZeroDivisionError:
print("E", end=" ")
return -1
else:
print("L", end=" ")
return r
finally:
print("F", end=" ")
print(f(2))
print(f(0))
L F 5, তারপরE F -1L 5 F, তারপরE -1 FL F 5, তারপরE L F -1L F 5, তারপরE -1
(কঠিন) try-তে return, finally-তেও return — CPython 3.14-এ কী ঘটবে?
def g():
try:
return "try"
finally:
return "finally"
print(g())
tryfinally(সঙ্গে stderr-এ একটি SyntaxWarning)try, তারপরfinallySyntaxError
(কঠিন) except-এর নামটি ব্লকের পরে
try:
1 / 0
except ZeroDivisionError as e:
pass
print(e)
division by zeroNoneZeroDivisionError('division by zero')NameError
(মাঝারি) ফাইলের মোড "w" আর "a"
from pathlib import Path
p = Path("note.txt")
p.write_text("one\n")
with open(p, "w") as f:
f.write("two\n")
with open(p, "a") as f:
f.write("three\n")
print(p.read_text().splitlines())
['one', 'two', 'three']['three']['two', 'three']['two\n', 'three\n']
ক্রমএকটি try-এর প্রথম লাইন চলে, দ্বিতীয় লাইনে ValueError ওঠে; সঙ্গে আছে except ValueError, else আর finally, তারপর ব্লকের বাইরে একটি print. ঘটনাগুলো কোন ক্রমে ঘটবে? (▲▼ চেপে সাজান)
- try-এর প্রথম লাইন চলে
- দ্বিতীয় লাইনে ValueError — try-এর বাকি লাইন বাদ পড়ে
- মিলে যাওয়া except ValueError ব্লক চলে
- finally ব্লক চলে (else বাদ, কারণ exception হয়েছে)
- পুরো try statement-এর পরের print চলে
নিজে চালিয়ে দেখুন — exception-এর পথ
def f(x):
try:
r = 10 // x
except ZeroDivisionError:
print("E", end=" ")
return -1
else:
print("L", end=" ")
return r
finally:
print("F", end=" ")
print(f(2))
print(f(0))
def h():
for i in range(3):
try:
return i
finally:
print("cleanup", i)
print(h())
try:
1 / 0
except ZeroDivisionError as e:
saved = e
print(type(saved).__name__, "e" in dir())আউটপুট: L F 5, E F -1, cleanup 0, 0, আর ZeroDivisionError False. h()-এ লুপের প্রথম ঘোরাতেই return 0, কিন্তু ফেরার আগে finally চলে — তাই আগে cleanup 0. শেষ লাইনে "e" in dir() মিথ্যা: except-এর নাম মুছে গেছে, কিন্তু saved দিয়ে exception object-টি রয়ে গেছে।
finally-তে return দেখতে কেমন, আর 3.14 কী বলে — নিচের কোডটি এই পাতায় চালানো যাবে না (stderr-এ warning আসে), তাই আউটপুট পাশে দেওয়া হলো:
def g():
try:
return "try"
finally:
return "finally"
print(g())$ python3.14 finally_return.py
finally_return.py:5: SyntaxWarning: 'return' in a 'finally' block
return "finally"
finally
সংস্করণ-নির্ভর: warning-টি আসে ফাইল compile হওয়ার সময়, তাই চালানোর আগেই। Python 3.13 বা তার আগে একই কোড কোনো warning ছাড়াই finally ছাপত। PEP 765 finally ব্লক থেকে বের হয়ে যাওয়া break ও continue-এর জন্যও একই warning যোগ করেছে। আর import করা module-এ warning দেখা যায় শুধু প্রথমবার compile-এর সময় — পরে cache করা .pyc থেকে চললে আর দেখায় না।
রিভিশনের জন্য: পর্ব ২২ · module, package আর import, পর্ব ২৪ · exception, পর্ব ২৫ · ফাইল I/O, with আর pathlib.
৭ · অবজেক্ট-ওরিয়েন্টেড Python — attribute, MRO, dunder আর generator
OOP-এর আউটপুট প্রশ্নে attribute খোঁজার নিয়মটি মুখস্থ রাখুন: obj.x পড়লে Python আগে object-এর নিজের __dict__-এ খোঁজে, না পেলে class-এ, তারপর MRO ধরে base class-গুলোতে। কিন্তু obj.x = … লিখলে সবসময় object-এর নিজের ঘরে লেখে। আর print(obj) ডাকে __str__, কিন্তু list-এর ভেতরে থাকলে __repr__.
(সহজ) Class attribute বনাম instance attribute
class Dog:
sound = "woof"
a, b = Dog(), Dog()
a.sound = "bark"
Dog.sound = "grr"
print(a.sound, b.sound)
bark grrbark woofgrr grrbark bark
(মাঝারি) Class-এ রাখা list
class Team:
members = []
def add(self, m):
self.members.append(m)
x, y = Team(), Team()
x.add("Sakib")
y.add("Tamim")
print(x.members)
['Sakib', 'Tamim']['Sakib'][]AttributeError
(কঠিন) Diamond inheritance আর super()
class A:
def hi(self): print("A", end=" ")
class B(A):
def hi(self): print("B", end=" "); super().hi()
class C(A):
def hi(self): print("C", end=" "); super().hi()
class D(B, C):
def hi(self): print("D", end=" "); super().hi()
D().hi()
D B C AD B A C AD B AD C B A
(কঠিন) __eq__ আছে, __hash__ নেই — CPython 3.14-এ কী ঘটবে?
class P:
def __init__(self, x): self.x = x
def __eq__(self, o): return self.x == o.x
print(P(1) == P(1))
print({P(1)})
True, তারপর{<__main__.P object at …>}True, তারপরTypeErrorFalse, তারপরTypeError- শুধু
TypeError
(মাঝারি) __str__ আর __repr__ — কোনটি কখন?
class Taka:
def __init__(self, v): self.v = v
def __str__(self): return f"৳{self.v}"
def __repr__(self): return f"Taka({self.v})"
t = Taka(50)
print(t, [t], f"{t!r}")
৳50 [Taka(50)] Taka(50)৳50 [৳50] Taka(50)Taka(50) [Taka(50)] Taka(50)৳50 [৳50] ৳50
(মাঝারি) Setter ছাড়া property
class Temp:
def __init__(self): self._c = 0
@property
def f(self): return self._c * 9 / 5 + 32
t = Temp()
t._c = 100
print(t.f)
t.f = 50
212.0, তারপরAttributeError- শুধু
212.0— শেষ লাইন নিঃশব্দে f বদলায় 212, তারপরAttributeError- শুধু
AttributeError
(সহজ) dataclass-এর repr আর ==
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int = 0
p, q = Point(1), Point(1, 0)
print(p, p == q, p is q)
Point(x=1, y=0) True False<__main__.Point object at …> False FalsePoint(x=1, y=0) True TruePoint(1, 0) True False
(মাঝারি) একই generator দুবার ব্যবহার
g = (n * n for n in range(4))
print(sum(g), sum(g), list(g))
14 0 []14 14 [0, 1, 4, 9]14 0 [0, 1, 4, 9]14, তারপরStopIteration
(মাঝারি) দুটি decorator — কোনটি বাইরে?
def bold(f): return lambda: "<b>" + f() + "</b>"
def ital(f): return lambda: "<i>" + f() + "</i>"
@bold
@ital
def hi(): return "hi"
print(hi())
<i><b>hi</b></i><b>hi</b><b><i>hi</i></b><b>hi</b><i>hi</i>
নিজে চালিয়ে দেখুন — class attribute, MRO, repr আর generator
class Team:
members = []
def __init__(self, name):
self.name = name
def add(self, m):
self.members.append(m)
x, y = Team("A"), Team("B")
x.add("Sakib")
y.add("Tamim")
print("1)", x.members, y.members is x.members)
class A:
def hi(self): return "A"
class B(A):
def hi(self): return "B" + super().hi()
class C(A):
def hi(self): return "C" + super().hi()
class D(B, C):
def hi(self): return "D" + super().hi()
print("2)", D().hi(), [k.__name__ for k in D.__mro__])
class Taka:
def __init__(self, v): self.v = v
def __str__(self): return f"৳{self.v}"
def __repr__(self): return f"Taka({self.v})"
t = Taka(50)
print("3)", t, [t])
g = (n * n for n in range(4))
print("4)", sum(g), sum(g))১) ['Sakib', 'Tamim'] True — দুই দলের একটিই list. ২) DBCA ['D', 'B', 'C', 'A', 'object'] — __mro__ দিয়ে MRO নিজেই দেখে নিন; প্রশ্নে সন্দেহ হলে এটাই সবচেয়ে দ্রুত যাচাই। ৩) ৳50 [Taka(50)]. ৪) 14 0 — generator-এর exhaustion. একটি পরীক্ষা: Team-এর __init__-এ self.members = [] যোগ করে চালান — ১ নম্বর লাইন কী হবে, আগে অনুমান করুন।
রিভিশনের জন্য: পর্ব ২৯ · class ও object, পর্ব ৩০ · dunder method, পর্ব ৩১ · inheritance বনাম composition, পর্ব ৩২ · property আর dataclass, পর্ব ৩৪ · iterator আর generator, পর্ব ৩৫ · decorator.
৮ · আধুনিক Python — with, closure, itertools, type hint আর concurrency
এই ভাগের ফাঁদগুলো সময় নিয়ে: with শেষে __exit__ চলে খোলার উল্টো ক্রমে; closure ভ্যারিয়েবলের মান নয়, ভ্যারিয়েবলটিকেই ধরে রাখে (late binding); groupby শুধু পাশাপাশি থাকা সমান মান জোড়ে। আর concurrency-র প্রশ্নে আগে জিজ্ঞেস করুন: কোন অংশের ক্রম আসলে নিশ্চিত?
(মাঝারি) এক with-এ দুটি context manager
class CM:
def __init__(self, n): self.n = n
def __enter__(self):
print("enter", self.n)
return self
def __exit__(self, *exc):
print("exit", self.n)
return False
with CM(1), CM(2):
print("body")
- enter 1 → enter 2 → body → exit 2 → exit 1
- enter 1 → enter 2 → body → exit 1 → exit 2
- enter 1 → exit 1 → enter 2 → body → exit 2
- enter 2 → enter 1 → body → exit 1 → exit 2
(কঠিন) __exit__ True ফেরত দিলে
class Quiet:
def __enter__(self): return self
def __exit__(self, et, ev, tb):
print("exit:", et.__name__)
return True
with Quiet():
1 / 0
print("after")
print("next")
exit: ZeroDivisionError,after,nextexit: ZeroDivisionError, তারপরnextexit: ZeroDivisionError, তারপর traceback- শুধু
next
(মাঝারি) লুপে বানানো lambda
fs = [lambda: i for i in range(3)]
print([f() for f in fs])
[2, 2, 2][0, 1, 2][3, 3, 3]NameError
(মাঝারি) না সাজিয়ে groupby
from itertools import groupby
print([(k, len(list(g))) for k, g in groupby("aabba")])
[('a', 2), ('b', 2), ('a', 1)][('a', 3), ('b', 2)][('a', 2), ('b', 2)]TypeError
(সহজ) Type hint কি চলার সময় যাচাই হয়?
def double(x: int) -> int:
return x * 2
print(double("ha"), double([0]))
haha [0, 0]TypeErrorNone None- mypy-র error বার্তা ছাপা হয়
(কঠিন) asyncio.gather — কোন ক্রমে?
import asyncio
async def job(name, delay):
await asyncio.sleep(delay)
print("done", name)
return name
async def main():
r = await asyncio.gather(job("A", 0.2), job("B", 0.1))
print(r)
asyncio.run(main())
- done B → done A →
['A', 'B'] - done A → done B →
['A', 'B'] - done B → done A →
['B', 'A'] - done B → done A →
None
(মাঝারি) Thread আর join() — কোন আউটপুট নিশ্চিত?
import threading
results = []
def work(n):
results.append(n * n)
ts = [threading.Thread(target=work, args=(i,)) for i in range(4)]
for t in ts: t.start()
for t in ts: t.join()
print(sorted(results), len(results))
[0, 1, 4, 9] 4- ক্রম অনিশ্চিত, তাই আউটপুট বলা যায় না
[] 0- কখনো কখনো তিনটি মান — race condition
নিজে চালিয়ে দেখুন — with, closure, groupby আর thread
import threading
from itertools import groupby
class CM:
def __init__(self, n): self.n = n
def __enter__(self):
print("enter", self.n)
return self
def __exit__(self, *exc):
print("exit", self.n)
return False
with CM(1), CM(2):
print("body")
late = [lambda: i for i in range(3)]
fixed = [lambda i=i: i for i in range(3)]
print([f() for f in late], [f() for f in fixed])
print([(k, len(list(g))) for k, g in groupby("aabba")])
print([(k, len(list(g))) for k, g in groupby(sorted("aabba"))])
results = []
def work(n):
results.append(n * n)
ts = [threading.Thread(target=work, args=(i,)) for i in range(4)]
for t in ts: t.start()
for t in ts: t.join()
print(sorted(results))আউটপুট: enter 1, enter 2, body, exit 2, exit 1 (পাঁচ লাইন); তারপর [2, 2, 2] [0, 1, 2] — default argument দিয়ে late binding-এর সমাধান; [('a', 2), ('b', 2), ('a', 1)] আর সাজানোর পরে [('a', 3), ('b', 2)]; শেষে [0, 1, 4, 9]. thread থেকে সরাসরি print করা হয়নি — করলে লাইনগুলোর ক্রম আর নিশ্চিত থাকত না।
রিভিশনের জন্য: পর্ব ৩৬ · context manager, পর্ব ৩৭ · type hint আর mypy, পর্ব ৩৮ · lambda, functools আর itertools, পর্ব ৪১ · thread, process আর GIL, পর্ব ৪২ · asyncio.
৯ · বাস্তব প্রয়োগ — Pythonic idiom, regex আর বাংলা লেখা
মডিউল ৭-এর কোডে ফাঁদ কম, কিন্তু খালি ইনপুট আর Unicode-এ চমক থাকে। মনে রাখুন: খালি iterable-এ any মিথ্যা, all সত্য; dict-এর ওপর max key দেখে, মান নয়; আর Python 3-এর str হলো Unicode code point-এর ক্রম — একটি বাংলা “অক্ষর” চোখে একটি হলেও একাধিক code point হতে পারে।
(সহজ) খালি list-এ any, all আর sum
print(any([]), all([]), sum([], 10))
False True 10False False 0True True 10None None 10
(মাঝারি) dict-এর ওপর max
d = {"x": 9, "y": 2}
print(max(d), max(d, key=d.get), sorted(d, key=d.get))
y x ['y', 'x']x x ['y', 'x']9 9 [2, 9]y y ['x', 'y']
(মাঝারি) \d কি বাংলা অঙ্ক চেনে?
import re
t = "মূল্য ১২০ টাকা, 30 tk"
print(re.findall(r"\d+", t), re.findall(r"\d+", t, re.ASCII))
['১২০', '30'] ['30']['30'] ['30']['১', '২', '০', '30'] ['30']['30'] []
(মাঝারি) বাংলা string-এর দৈর্ঘ্য
s = "বাংলা"
print(len(s), len("ক্ষ"), s[0])
5 3 ব3 1 ব5 1 বা15 9 ব
(কঠিন) বাংলা অঙ্কের string থেকে int
print(int("১২৩") + 1, "১২৩".isdigit())
124 TrueValueError১২৩1 TrueTypeError
নিজে চালিয়ে দেখুন — বাংলা লেখা, regex আর idiom
import re
from collections import Counter
text = "মূল্য ১২০ টাকা, 30 tk"
print("1)", re.findall(r"\d+", text))
print("2)", re.findall(r"\d+", text, re.ASCII))
print("3)", int("১২০") + 1)
word = "বাংলা"
print("4)", len(word), [hex(ord(ch)) for ch in word])
print("5)", len("ক্ষ"))
print("6)", Counter("banana").most_common(2))
d = {"x": 9, "y": 2}
print("7)", max(d), max(d, key=d.get))
print("8)", any([]), all([]), sum([], 10))১) ['১২০', '30']. ২) ['30']. ৩) 121. ৪) 5 ['0x9ac', '0x9be', '0x982', '0x9b2', '0x9be'] — পাঁচটি code point আলাদা করে দেখা যাচ্ছে: ব, া, ং, ল, া. ৫) 3. ৬) [('a', 3), ('n', 2)] — Counter গুনে সবচেয়ে বেশি থেকে সাজায়। ৭) y x. ৮) False True 10.
রিভিশনের জন্য: পর্ব ০৯ · string আর বাংলা Unicode, পর্ব ২৮ · regular expression, পর্ব ৪৪ · Pythonic কোড, পর্ব ৪৫ · অ্যালগরিদম ও কনটেস্টে Python.
১০ · AI-এর যুগে — interpreter-ই শেষ বিচারক
আজ অনেকে AI-কে কোড দেখিয়ে জিজ্ঞেস করেন “এটা কী ছাপবে?” সাধারণ কোডে উত্তর প্রায়ই ঠিক আসে। কিন্তু এই পাতায় যে জায়গাগুলো অনুশীলন করলেন — mutable default, closure-এর late binding, is আর int cache, generator-এর exhaustion, MRO — সেখানে ভাষা-মডেল প্রায়ই আত্মবিশ্বাসী সুরে ভুল বলে। কারণ সে কোড চালায় না; অনুরূপ লেখা দেখে সম্ভাব্য উত্তর অনুমান করে। আর ইন্টারনেটের পুরোনো লেখায় Python 2-এর নিয়ম (7 / 2 হলো 3, print একটি statement, comprehension-এর নাম বাইরে ফাঁস হওয়া) এখনো প্রচুর, তাই AI মাঝে মাঝে সেগুলো মিশিয়ে ফেলে। নতুন সংস্করণের বদলও — যেমন 3.14-এর finally-return warning বা নতুন error বার্তা — সে প্রায়ই জানে না।
আর “ক্রম নিশ্চিত নয়” ধরনের কোডে একবার চালানোও প্রমাণ নয় — প্রমাণ হলো ভাষার নিয়ম।
AI সাধারণত যেখানে Python-এর আউটপুট ভুল বলে
| যে ধরনের কোড | AI-এর সাধারণ ভুল | কীভাবে ধরবেন |
|---|---|---|
| mutable default, এক print-এ কয়েকটি ডাক | প্রতিটি ডাকের “তখনকার” মান আলাদা করে লেখে | নিজে চালান; f.__defaults__ ছাপিয়ে দেখুন |
| লুপে lambda বা nested function | প্রতিটি closure-কে তৈরির সময়ের মান দেয় | চালান; সমাধান হিসেবে i=i বা functools.partial চেয়ে নিন |
is দিয়ে int বা string তুলনা | “-5 থেকে 256” নিয়ম ভুল জায়গায় খাটায়, অথবা একটি নির্দিষ্ট ফলকে নিশ্চিত বলে | প্রশ্ন করুন: এটা কি ভাষার নিয়ম, নাকি CPython-এর implementation detail? |
| generator, map, zip দুবার ব্যবহার | দ্বিতীয়বারও একই মান দেয় | চালান; iterator একবারের জিনিস — দরকারে list বানান |
| set-এর string, thread-এর print | একটি নির্দিষ্ট ক্রম লিখে দেয় | কয়েকবার চালান; নিয়মে নিশ্চয়তা আছে কি না দেখুন |
| exception-এর উত্তর | ভুল exception নাম, বা আগের লাইনের আউটপুট বাদ দেয় | traceback-এর শেষ লাইন আর stdout আলাদা করে মেলান |
AI দিয়ে নতুন অনুশীলনের প্রশ্ন বানাতে পারেন — শর্ত একটাই, উত্তর যাচাইয়ের দায়িত্ব আপনার। নিচের prompt-টি AI-কে এমনভাবে বাঁধে, যাতে সে আগে যুক্তি দেখায় আর “ক্রম নিশ্চিত নয়” ধরনের উত্তর লুকাতে না পারে:
AI-এর দেওয়া প্রশ্ন বা উত্তর হাতে পেলে — যাচাইয়ের চেকলিস্ট
যে ফাঁদে সবাই পড়ে
Python-এর “আউটপুট কী হবে?” প্রশ্নে বারবার ফিরে আসা পাঁচটি গভীর ফাঁদ। প্রতিটি নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১Default argument একবারই তৈরি হয় কেন — Python কি এটা ভুল করে বানিয়েছে?
না, এটি ইচ্ছাকৃত নকশার একটি ফল। Python-এ def একটি সাধারণ statement, যা চলার সময় একটি function object তৈরি করে। default মানগুলো সেই মুহূর্তে একবার মূল্যায়ন হয়ে function object-এর __defaults__ tuple-এ জমা থাকে। প্রতিটি ডাকে নতুন করে মূল্যায়ন হয় না। int বা str-এর মতো immutable default-এ এতে কোনো সমস্যা নেই, কারণ তাদের বদলানো যায় না। কিন্তু list, dict বা set default হলে, একটি ডাক সেটি বদলালে পরের সব ডাক সেই বদলানো object পায়।
- অফিসিয়াল স্বীকৃতি: Python-এর নিজের FAQ-তে প্রশ্নটি আছে — “Why are default values shared between objects?” — আর টিউটোরিয়ালে আলাদা করে সতর্কবাণী দেওয়া আছে। অর্থাৎ ভাষার নকশাকারীরা জানেন এটি নতুনদের অবাক করে।
- বিকল্প প্রস্তাব: ২০২১ সালে PEP 671 (Chris Angelico)
def f(x=>[])ধরনের “late-bound default” প্রস্তাব করে, যা প্রতিটি ডাকে মূল্যায়ন হতো। প্রস্তাবটি এখনো গৃহীত হয়নি; তাই আজও প্রচলিত সমাধানNonesentinel. - টুলের সাহায্য: Pylint-এর W0102 (dangerous-default-value) আর flake8-bugbear-এর B006 (ruff-এও আছে) mutable default দেখলেই সতর্ক করে।
মজার ব্যাপার, এই আচরণকে কাজেও লাগানো হয়: def fib(n, _memo={}) লিখে একটি সহজ cache বানানো যায়, যা ডাকের পর ডাকে টিকে থাকে। তবে আজ এ কাজে functools.cache অনেক পরিষ্কার। আউটপুট প্রশ্নে পড়ার নিয়ম: ফাংশনের প্রথম লাইনে mutable default দেখলেই খাতায় একটি আলাদা object (L1) আঁকুন, যার জীবন ফাংশনের সমান লম্বা — আর মনে রাখুন print(f(1), f(2))-এ দুটি argument একই object হলে দুটিই শেষ অবস্থা দেখাবে।
মূল উপলব্ধি: default মান “ফাংশন তৈরির সময়ের” জিনিস, “ডাকের সময়ের” নয়। immutable হলে নিরাপদ; mutable হলে None দিয়ে শুরু করে ভেতরে নতুন object বানান।
প্র ০২লুপের closure সবাই শেষ মানটাই কেন মনে রাখে — আর এর তিনটি সমাধানের পার্থক্য কী?
closure কোনো মান কপি করে না; সে বাইরের scope-এর ভ্যারিয়েবলটিকে ধরে রাখে। CPython-এ এটি হয় একটি “cell” object দিয়ে, যা ফাংশনের __closure__-এ দেখা যায়। লুপ যতবার ঘোরে, একই ভ্যারিয়েবল i নতুন মান পায়; সব lambda একই cell দেখে। ডাকার সময় তারা cell-এর তখনকার মান পড়ে — লুপ শেষ, তাই শেষ মান। একে বলে late binding, আর Python-এর FAQ-তে এর জন্যও আলাদা প্রশ্ন আছে: “Why do lambdas defined in a loop with different values all return the same result?”
lambda i=i: i: default মান তৈরি হয় lambda তৈরির মুহূর্তে (প্র ০১-এর নিয়ম), তাই প্রতিটি lambda নিজের মান পায়। অসুবিধা: কেউ ভুল করে argument দিলে মান বদলে যায়।functools.partial(f, i): একটি নতুন callable, যার argument তৈরির সময়েই বাঁধা। পরিষ্কার, আর argument লুকানো থাকে না।- factory ফাংশন:
def make(i): return lambda: i— প্রতিটি ডাক নতুন scope আর নতুন cell তৈরি করে।
এই সমস্যা শুধু Python-এর নয়। JavaScript-এ var দিয়ে লুপে closure বানালে হুবহু একই বাগ হতো; ২০১৫ সালের ES2015 let এনে প্রতিটি ঘোরায় নতুন binding দিয়ে সমস্যাটি মিটিয়েছে। Python সেই পথে যায়নি — লুপ এখানে নতুন scope তৈরি করে না। তবে comprehension নিজের scope পায় (Python 3 থেকে); Python 3.12-এর PEP 709 comprehension-কে ভেতরে inline করলেও এই আচরণ বদলায়নি, তাই [lambda: i for i in range(3)]-এর ফল এখনো [2, 2, 2].
মূল উপলব্ধি: closure “নাম” ধরে, “মান” নয়। মান আটকাতে চাইলে তৈরির মুহূর্তে মূল্যায়ন হয় এমন কিছু ব্যবহার করুন — default argument, partial বা নতুন ফাংশন-ডাক।
প্র ০৩is আর ==-এর পার্থক্য কী, আর ছোট সংখ্যায় is কেন “কাজ করে”?
== জিজ্ঞেস করে “মান কি সমান?” — এটি __eq__ ডাকে, যা প্রতিটি class নিজের মতো লিখতে পারে। is জিজ্ঞেস করে “এরা কি একই object?” — এটি কোনো method ডাকে না, শুধু পরিচয় (identity) মেলায়; CPython-এ কার্যত মেমরি ঠিকানা তুলনা। দুটি আলাদা list-এ একই সংখ্যা থাকলে == সত্য, is মিথ্যা।
- Small int cache: CPython চালু হওয়ার সময়ই -5 থেকে 256 পর্যন্ত int object আগে থেকে বানিয়ে রাখে, আর এই মানগুলো যেখানেই লাগুক, সেই একই object দেয়। তাই
int("100") is int("100")সত্য, কিন্তু 300-এ মিথ্যা। - Constant sharing: একই ফাইলের একই code object-এ একই literal দুবার থাকলে compiler একটিই constant রাখে — তাই স্ক্রিপ্টে
a = 1000; b = 1000-এa is bসত্য হতে পারে, অথচ REPL-এ আলাদা লাইনে লিখলে মিথ্যা। - String interning: identifier-এর মতো দেখতে ছোট string অনেক সময় একই object হয়ে যায়;
sys.intern()দিয়ে জোর করা যায়।
এগুলোর কোনোটিই ভাষার নিয়ম নয় — সবই CPython-এর implementation detail, যা PyPy বা ভবিষ্যৎ সংস্করণে বদলাতে পারে। এই বিপদের কারণে Python 3.8 থেকে x is 5-এর মতো literal-এর সাথে is লিখলে compiler SyntaxWarning দেয়: "is" with 'int' literal. Did you mean "=="? তাহলে is কোথায় ঠিক? যেখানে Python একটিমাত্র object-এর নিশ্চয়তা দেয় — None, True, False, Ellipsis আর নিজের বানানো sentinel. PEP 8 এই কারণে বলে: if x is None, কখনো if x == None নয় — কারণ একটি class চাইলে __eq__ দিয়ে == None-কে সত্য বানাতে পারে।
মূল উপলব্ধি: মান তুলনায় সবসময় ==; পরিচয় তুলনায় is, শুধু singleton-এ। আউটপুট প্রশ্নে বড় int বা string-এ is দেখলে উত্তরে লিখুন: “CPython-এ এই ফল, কিন্তু নিশ্চয়তা নেই।”
প্র ০৪float-এ 0.1 + 0.2 ≠ 0.3 কেন, আর Python এত লম্বা সংখ্যা ছাপে কেন?
Python-এর float হলো IEEE 754 standard-এর ৬৪-বিট double — প্রায় সব আধুনিক CPU এই ফরম্যাটে হিসাব করে। এতে সংখ্যা রাখা হয় binary ভগ্নাংশে। যেমন দশমিকে ১/৩ শেষ হয় না (0.333…), তেমনি binary-তে ১/১০ শেষ হয় না। তাই 0.1 আসলে রাখা হয় তার সবচেয়ে কাছের binary মান হিসেবে, যা 0.1-এর চেয়ে সামান্য বড়। 0.2-ও তাই। যোগ করলে ছোট ছোট ভুল জমে, আর ফল এমন একটি float হয় যা “0.3 লিখলে যে float পাওয়া যায়” তার পরেরটি।
- লম্বা repr: Python 3.1 থেকে (আর 2.7-এ) float-এর repr হলো এমন সবচেয়ে ছোট দশমিক লেখা, যা পড়ে আবার হুবহু একই float পাওয়া যায় (David Gay-এর অ্যালগরিদমের ওপর ভিত্তি করে)। 0.1-এর জন্য সেটি “0.1”, কিন্তু যোগফলের জন্য “0.30000000000000004” — কারণ এর চেয়ে ছোট লেখা অন্য float-কে বোঝাত।
- round-এর চমক:
round(2.675, 2)হলো 2.67, কারণ 2.675-এর আসল মান 2.67499999…; Python-এর ডকুমেন্টেশন নিজেই এই উদাহরণ দিয়ে বলে এটি বাগ নয়। আর ঠিক মাঝখানের মানে Python 3 banker's rounding করে (round(2.5)→ 2), যাতে অনেক সংখ্যা যোগ করলে ওপরের দিকে পক্ষপাত না জমে; Python 2 মাঝখানের মান শূন্য থেকে দূরে নিত। - কী করবেন: তুলনায়
math.isclose()(Python 3.5, PEP 485); টাকা-পয়সায়decimal.Decimal("0.1")বা পয়সায় int; ভগ্নাংশেfractions.Fraction.
পরীক্ষার উত্তরে শুধু “False” লিখলে আংশিক নম্বর; সাথে লিখুন “0.1 binary-তে ঠিকঠাক রাখা যায় না, IEEE 754 double-এ যোগফল 0.30000000000000004”. এটি C, Java, JavaScript — সব ভাষায় একই, কারণ সমস্যাটি ভাষার নয়, ফরম্যাটের।
মূল উপলব্ধি: float একটি কাছাকাছি মান, সঠিক মান নয়। float-এ == প্রায় সবসময় ভুল প্রশ্ন; “কতটা কাছে?” জিজ্ঞেস করুন।
প্র ০৫Generator একবার ব্যবহারেই ফুরিয়ে যায় কেন, আর কোন কোন জিনিস এভাবে আচরণ করে?
generator একটি iterator — এক দিকে চলা একটি কার্সর, যে প্রতিবার next()-এ একটি মান তৈরি করে দেয় আর নিজের অবস্থান মনে রাখে। শেষে পৌঁছালে StopIteration তোলে, আর তারপর চিরকাল তাই করবে; পেছনে ফেরার বা শুরু থেকে আবার চালানোর কোনো উপায় নেই। sum, list, for — সবাই ভেতরে next() ডাকে আর StopIteration চুপচাপ সামলায়, তাই দ্বিতীয়বার কোনো error আসে না, শুধু খালি ফল।
- ইতিহাস: generator এসেছে Python 2.2-এ (PEP 255, ২০০১), generator expression 2.4-এ (PEP 289). Python 3.0-এ
map,filter,ziplist-এর বদলে iterator ফেরত দিতে শুরু করে — মেমরি বাঁচাতে। তাই Python 2-এর অনেক পুরোনো কোড, যাm = map(...)-কে দুবার ব্যবহার করত, Python 3-এ চুপচাপ ভুল ফল দেয়। - একবারের জিনিস: generator,
map,filter,zip,enumerate,reversed, খোলা ফাইল,iter(x). বারবার চলে: list, tuple, str, dict, set,range— এরা iterable; প্রতিটি for লুপে নতুন iterator বানায়। - লুকানো খরচ:
3 in gলিখলে generator 3 পর্যন্ত মান খেয়ে ফেলে; পরের লুপ শুরু হয় তার পর থেকে।
নিজে পরীক্ষা করার সহজ উপায়: iter(x) is x সত্য হলে x নিজেই একটি iterator — অর্থাৎ একবারের। একাধিকবার দরকার হলে list() বানিয়ে রাখুন, বা ফাংশনে মোড়ান যাতে প্রতিবার নতুন generator তৈরি হয়; itertools.tee একটি বিকল্প, তবে সেটিও ভেতরে মান জমিয়ে রাখে।
মূল উপলব্ধি: iterable হলো বই, iterator হলো বুকমার্ক। বুকমার্ক শেষ পাতায় পৌঁছালে আবার পড়তে নতুন বুকমার্ক লাগে — আর generator নিজেই একটি বুকমার্ক, বই নয়।
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বে ব্যবহৃত সব শব্দ এখানে একসাথে:
মনে রাখুন
- আউটপুট অনুমান নয় — trace table এঁকে লাইন ধরে চালান: নাম আর object আলাদা কলামে, তীর দিয়ে যুক্ত; call stack; আর কোন জিনিস কখন মূল্যায়ন হয়।
=নাম বাঁধে, কপি করে না। alias দিয়ে বদল সব নামে দেখা যায়;[:]shallow copy;[[0]*3]*3একই সারি তিনবার; বদলানো methodNoneদেয়।- default মান আর decorator চলে
def-এর সময়, একবার; closure নাম ধরে (late binding); generator একবারের;finallyসব পথে চলে। - উত্তর পাঁচ রকম: নির্দিষ্ট আউটপুট, নির্দিষ্ট exception (আগের আউটপুটসহ), SyntaxError (কিছুই চলে না), ক্রম নিশ্চিত নয়, সংস্করণ বা implementation-নির্ভর।
- AI-এর আউটপুট-অনুমান যাচাই করুন: নিজে trace করুন, python3.14 দিয়ে চালান, stdout আর stderr আলাদা দেখুন, implementation detail-কে নিশ্চিত বলবেন না।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৫৩ · MCQ প্রশ্নব্যাংক — যেকোনো পরীক্ষার প্রস্তুতিপরবর্তী পর্বপুরো কোর্সের ধারণা যাচাইয়ের বিষয়ভিত্তিক MCQ.
- পর্ব ৫৪ · লিখিত ও মৌখিক (ভাইভা) প্রশ্নলিখিত পরীক্ষা“পার্থক্য লিখুন”, “ফাংশন লিখুন” আর ভাইভার উত্তর সাজানোর কৌশল।
- পর্ব ৫৫ · Python কোডিং ইন্টারভিউ — সমস্যা থেকে সমাধানইন্টারভিউসমস্যা বোঝা, পরিকল্পনা, কোড আর টেস্ট — ইন্টারভিউর পুরো ধারা।
- C++ প্রোগ্রামিং · আউটপুট কী হবে?C++ কোর্সconstructor-destructor-এর ক্রম, virtual আর move নিয়ে আউটপুট প্রশ্ন।
- Python প্রোগ্রামিং · কোর্স হোম৫৫টি পর্বপুরো কোর্সের রোডম্যাপ — যে ভাগে ভুল বেশি হলো, সেই পর্বে ফিরে যান।