অধ্যায় 12

লিস্ট — একই নামে অনেকগুলো মান

একটি নামে যত খুশি মান রাখা, সূচক আর স্লাইস দিয়ে ভেতরে পৌঁছানো, আর লিস্ট বদলানোর মেথডগুলো — সাথে দুটি নাম এক লিস্টে লেগে যাওয়ার ফাঁদ।

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

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

একটা দোকানের তিনটি পণ্যের দাম রাখতে হবে। এতদিন যা শিখেছি তাতে উপায় একটাই:

python
price_1 = 15
price_2 = 60
price_3 = 850

print(price_1 + price_2 + price_3)
text
925

এখন প্রশ্ন: পণ্য চল্লিশটা হলে? চল্লিশটা ভেরিয়েবল। আর যোগ করার লাইনটা চল্লিশ টুকরোর। আর দোকানে একটা নতুন পণ্য এলে কোডে গিয়ে নতুন ভেরিয়েবল বানাতে হবে।

সমস্যাটা শুধু পরিশ্রমের নয়, গঠনেরও। price_1, price_2, price_3 — এই তিনটি নাম পাইথনের কাছে সম্পূর্ণ সম্পর্কহীন, ঠিক যেমন name আর age সম্পর্কহীন। কিন্তু আপনি জানেন এরা একই জিনিসের তিনটি উদাহরণ। সেই «একই জিনিসের অনেকগুলো» ব্যাপারটা কোডে লেখার কোনো উপায় এখনো আমাদের নেই।

লিস্ট সেই উপায়। একটি নাম, তার নিচে যত খুশি মান, ক্রম বজায় রেখে।

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

  • লিস্ট বানাতে, আর সূচক ও স্লাইস দিয়ে তার ভেতরে পৌঁছাতে
  • append, insert, remove, pop দিয়ে লিস্ট বদলাতে
  • বলতে কেন লিস্ট বদলানো যায় অথচ লেখা বদলানো যায় না
  • b = a লিখলে দুটি নাম একই লিস্টে লেগে যায় — এটা চিনতে ও এড়াতে
  • IndexError আর ValueError পড়ে বুঝতে কী ভুল হয়েছে

আগে যা জানা লাগবে: for লুপ আর range।


একটি লিস্ট বানানো

বর্গবন্ধনী, আর ভেতরে কমা দিয়ে আলাদা করা মান:

python
prices = [15, 60, 850]

print(prices)
print(len(prices))
text
[15, 60, 850]
3

len() এখানেও কাজ করে, ঠিক লেখার মতোই — তিনটি মান, তাই 3।

ছাপালে বর্গবন্ধনীসহ পুরো লিস্টটাই দেখা যায়। এটা মানুষের পড়ার জন্য নয়, বোঝার জন্য: [15, 60, 850] দেখে বোঝা যায় এটি একটি লিস্ট, 925 নয়।

ভেতরে পৌঁছানো

স্ট্রিং-এর সূচক যেভাবে কাজ করত, ঠিক সেভাবেই — শূন্য থেকে শুরু, পিছন থেকে ঋণাত্মক:

python
prices = [15, 60, 850]

print(prices[0])
print(prices[-1])
print(prices[1:])
text
15
850
[60, 850]

তিনটি লাইনের তিনটিই ষষ্ঠ অধ্যায়ের নিয়ম, নতুন কিছু নয়। একটাই পার্থক্য খেয়াল করার মতো: prices[0] একটি সংখ্যা দেয়, কিন্তু prices[1:] একটি লিস্ট দেয় — স্লাইস সবসময় একই ধরনের জিনিস ফেরত দেয়, একটি টুকরো নয়।

এবার আসল পার্থক্য: লিস্ট বদলানো যায়

ষষ্ঠ অধ্যায়ে দেখেছিলাম লেখার ভেতরের একটি অক্ষর বদলানো যায় না:

python
name = "rafi"
name[0] = "R"
text
TypeError: 'str' object does not support item assignment

লিস্টে ঠিক একই লাইন কাজ করে:

python
prices = [15, 60, 850]
prices[0] = 20
print(prices)
text
[20, 60, 850]

কোনো নতুন লিস্ট তৈরি হয়নি — এই লিস্টটাই বদলে গেছে। এটিই লিস্টের সবচেয়ে গুরুত্বপূর্ণ বৈশিষ্ট্য, আর এই অধ্যায়ের শেষ অংশের ফাঁদটাও এখান থেকেই আসে।

পরিভাষায়: লেখা immutable (অপরিবর্তনীয়), লিস্ট mutable (পরিবর্তনীয়)।

যোগ করা আর বাদ দেওয়া

python
stock = ["pen", "notebook"]

stock.append("bag")
stock.insert(1, "eraser")
print(stock)

stock.remove("pen")
print(stock)

last = stock.pop()
print(last)
print(stock)
text
['pen', 'eraser', 'notebook', 'bag']
['eraser', 'notebook', 'bag']
bag
['eraser', 'notebook']

চারটি মেথড, চারটি আলাদা কাজ:

  • append(x) — শেষে যোগ করে
  • insert(i, x) — i নম্বর জায়গায় ঢোকায়, বাকিদের ডানে সরিয়ে
  • remove(x) — মান ধরে খুঁজে প্রথমটি বাদ দেয়
  • pop() — শেষেরটি বাদ দেয় এবং ফেরত দেয়

শেষ দুটির পার্থক্যটা খেয়াল করুন, কারণ এখানেই বেশিরভাগ বিভ্রান্তি: remove জানতে চায় কী বাদ দিতে হবে, pop জানতে চায় কোথা থেকে। আর pop বাদ দেওয়া জিনিসটা হাতে ধরিয়ে দেয়, remove দেয় না।

এই মেথডগুলো কিছু ফেরত দেয় না। স্ট্রিং-এ name.upper()-এর ফল ধরে রাখতে হতো, কারণ সে নতুন লেখা বানিয়ে ফেরত দিত। লিস্টে ঠিক উল্টো: append মূল লিস্টটাই বদলে দেয়, আর ফেরত দেয় None।

তাই এই লাইনটি একটি ক্লাসিক ভুল:

python
stock = ["pen", "notebook"]
stock = stock.append("bag")
print(stock)
text
None

লিস্টটা হারিয়ে গেল। stock.append("bag") লিখুন, stock = stock.append("bag") নয়।

খোঁজা আর ঘোরা

python
stock = ["pen", "notebook", "bag"]

print("pen" in stock)
print("chair" in stock)

for item in stock:
    print(item.upper())
text
True
False
PEN
NOTEBOOK
BAG

in লেখার ভেতরে খুঁজত, লিস্টেও খোঁজে — একই শব্দ, একই কাজ।

আর for item in stock লিস্টের প্রতিটি মানের উপর দিয়ে হাঁটে। খেয়াল করুন এখানে range লাগেনি, সূচকের হিসাবও করতে হয়নি — একাদশ অধ্যায়ে লেখার অক্ষরের উপর যেভাবে হেঁটেছিলাম, হুবহু সেভাবে।

অবস্থান আর মান দুটোই দরকার হলে enumerate:

python
stock = ["pen", "notebook"]
for position, item in enumerate(stock, start=1):
    print(position, item)
text
1 pen
2 notebook

start=1 না দিলে গণনা শূন্য থেকে শুরু হয়। মানুষকে দেখানোর তালিকায় সাধারণত এক থেকেই শুরু করতে হয়।


যে ফাঁদটা সবচেয়ে বেশি ভোগায়

চতুর্থ অধ্যায়ে দেখেছিলাম:

python
a = 3
b = a
b = b + 1
print(a, b)
text
3 4

b মান পেয়েছিল, a-এর দিকে তাকিয়ে ছিল না। এখন লিস্টে একই জিনিস করে দেখুন:

python
a = [1, 2, 3]
b = a
b.append(4)

print(a)
print(b)
text
[1, 2, 3, 4]
[1, 2, 3, 4]

b বদলানো হয়েছিল, কিন্তু a-ও বদলে গেছে।

কারণটা আগের নিয়মের বিরোধী নয়, বরং ঠিক সেটাই। b = a লাইনে b পেয়েছে a-তে যা ছিল তা-ই — আর a-তে ছিল লিস্টটির অবস্থান, লিস্টটির একটি কপি নয়। তাই দুটি নাম এখন একই লিস্টের দিকে দেখাচ্ছে। b.append(4) সেই একটিমাত্র লিস্টকেই বদলেছে, আর দুটি নাম দিয়েই সেই একই বদল দেখা যাচ্ছে।

সংখ্যায় এটা চোখে পড়ে না, কারণ b = b + 1 লাইনটি পুরোনো সংখ্যাটা বদলায় না — সে একটি নতুন সংখ্যা বানিয়ে b-তে বসায়। বদলাতে পারে না বলেই সংখ্যা নিরাপদ। লিস্ট বদলাতে পারে, তাই লিস্ট নয়।

আলাদা একটা কপি চাইলে চেয়ে নিতে হবে:

python
a = [1, 2, 3]
b = a.copy()
b.append(4)

print(a)
print(b)
text
[1, 2, 3]
[1, 2, 3, 4]
কখন সতর্ক হবেন। যখনই একটি লিস্ট অন্য নামে বসাচ্ছেন, বা একটি লিস্ট কোনো ফাংশনে পাঠাচ্ছেন (উনিশতম অধ্যায়), একবার জিজ্ঞেস করুন: আমি কি এটাকেই বদলাতে চাই, নাকি একটা কপি চাই? এই প্রশ্নটা যারা করে না, তারা দিনের পর দিন খুঁজে বেড়ায় «আমি তো এখানে কিছু বদলাইনি» জাতীয় বাগ।

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

shopping.py:

python
# A shopping list, built up and then summarised
items = []

items.append("pen")
items.append("notebook")
items.append("bag")
items.insert(0, "eraser")

print("List    :", items)
print("Count   :", len(items))
print("First   :", items[0])
print("Last    :", items[-1])
print("Has bag :", "bag" in items)

items.remove("pen")
dropped = items.pop()
print()
print("After removing 'pen' and popping the last one:")
print("List    :", items)
print("Dropped :", dropped)
print("Count   :", len(items))

print()
print("Numbered:")
for position, item in enumerate(items, start=1):
    print(f"  {position}. {item}")
text
List    : ['eraser', 'pen', 'notebook', 'bag']
Count   : 4
First   : eraser
Last    : bag
Has bag : True

After removing 'pen' and popping the last one:
List    : ['eraser', 'notebook']
Dropped : bag
Count   : 2

Numbered:
  1. eraser
  2. notebook

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

শুরুটা খালি লিস্ট দিয়ে। items = [] লাইনটি লুপের বাইরের সঞ্চায়কের মতোই — দশম অধ্যায়ে total = 0 যে কাজটা করত, এখানে [] সেটাই করছে। জমা করার আগে জমা করার জায়গাটা থাকতে হয়।

insert(0, "eraser") সবার আগে বসিয়েছে, তাই ছাপা তালিকায় eraser প্রথম — যোগ করার ক্রম আর তালিকার ক্রম এক নয়।

pop() বাদ দেওয়া জিনিসটা ফেরত দিয়েছে, তাই dropped-এ bag ধরা আছে। remove("pen") কিছু ফেরত দেয়নি, তাই বাদ দেওয়া pen আর কোথাও নেই।


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

IndexError: list index out of range লিস্টে যতগুলো মান আছে, তার বাইরের সূচক চাওয়া হয়েছে। তিনটি মানের লিস্টে বৈধ সূচক 0, 1, 2 — prices[3] নেই। শেষ মানটি চাইলে prices[-1] সবচেয়ে নিরাপদ, কারণ দৈর্ঘ্য জানার দরকার পড়ে না।

ValueError: list.remove(x): x not in list যে মানটি বাদ দিতে বলা হয়েছে সেটি লিস্টে নেই। আগে if x in stock: দিয়ে দেখে নিন, নয়তো চব্বিশতম অধ্যায়ের try ব্যবহার করুন।

AttributeError: 'NoneType' object has no attribute 'append' আগের কোনো লাইনে stock = stock.append(...) লেখা হয়েছে, তাই stock এখন None। ফলটি ধরে রাখবেন না — stock.append(...) যথেষ্ট।

একটা লিস্ট বদলেছি, অন্যটাও বদলে গেছে দুটি নাম একই লিস্টে লেগে আছে। b = a নয়, b = a.copy() লিখুন।

লুপের ভেতরে বাদ দিচ্ছি, কিছু বাদ পড়ে যাচ্ছে

python
numbers = [1, 2, 2, 3, 4]

for n in numbers:
    if n % 2 == 0:
        numbers.remove(n)

print(numbers)
text
[1, 2, 3]

একটি 2 টিকে গেছে। যে লিস্টের উপর দিয়ে হাঁটছেন সেটিকেই ছোট করলে পাইথনের গণনা আর লিস্টের অবস্থান মিলে না, তাই কিছু মান এড়িয়ে যায়। সমাধান: বাদ না দিয়ে নতুন একটি লিস্টে রাখুন যেগুলো রাখতে চান।

python
numbers = [1, 2, 2, 3, 4]
kept = []

for n in numbers:
    if n % 2 != 0:
        kept.append(n)

print(kept)
text
[1, 3]

list নামে ভেরিয়েবল বানিয়েছি, এখন list(...) কাজ করছে না list পাইথনের নিজের নাম। items, prices, names — অর্থপূর্ণ নাম দিন।