"age = 25" লিখলে Python-এর ভেতরে কী ঘটে — namespace, reference, object, refcount সব মিলিয়ে একটা animated memory-diagram দিয়ে বোঝা যাক। (ধরে নিচ্ছি CPython — Python-এর সবচেয়ে জনপ্রিয় implementation)
এই পুরো Lab-টা এই দুই লাইন কোডকে ঘিরেই। প্রতিটা ধাপে আমরা দেখব — নাম (name), reference (arrow), আর heap-এর object — এই তিনটা জিনিস আলাদা আলাদা কিন্তু একসাথে কীভাবে কাজ করে।
age = 25 লেখা হয় — Object তৈরি হওয়াPython প্রথমে ডান পাশ (RHS) evaluate করে: 25 একটা literal। CPython compiler এটাকে bytecode-এ কনভার্ট করে —
প্রথমে interpreter heap-এ একটা PyObject বানায় (type=int, value=25, refcount=1) — কিন্তু যদি 25 আগে থেকেই ক্যাশে থাকে, নতুন object বানাতে হয় না (নিচের note দেখো)। এরপর নাম age-কে সেই object-এর সাথে bind করা হয়।
25 এই range-এ থাকায় বাস্তবে প্রায়ই নতুন object বানানো লাগে না — আগে থেকে বানানো 25-কেই re-use করা হয়। এটা language spec না, CPython-এর নিজস্ব optimization।age নিজে কোনো memory location না। এটা module-এর namespace dict-এ একটা key, যার value হলো heap object-টার একটা reference (pointer)। CPython-এ module-level namespace literally একটা dict — globals() চালিয়ে দেখা যায়:
Function-এর ভেতরের local variable একটু আলাদা — performance-এর জন্য CPython সেগুলোকে dict না বানিয়ে সরাসরি frame-এর একটা array slot-এ রাখে (STORE_FAST / LOAD_FAST), locals() কল করলে তখন একটা dict বানিয়ে দেখানো হয় মাত্র।
CPython নিজের একটা private heap manage করে (pymalloc)। প্রতিটা object আসলে একটা C struct — শুরুতে থাকে একটা fixed header:
ob_refcnt হলো reference-counting garbage collector-এর হৃদয়। যখনই কোনো নাম বা container object-টাকে ধরে রাখে, refcount বাড়ে; যখন ছেড়ে দেয়, কমে। refcount শূন্যে নামলে object-এর memory সাথে সাথে ফেরত দেওয়া হয় (আর reference cycle-এর জন্য আলাদা একটা cyclic garbage collector আছে)।
print(age) — Python কীভাবে খুঁজে পায়Interpreter bytecode চালায়:
ধাপে ধাপে: (1) namespace dict-এ "age" key দিয়ে খোঁজা → (2) reference (pointer) পাওয়া → (3) সেই reference অনুসরণ করে heap-এর object-এ পৌঁছানো (dereference) → (4) object-এর type-এর __repr__/__str__ কল করে string বানানো → (5) console-এ দেখানো। "Value" নিজে কখনো namespace-এর ভেতরে বসে থাকে না — সবসময় খোঁজা হয়, তারপর দেখানো হয়।
id(age) আসলে কী রিটার্ন করেPython language spec শুধু এইটুকু guarantee করে: id() এমন একটা integer দেয় যেটা unique এবং object-এর পুরো lifetime জুড়ে constant থাকবে। এটা "মেমরি অ্যাড্রেস" — এই কথাটা spec-এ নেই!
id() object-টার আসল C pointer address-ই রিটার্ন করে (hex(id(x)) চালিয়ে দেখলে ঠিক ওই 0x7f9a... address পাওয়া যায়)। কিন্তু PyPy বা Jython-এর মতো implementation-এ id() memory address নাও হতে পারে — সেখানে এটা শুধু একটা counter/hash হতে পারে। তাই সবসময় বলা উচিত: "CPython-এ id() = memory address", "Python spec অনুযায়ী id() = unique constant identifier"।x = 25; y = x — একই Object-কে দুই নামy = x মানে x-এর value copy হচ্ছে না — y সরাসরি x যেই object-কে point করে, সেই একই object-কে point করা শুরু করে। ফলে একটাই 25 object, কিন্তু তাকে ধরে রাখা reference এখন দুইটা — refcount 2।
x = 50 — নতুন object, পুরনো y অক্ষতএটা rebinding, mutation না। x এখন নতুন একটা object 50-কে point করা শুরু করে। পুরনো 25 object-টা মোছা যায় না, কারণ y এখনও সেটাকে ধরে আছে — তার refcount শুধু 2 থেকে 1-এ নামে।
append() কেন আলাদাlist mutable — b.append(4) নতুন object বানায় না, একই list object-এর ভেতরেই পরিবর্তন করে। যেহেতু a আর b দুজনেই সেই একই object-কে point করছিল, দুজনের চোখেই পরিবর্তনটা দেখা যায়। এটা int-এর rebinding (ধাপ 07) থেকে সম্পূর্ণ আলাদা — সেখানে নতুন object তৈরি হয়েছিল, এখানে একই object পরিবর্তিত হচ্ছে।
এই বাক্যটা ইঙ্গিত দেয় যেন variable একটা container, আর value তার ভেতরে বসে আছে — C-এর মতো। কিন্তু Python-এ:
তাই বেশি accurate বাক্য: "একটা variable হলো namespace-এ একটা নাম, যেটা heap-এ থাকা একটা independent object-কে reference (bind) করে।"
variable-এর নিজস্ব fixed-size memory address আছে, value সরাসরি সেখানেই থাকে। y = x করলে পুরো 25 বাইট-বাই-বাইট copy হয়ে যায় — দুটো আলাদা address, দুটো আলাদা কপি।
variable মানেই একটা reference — value কখনোই variable-এর "ভেতরে" থাকে না, সবসময় আলাদা heap object-এ থাকে। y = x শুধু reference copy করে, value না।
লকার-রুম ভাবো। প্রতিটা locker (heap object) নিজেই একটা জিনিস — তার নিজস্ব একটা নাম্বার আছে (id/address), আর তার ভেতরে জিনিসপত্র (value) আছে। কিন্তু locker নিজে কোনো নাম জানে না।
তুমি একটা sticky-note নেমপ্লেট লেখো "age" আর সেটা কোনো একটা locker-এর গায়ে সাঁটিয়ে দাও (bind করো) — এখন সবাই জানে "age" মানে ওই locker। আরেকটা নেমপ্লেট "y"-ও চাইলে একই locker-এর গায়ে সাঁটানো যায় (y = x) — locker একটাই, নেমপ্লেট দুইটা। নেমপ্লেট সরিয়ে অন্য লকারে লাগালে (x = 50), পুরনো locker আর তার জিনিস অক্ষত থাকে, যদি অন্য কোনো নেমপ্লেট এখনও তাতে লাগানো থাকে। কোনো নেমপ্লেট না থাকলে সেই locker খালি করে অন্য কাউকে দিয়ে দেওয়া হয় (garbage collected)।
নিচের script-টা লাইন-বাই-লাইন চালাও — প্রতিটা assignment/append হওয়ার সাথে সাথে namespace + heap diagram নিজে থেকে আপডেট হবে, refcount আর garbage-collection-ও দেখা যাবে। সমর্থিত syntax: x = 25, x = "text", x = [1,2,3], y = x, x.append(4)।