কম্প্রিহেনশন — লুপকে এক লাইনে
লিস্ট, ডিকশনারি আর সেট কম্প্রিহেনশন, ছাঁকার if আর বেছে নেওয়ার if/else-এর পার্থক্য, আর কখন কম্প্রিহেনশন লিখতে নেই।
কীভাবে শিখতে চান?
যে সমস্যাটা আমরা সমাধান করছি
গত ছয়টি অধ্যায়ে একটি লেখার ধরন বারবার ফিরে এসেছে:
marks = [72, 45, 90, 33]
doubled = []
for mark in marks:
doubled.append(mark * 2)
print(doubled)
doubled = [mark * 2 for mark in marks]
print(doubled)[144, 90, 180, 66]
[144, 90, 180, 66]উপরের চারটি লাইন আর নিচের একটি লাইন হুবহু একই কাজ করে।
চারটি লাইনের মধ্যে তিনটি আসলে কাজের কথা নয়, ব্যবস্থাপনার কথা — একটি খালি লিস্ট বানানো, তার উপর ঘোরা, প্রতিটিতে append করা। আসল সিদ্ধান্তটা কেবল একটাই: mark * 2। বাকিটা প্রতিবার একই।
কম্প্রিহেনশন সেই পুনরাবৃত্তিটুকু সরিয়ে দেয়। এটি নতুন কোনো ক্ষমতা নয় — যা লিখতে পারতেন তা-ই, কম শব্দে। আর সেই কারণেই এর একটি সীমা আছে, যা এই অধ্যায়ের শেষ অংশের বিষয়: যা ছোট করলে পড়া সহজ হয় তাই ছোট করা উচিত, সবকিছু নয়।
এই অধ্যায় শেষে আপনি পারবেন
- লিস্ট কম্প্রিহেনশন লিখতে ও পড়তে
ifদিয়ে ছেঁকে নিতে, আরif/elseদিয়ে বেছে নিতে- ডিকশনারি আর সেট কম্প্রিহেনশন লিখতে
- বলতে কখন কম্প্রিহেনশন লিখতে নেই
- একটি অপাঠ্য কম্প্রিহেনশনকে আবার লুপে ভেঙে দিতে
আগে যা জানা লাগবে: নেস্টেড ডেটা — ডিকশনারির লিস্ট।
গঠনটা
[ result for item in source ]এখানে result হলো যা বানাতে চান, item প্রতিবার আসা একটি জিনিস, আর source সেই লিস্ট (বা অন্য সংগ্রহ) যেখান থেকে জিনিসগুলো আসছে।
ইংরেজিতে পড়লে বাক্যটা সোজা: "`mark 2 for each mark in marks`"*।
মনে রাখার একটি উপায়: for অংশটা আগে পড়ুন, তারপর শুরুর অংশটা। কোথা থেকে আসছে সেটা আগে জানলে কী বানানো হচ্ছে সেটা বোঝা সহজ।
ছেঁকে নেওয়া
শেষে একটি if জুড়লে কেবল যেগুলো শর্ত মেলে সেগুলোই আসে:
marks = [72, 45, 90, 33]
passed = [mark for mark in marks if mark >= 40]
print(passed)[72, 45, 90]এখানে প্রথম অংশটা কেবল mark — কোনো হিসাব নয়, যেমন আছে তেমনই। ছাঁকা আর রূপান্তর, দুটোই একসাথে করা যায়:
names = [" rafi ", "AHMED", " dia"]
clean = [name.strip().lower() for name in names if name.strip()]
print(clean)['rafi', 'ahmed', 'dia']শর্তটি name.strip() — কোনো তুলনা নেই। ৮ নম্বর অধ্যায়ের truthiness-এর নিয়ম মনে করুন: খালি স্ট্রিং মানে false। তাই কেউ কেবল স্পেস লিখে থাকলে সেটি বাদ পড়ে যায়।
if শেষে বনাম if শুরুতে
দুটি আলাদা জিনিস, আর জায়গাটাই পার্থক্য:
marks = [72, 33, 90]
labels = ["pass" if m >= 40 else "fail" for m in marks]
print(labels)['pass', 'fail', 'pass']শেষের if ছাঁকে — কিছু জিনিস ফলে আসে না, তাই ফলের দৈর্ঘ্য কম হতে পারে।
শুরুর if ... else বেছে নেয় — প্রতিটি জিনিসের জন্য কিছু না কিছু আসেই, তাই দৈর্ঘ্য একই থাকে। এটি ১৫ নম্বর অধ্যায়ে দেখা সেই এক-লাইনের শর্ত, কেবল কম্প্রিহেনশনের ভেতরে।
দৈর্ঘ্য বদলায় কি না — এই প্রশ্নটাই দুটিকে আলাদা করার সবচেয়ে সহজ উপায়।
ডিকশনারি আর সেটও
একই গঠন, কেবল ব্র্যাকেট আলাদা:
marks = {"rafi": 72, "ahmed": 45, "bilal": 90}
passed = {name: mark for name, mark in marks.items() if mark >= 50}
print(passed)
doubled = {name: mark * 2 for name, mark in marks.items()}
print(doubled){'rafi': 72, 'bilal': 90}
{'rafi': 144, 'ahmed': 90, 'bilal': 180}কার্লি ব্র্যাকেট আর একটি কোলন — ডিকশনারি কম্প্রিহেনশন। marks.items() প্রতিবার একটি টাপল দেয়, আর name, mark সেটি আনপ্যাক করে, ঠিক যেমন লুপে করত।
কোলন ছাড়া একই কার্লি ব্র্যাকেট দিলে সেট:
orders = [
{"item": "pen"},
{"item": "bag"},
{"item": "pen"},
]
items = {order["item"] for order in orders}
print(sorted(items))['bag', 'pen']১৬ নম্বর অধ্যায়ের “আলাদা কয়টি” প্রশ্নটি এক লাইনে। pen দুবার এলেও সেটে একবারই থাকে।
সংখ্যাকে স্ট্রিংয়ে — যেটি আপনি আগেই দেখেছেন
১৩ নম্বর অধ্যায়ে এই লাইনটি এসেছিল, আর তখন বলা হয়েছিল ব্যাখ্যা পরে আসবে:
marks = [72, 45, 90]
print(", ".join(str(m) for m in marks))
print([str(m) for m in marks])72, 45, 90
['72', '45', '90']এখন পুরোটা পড়া যায়। str(m) for m in marks একটি কম্প্রিহেনশনই, কেবল স্কয়ার ব্র্যাকেট ছাড়া — এই রূপটির নাম জেনারেটর এক্সপ্রেশন, আর সে গোটা লিস্টটা একবারে বানায় না, একটি একটি করে join-কে দেয়। ফাংশনের একমাত্র আর্গুমেন্ট হলে স্কয়ার ব্র্যাকেট বাদ দেওয়া যায়, আর বড় ডেটায় সেটি মেমোরি বাঁচায়।
কখন লিখবেন না
কম্প্রিহেনশন লেখা যায় বলেই লিখতে হবে, এমন নয়। এটি দেখুন:
rows = [["1", "2"], ["3", "x"]]
numbers = [int(c) for row in rows for c in row if c.isdigit()]
print(numbers)[1, 2, 3]লাইনটি ঠিক কাজ করে। কিন্তু এতে দুটি for আর একটি if আছে, আর ক্রমটা মনে রাখতে হয়: for গুলো বাইরে থেকে ভেতরে পড়তে হয়, যেভাবে নেস্টেড লুপে লেখা হতো। ছয় মাস পর এই লাইনটা পড়ে বোঝা কষ্ট।
লুপে লিখলে:
rows = [["1", "2"], ["3", "x"]]
numbers = []
for row in rows:
for c in row:
if c.isdigit():
numbers.append(int(c))
print(numbers)[1, 2, 3]বেশি লাইন, কম চিন্তা। কোডের উদ্দেশ্য সংক্ষিপ্ত হওয়া নয়, পরিষ্কার হওয়া — আর বেশিরভাগ ক্ষেত্রে সংক্ষিপ্ততাই পরিষ্কার করে, কিন্তু সবসময় নয়।
কাজের নিয়ম হিসেবে: একটি for, আর বড়জোর একটি if — কম্প্রিহেনশন। তার বেশি হলে লুপ। আর কম্প্রিহেনশনের ভেতরে কখনো print বা append লিখবেন না; সেটি একটি ভ্যালু বানানোর জিনিস, কাজ করার জিনিস নয়।
একটা সম্পূর্ণ উদাহরণ
views.py:
# One list of records in, three different views out
orders = [
{"customer": "rafi", "item": "pen", "quantity": 3, "price": 15.0},
{"customer": "ahmed", "item": "bag", "quantity": 1, "price": 850.0},
{"customer": "rafi", "item": "ink", "quantity": 2, "price": 120.0},
{"customer": "dia", "item": "pen", "quantity": 5, "price": 15.0},
]
totals = [order["quantity"] * order["price"] for order in orders]
print("Totals :", totals)
big = [order["item"] for order in orders if order["quantity"] * order["price"] > 100]
print("Over 100 :", big)
items = {order["item"] for order in orders}
print("Distinct :", sorted(items))
by_item = {order["item"]: order["price"] for order in orders}
print("Price list:", by_item)
print("Grand :", sum(totals))Totals : [45.0, 850.0, 240.0, 75.0]
Over 100 : ['bag', 'ink']
Distinct : ['bag', 'ink', 'pen']
Price list: {'pen': 15.0, 'bag': 850.0, 'ink': 120.0}
Grand : 1210.0১৭ নম্বর অধ্যায়ের সেই একই ডেটা, আর সেখানে যা লুপে লেখা হয়েছিল তার বেশিরভাগ এখানে এক লাইনে।
তিনটি জিনিস লক্ষ করার মতো।
totals আর sum(totals) আলাদা লাইনে। মোট বের করতে sum(order["quantity"] * order["price"] for order in orders) লেখা যেত, কিন্তু তাহলে প্রতিটি লাইনের ভ্যালু আর হাতে থাকত না। একটি মধ্যবর্তী নাম প্রায়ই একটি এক-লাইনের কৌশলের চেয়ে দামি।
big-এর ভেতরে একই হিসাব দুবার। শর্তেও quantity * price, আর ফলেও লাগত। এখানে ফলে শুধু নাম নেওয়া হয়েছে বলে সমস্যা হয়নি — কিন্তু দুটোই লাগলে এটি কম্প্রিহেনশন ছেড়ে লুপে ফেরার একটি ভালো কারণ, কারণ লুপে ভ্যালুটি একবার হিসাব করে একটি নামে রাখা যায়।
by_item একটি রেকর্ড হারিয়েছে। চারটি অর্ডার, কিন্তু ফলে তিনটি জোড়া — pen দুবার আছে, আর ১৫ নম্বর অধ্যায়ের নিয়ম মতো শেষেরটি আগেরটিকে চুপচাপ বদলে দিয়েছে। এখানে দুটি pen-এর দাম একই বলে ক্ষতি হয়নি; আলাদা হলে নীরবে ভুল হতো। কম্প্রিহেনশন এই সমস্যাটা তৈরি করে না, কিন্তু সংক্ষিপ্ত বলে সেটি চোখে পড়ার সুযোগও কমিয়ে দেয়।
কিছু ভাঙা অবস্থা ও তার সমাধান
SyntaxError — if আর else একসাথে শেষে লিখলে [m for m in marks if m >= 40 else 0] চলে না। ছাঁকার if শেষে বসে আর তার কোনো else হয় না; বেছে নেওয়ার if ... else শুরুতে বসে: [m if m >= 40 else 0 for m in marks]।
ফলটি None-এ ভরা কম্প্রিহেনশনের ভেতরে এমন কিছু ডাকা হয়েছে যা কিছু ফেরত দেয় না — যেমন [items.append(x) for x in other]। append None ফেরত দেয়, তাই ফলটি None-এর একটি লিস্ট। কম্প্রিহেনশন ভ্যালু বানানোর জন্য; কাজ করাতে চাইলে সাধারণ লুপ লিখুন।
NameError — কম্প্রিহেনশনের ভেরিয়েবলটি বাইরে নেই [m * 2 for m in marks] লেখার পর বাইরে m ব্যবহার করা যায় না। ১১ নম্বর অধ্যায়ে for লুপের ভেরিয়েবল লুপের পরেও টিকে থাকত; কম্প্রিহেনশনের ভেরিয়েবল থাকে না — সে কেবল ভেতরেই বাঁচে।
ডিকশনারি কম্প্রিহেনশনে জোড়ার সংখ্যা কম একই key একাধিকবার তৈরি হয়েছে, আর শেষেরটি আগেরগুলোকে বদলে দিয়েছে। len() মিলিয়ে দেখুন।
লাইনটা পড়া যাচ্ছে না সেটাই যথেষ্ট কারণ। লুপে ভেঙে ফেলুন — গতির যে সামান্য পার্থক্য হয় তা শেখার পর্যায়ে খুব কমই গুরুত্বপূর্ণ, আর পরের মানুষটি (সম্ভবত আপনি) কৃতজ্ঞ থাকবে।
ধাপ ৪ / ৬ — অনুমান
যাচাই করুন
শেষে একটি if আছে। কী ছাপা হবে?
marks = [72, 45, 90, 33]
passed = [mark for mark in marks if mark >= 40]
print(passed)- A[72, 45, 90, 33]
- B[True, True, True, False]
- C[33]
- D[72, 45, 90]
এবার if শুরুতে, আর সাথে else। কী ছাপা হবে?
marks = [72, 33, 90]
labels = ["pass" if m >= 40 else "fail" for m in marks]
print(labels)- A['pass', 'fail', 'pass']
- B['pass', 'pass']
- C[72, 90]
- D['pass', 'fail']
কম্প্রিহেনশনের ভেতরে append ডাকা হয়েছে। দুটি লাইন কী ছাপবে?
source = ["pen", "bag"]
target = []
result = [target.append(item) for item in source]
print(target)
print(result)- A['pen', 'bag'] [None, None]
- Bএকটি `TypeError`
- C['pen', 'bag'] ['pen', 'bag']
- D[] ['pen', 'bag']
উত্তর দিতে অ্যাকাউন্ট লাগবে
উত্তর মিলিয়ে দেখতে সাইন ইন করুন
প্রশ্নগুলো উপরে আছে, আর মাথায় মাথায় উত্তর ভেবে নেওয়াই আসল কাজ। সঠিক উত্তর, ব্যাখ্যা আর তিন ধাপের ইঙ্গিত দেখতে সাইন ইন করুন।
নিজে করুন
clean.py নামে একটি ফাইল লিখুন, যেখানে একটি লিস্টে কিছু অগোছালো নাম থাকবে — কিছুতে বাড়তি স্পেস, কিছু বড় হাতের, আর অন্তত একটি সম্পূর্ণ খালি।
কম্প্রিহেনশন দিয়ে বানান:
- পরিষ্কার নামের একটি লিস্ট — স্পেস ছাঁটা, ছোট হাতের, আর খালিগুলো বাদ
- প্রতিটি নামের দৈর্ঘ্যের একটি লিস্ট
- পাঁচ অক্ষরের বেশি নামগুলোর একটি লিস্ট
{name: length}আকারের একটি ডিকশনারি- নামের প্রথম অক্ষরগুলোর একটি সেট
তারপর দুটি পরীক্ষা:
- ১ নম্বরটি একটি সাধারণ
forলুপ দিয়েও লিখুন, আর দুটির ফল সমান কি না==দিয়ে যাচাই করুন - একটি কম্প্রিহেনশন লিখুন যাতে দুটি
forআর একটিifআছে, তারপর সেটিকে লুপে ভেঙে ফেলুন। কোনটি পড়তে সহজ লাগছে?
শেষ প্রশ্নটার কোনো “সঠিক” উত্তর নেই, আর সেটাই মূল কথা। প্রশ্নটা নিজেকে করতে শেখাই এই অধ্যায়ের আসল শিক্ষা।
ধাপ ৬ / ৬
কঠিন করা — অধ্যায়ের কুইজ
সহজ থেকে কঠিন — দশটি প্রশ্ন, শেষেরগুলো ইচ্ছে করেই কঠিন।
সাইন ইন করে কুইজ দিন