সরাসরি পর্বে যান
পর্ব ২২ · ৫৫-এর মধ্যে · মডিউল ৪

module, package আর import — বড় প্রোগ্রামকে ফাইলে ভাগ করা

Modules, Packages & How Python's import Really Works

Taught in Bangla, this lesson splits a program into files: every .py file as a module, the forms of import, how import searches, runs once and caches, the __main__ guard, sys.path and name-shadowing traps, packages and __init__.py, absolute versus relative imports, python -m, circular imports and __all__.

এই পর্বে যা শিখবেন

  • প্রোগ্রাম বড় হলে কেন দায়িত্ব অনুযায়ী ফাইলে ভাগ করতে হয়, আর module যে আসলে একটি namespace
  • import-এর চারটি রূপ, আর import চালালে ভেতরে ধাপে ধাপে কী ঘটে (cache, খোঁজা, একবার চালানো, নাম বাঁধা)
  • __name__ আর "__main__" — একই ফাইলকে script আর library দুইভাবে ব্যবহার করা
  • sys.path, standard library ঢেকে ফেলার ফাঁদ, package আর __init__.py, absolute বনাম relative import, python -m
  • circular import চেনা ও সারানো, __all__, underscore-এর নিয়ম, dir()/help(), আর একটি গোছানো ছোট project-এর কাঠামো

১ · একটি ফাইল যখন আর কুলায় না — প্রতিটি .py ফাইলই একটি module

পর্ব ২১-এর মাঝপথের প্রজেক্টে আপনি একটি ফলাফলের টুল বানিয়েছেন: নম্বর যোগ করা, GPA হিসাব, মেধাতালিকা ছাপা, কমান্ড পড়া — সব একটি ফাইলে। দেড়শো লাইন পর্যন্ত এক ফাইল চলে যায়। কিন্তু কল্পনা করুন, টুলটি এবার পুরো স্কুল ব্যবহার করবে: ফলাফল ফাইলে সেভ হবে, শিক্ষকদের জন্য আলাদা রিপোর্ট লাগবে, পরে হয়তো একটি ওয়েবসাইটও। দুই হাজার লাইনের একটি ফাইলে একটি ফাংশন খুঁজে পাওয়াই তখন কাজ হয়ে দাঁড়ায়, আর দুজন একসাথে একই ফাইলে কাজ করলে একে অপরের পরিবর্তন মুছে ফেলার ভয় থাকে।

একটি garments factory-র কথা ভাবুন। কাটিং সেকশন কাপড় কাটে, সুইং সেকশন সেলাই করে, ফিনিশিং সেকশন ইস্ত্রি আর প্যাকিং করে। প্রতিটি সেকশনের নিজের লোক, নিজের যন্ত্র, নিজের খাতা। সুইং সেকশনের “রহিম” আর ফিনিশিং সেকশনের “রহিম” আলাদা মানুষ — কেউ গুলিয়ে ফেলে না, কারণ সবাই বলে “সুইংয়ের রহিম”। এক সেকশনের কাজ দরকার হলে অন্য সেকশন অনুরোধ পাঠায়, ভেতরে ঢুকে যন্ত্র ধরে না। বড় প্রোগ্রামও এভাবে সাজানো হয়: প্রতিটি ফাইল একটি সেকশন, যার একটি নির্দিষ্ট দায়িত্ব.

Python-এ এই “সেকশন”-এর নাম module. সংজ্ঞাটি অবাক করার মতো সরল: যেকোনো .py ফাইলই একটি module, আর ফাইলের নাম (.py বাদ দিয়ে) হলো module-এর নাম। grading.py নামে একটি ফাইল বানালেন, ব্যস — grading নামের module তৈরি। বিশেষ কোনো ঘোষণা, কোনো কিওয়ার্ড লাগে না।

পর্ব ২০-এ দেখেছিলেন, প্রতিটি নাম কোনো একটি namespace-এ — নামের খাতায় — লেখা থাকে, আর “global” মানে আসলে “এই module-এর global”. এবার সেই কথাটি পূর্ণ হলো: প্রতিটি module-এর নিজের একটি global namespace আছে. grading.py-তে total আর report.py-তে total দুটি আলাদা নাম, আলাদা খাতায়। বাইরে থেকে ব্যবহার করতে হলে বলতে হয় grading.total — ঠিক “সুইংয়ের রহিম”-এর মতো। এই কারণে দুটি ফাইলে একই নাম থাকলেও সংঘর্ষ হয় না।

আর এক ফাইল যখন অন্য ফাইলের নাম ব্যবহার করতে চায়, তখন লেখে import. পর্ব ২১-এর টুলকে দায়িত্ব অনুযায়ী ভাগ করলে এমন দেখাতে পারে:

result_tool/
├── grading.py     # grade_point(), gpa() — শুধু হিসাব, কোনো print নেই
├── storage.py     # load(), save() — ফাইলে রাখা আর পড়া (পর্ব ২৫–২৬)
└── cli.py         # কমান্ড পড়া, ঠিক ফাংশন ডাকা, ফল ছাপা
# cli.py — শুধু ব্যবহারকারীর সাথে কথা বলে
import storage
from grading import gpa

def main():
    students = storage.load("marks.json")
    for name, marks in students.items():
        print(f"{name}: GPA {gpa(marks):.2f}")

if __name__ == "__main__":
    main()

এখনই সব লাইন বোঝার দরকার নেই — if __name__ লাইনটির গল্প অংশ ৪-এ। শুধু ভাগটা দেখুন। grading.py জানে না নম্বর কোথা থেকে আসে; সে শুধু হিসাব করে। storage.py জানে না GPA কী; সে শুধু ফাইল পড়ে-লেখে। cli.py কিছু হিসাব করে না; সে শুধু দুজনকে জোড়া লাগায়। পর্ব ২১-এ এই নীতির নাম শিখেছিলেন — separation of concerns. Module হলো সেই নীতিকে ফাইলের স্তরে নিয়ে যাওয়া।

খুঁজে পাওয়া সহজ

GPA-র নিয়ম বদলাবে? সোজা grading.py. ফাইলের নামই বলে দেয় কোথায় কী আছে।

পুনর্ব্যবহার

পরে ওয়েবসাইট বানালে grading.py হুবহু কাজে লাগবে — কারণ এর ভেতরে কোনো input() বা print() নেই।

পরীক্ষা সহজ

শুধু হিসাবের module আলাদা থাকলে তার টেস্ট লেখা সহজ (পর্ব ৩৯) — কমান্ড টাইপ করার ঝামেলা নেই।

দলে কাজ

একজন storage.py-তে, আরেকজন cli.py-তে — একই ফাইলে হাত না দিয়েই দুজনের কাজ চলে।

আপনি এতদিন না জেনেই module ব্যবহার করেছেন। import math, import random — এগুলো Python-এর সাথে আসা standard library-র module, যাদের বেশিরভাগ আসলে কারও লেখা সাধারণ .py ফাইল, আপনার কম্পিউটারে Python-এর ফোল্ডারে রাখা। আর কয়েকটি module একটি ফোল্ডারে গুছিয়ে রাখলে তাকে বলে package — অংশ ৬-এ।

মূল ধারণা

তিনটি শব্দ, তিনটি স্তর: module = একটি .py ফাইল = নামের একটি খাতা; package = module-এর একটি ফোল্ডার; import = অন্য খাতার নাম নিজের কাছে আনা. Python-এর পুরো বিশাল ecosystem — NumPy থেকে Django — এই তিনটি সরল ধারণার ওপর দাঁড়িয়ে।

২ · import-এর চারটি রূপ — আর কোনটি এড়াবেন

একই module চারভাবে import করা যায়। পার্থক্য একটাই: আপনার ফাইলের namespace-এ কোন নাম বসে.

লেখাআপনার ফাইলে যে নাম বসেব্যবহারকখন
import mathmath (পুরো module)math.sqrt(16)সবচেয়ে পরিষ্কার; প্রথম পছন্দ
import math as mm (একই module, অন্য নাম)m.sqrt(16)লম্বা নাম বা প্রচলিত ডাকনাম: import numpy as np
from math import sqrtশুধু sqrtsqrt(16)দু-একটি নাম বারবার লাগলে
from math import *module-এর সব public নামsqrt(16), pi, …নিজের কোডে প্রায় কখনো নয়

প্রথম রূপে আপনার namespace-এ একটিই নতুন নাম আসে — math, যা পুরো module object-এর গায়ে লাগানো লেবেল। পর্ব ০৫ থেকে জানেন, Python-এ সবকিছু object; module-ও তাই, আর math.sqrt মানে “math object-এর sqrt নামের attribute”. প্রতিটি ব্যবহারে উৎস লেখা থাকে, তাই যে কেউ পড়ে বোঝে sqrt এসেছে math থেকে।

দ্বিতীয় রূপ as শুধু লেবেলের নাম বদলায়; module একটাই। Data science-এ import numpy as np, import pandas as pd এত প্রচলিত যে এগুলো প্রায় নিয়ম হয়ে গেছে (পর্ব ৪৬)। তবে নিজে থেকে অদ্ভুত ডাকনাম (import grading as g) বানাবেন না — পাঠকের জন্য একটি নতুন ধাঁধা তৈরি হয়।

তৃতীয় রূপ from math import sqrt module-টিকে আপনার namespace-এ আনে না, আনে শুধু sqrt নামটি — যা সেই একই ফাংশন object-এর দিকে নির্দেশ করে। একসাথে কয়েকটিও আনা যায়: from math import sqrt, pi, floor. কয়েকটি নাম বারবার ব্যবহার করলে এটি সুবিধাজনক। as এখানেও চলে: from datetime import datetime as dt.

from x import * — কেন এড়াবেন

চতুর্থ রূপকে বলে wildcard import (star import). এটি module-এর সব “public” নাম — underscore দিয়ে শুরু হয় না এমন সব নাম, অথবা module যদি __all__ তালিকা দেয় তবে শুধু সেগুলো — একবারে আপনার namespace-এ ঢেলে দেয়। দেখতে সুবিধাজনক, কিন্তু তিনটি গুরুতর সমস্যা:

  • উৎস হারিয়ে যায়: ফাইলের মাঝখানে floor(x) দেখে বোঝার উপায় নেই এটি কোথা থেকে এল — আপনার নিজের, নাকি কোনো import-এর? তিনটি star import থাকলে খুঁজতে হয় তিনটি module-এ।
  • নিঃশব্দে ঢেকে ফেলা: পর্ব ২০-এর shadowing এখানে লুকিয়ে ঘটে। from os import * লিখলে os module-এর open নামের ফাংশন built-in open-কে ঢেকে দেয় — আর তার আচরণ একেবারে আলাদা। দুটি module-এ একই নাম থাকলে পরেরটি আগেরটিকে মুছে দেয়, কোনো সতর্কতা ছাড়া।
  • টুল অন্ধ হয়ে যায়: Editor-এর auto-complete, linter আর পাঠক — কেউই নিশ্চিত জানে না কোন নাম আছে। PEP 8 এই কারণেই স্পষ্ট বলে, wildcard import এড়ানো উচিত।

নিয়ম: নিজের কোডে import * লিখবেন না। REPL-এ দ্রুত কিছু পরীক্ষা করতে চলে, আর package-এর __init__.py-তে ভেতরের নাম বাইরে দেখানোর একটি বিশেষ ব্যবহার আছে (অংশ ৯)। আরেকটি তথ্য: Python 3-এ ফাংশনের ভেতরে import * লেখা যায় না — SyntaxError; শুধু module-এর বাইরের স্তরে চলে।

একটি ছোট শৈলীর কথা। PEP 8 অনুযায়ী সব import ফাইলের একেবারে শুরুতে (module docstring-এর পরে) থাকে, তিনটি দলে, মাঝে একটি ফাঁকা লাইন: প্রথমে standard library (import sys), তারপর বাইরে থেকে install করা library (import requests), শেষে নিজের project-এর module (import grading). তাহলে ফাইল খুলেই বোঝা যায় এটি কার ওপর নির্ভর করে। ruff-এর মতো টুল (পর্ব ৪৪) এই সাজানো নিজেই করে দেয়।

৩ · import আসলে কী করে — খোঁজা, একবার চালানো, cache, নাম বাঁধা

অনেকে ভাবেন import বুঝি C-র #include-এর মতো — অন্য ফাইলের লেখা এখানে কপি করে বসানো। তা নয়। import একটি সাধারণ statement, যা চালানোর মুহূর্তে চারটি কাজ করে:

  1. Cache দেখা: Python একটি dict রাখে — sys.modules — যার চাবি module-এর নাম, মান module object. আগে দেখে, এই নামে module কি আগেই লোড হয়েছে? হলে সোজা ধাপ ৪-এ।
  2. খোঁজা: না থাকলে খোঁজে — কিছু module interpreter-এর ভেতরেই তৈরি (যেমন sys), বাকিদের জন্য sys.path তালিকার ফোল্ডারগুলোতে একে একে grading.py বা grading/ ফোল্ডার খোঁজে (অংশ ৫)। কোথাও না পেলে ModuleNotFoundError.
  3. তৈরি, cache-এ রাখা আর চালানো: পেলে একটি খালি module object তৈরি করে, সঙ্গে সঙ্গে sys.modules-এ রাখে, তারপর ফাইলের কোড উপর থেকে নিচে একবার চালায়। এই চালানোর সময় যত def, যত assignment — সব module-এর namespace ভরে তোলে। (পর্ব ০৫-এর bytecode পরের বারের জন্য __pycache__ ফোল্ডারে .pyc হিসেবে জমা থাকে, যাতে পরেরবার compile করতে না হয়।)
  4. নাম বাঁধা: শেষে আপনার ফাইলের namespace-এ একটি নাম সেই module object-এর গায়ে লাগায় — import grading হলে grading, from grading import gpa হলে শুধু gpa.

এখান থেকে দুটি গুরুত্বপূর্ণ ফল আসে। এক: module-এর বাইরের স্তরের কোড (যেকোনো ফাংশনের বাইরে লেখা কোড) চলে মাত্র একবার, প্রথম import-এর সময়। দুই: একই module যতবার, যত ফাইল থেকেই import করুন, সবাই একই object পায় — তাই এক জায়গায় module-এর কোনো মান বদলালে সবাই সেই বদল দেখে। ধাপে ধাপে দেখুন:

1# config.py2print("config লোড হচ্ছে")3VAT = 0.154# report.py5import config6# main.py7import config8import report9import config10print(config.VAT, report.config is config)
cache আর নামগুলো
sys.modules
main-এর নাম
report-এর নাম
  1. প্রথম import — cache ফাঁকা। main.py-র লাইন ৭-এ import config. Python sys.modules-এ "config" চাবি খুঁজল — নেই। তাই sys.path-এ খুঁজে config.py পেল।
  2. ফাইলটি একবার চলল। নতুন module object তৈরি হয়ে আগে sys.modules["config"]-এ বসল, তারপর config.py-র কোড উপর থেকে চলল: ছাপা হলো config লোড হচ্ছে, আর VAT = 0.15 module-এর namespace-এ বসল। শেষে main-এর namespace-এ config নামটি সেই object-এ লাগল।
  3. অন্য ফাইল থেকে import — cache থেকে। লাইন ৮-এ import report: report নতুন, তাই report.py চলতে শুরু করল। তার লাইন ৫-এ import config — এবার sys.modules-এ config আছে! তাই config.py আর চলল না, কিছু ছাপাও হলো না; report-এর namespace-এ শুধু config নামটি সেই একই object-এ লাগল।
  4. আবার import — কিছুই চলে না। লাইন ৯-এ আবার import config. Cache-এ আছে, তাই খোঁজা নেই, চালানো নেই — নাম বাঁধার কাজটুকুই। একে প্রায় খরচহীন বলা যায়। এই কারণেই একই module দশটি ফাইলে import করলেও সমস্যা নেই।
  5. প্রমাণ। config.VAT ছাপল 0.15, আর report.config is config ছাপল True — দুটি ফাইলের দুটি লেবেল একটিই module object-এর দিকে। পুরো প্রোগ্রামে config লোড হচ্ছে ছাপা হয়েছে মাত্র একবার।

এর একটি ব্যবহারিক ফল: একটি module পুরো প্রোগ্রামে একবারই তৈরি হয়, তাই module নিজেই একধরনের singleton. সেটিং, database connection বা লগার রাখার জন্য অনেকে এই সুবিধা নেন। তবে উল্টো দিকও আছে: module-এর বাইরের স্তরে ভারী কাজ (বড় ফাইল পড়া, নেটওয়ার্কে যাওয়া) রাখলে প্রতিটি প্রোগ্রাম শুরু হতেই সেটি হবে, এমনকি যে কাজে দরকার নেই তাতেও। বাইরের স্তরে রাখুন শুধু হালকা জিনিস — import, ধ্রুবক, ফাংশন আর class-এর সংজ্ঞা।

এবার প্রথম runnable প্রোগ্রাম। এখানে শুধু standard library ব্যবহার করে import-এর রূপগুলো, sys.modules-এর cache আর dir() দেখা হচ্ছে। চালানোর আগে প্রথম লাইনের আউটপুট অনুমান করুন:

import sys
import math
import math as m
from math import sqrt

print(__name__)                         # এই ফাইলের নিজের নাম
print(m is math, sqrt is math.sqrt)     # একই object, ভিন্ন লেবেল
print(sqrt(2025), type(math).__name__)
print(sys.modules["math"] is math)      # cache-এ রাখা সেই module
print([n for n in dir(math) if n.startswith("is")])

import random
random.seed(22)                         # নির্দিষ্ট seed → প্রতিবার একই ফল
print([random.randint(1, 6) for _ in range(3)])
print(random.__name__, "random" in sys.modules)

আউটপুট সাত লাইনের। প্রথম লাইন __main__ — যে ফাইল সরাসরি চালানো হয়, তার __name__ এটাই (অংশ ৪-এ বিস্তারিত)। দ্বিতীয় লাইন True True: import math দুবার লেখা হলেও module একটাই, m তার আরেকটি লেবেল, আর sqrt সেই module-এর ভেতরের একই ফাংশন। তৃতীয় লাইন 45.0 module — module নিজেও একটি object, যার type module. চতুর্থ লাইন True প্রমাণ করে sys.modules["math"]-এ সেই একই object রাখা। পঞ্চম লাইনে dir(math) থেকে “is” দিয়ে শুরু নামগুলো: ['isclose', 'isfinite', 'isinf', 'isnan', 'isqrt'] — dir() সাজানো তালিকা দেয়, তাই ক্রম নির্দিষ্ট। ষষ্ঠ লাইন [2, 2, 1] — নির্দিষ্ট seed থাকায় এই Python version-এ প্রতিবার একই তিনটি “ছক্কার দান”। শেষ লাইন random True: import করা module-এর __name__ তার নিজের নাম, "__main__" নয়।

৪ · if __name__ == "__main__": — একই ফাইল, script আর library দুই রূপে

অংশ ৩ থেকে একটি সমস্যা সরাসরি বেরিয়ে আসে। ধরুন আপনি bangla_utils.py নামে একটি সহায়ক ফাইল লিখলেন — টাকার অঙ্ক বাংলা অঙ্কে সুন্দর করে লেখার ফাংশন। ফাংশনটি ঠিক কাজ করছে কি না দেখতে ফাইলের শেষে কয়েকটি print দিলেন। এবার cli.py থেকে import bangla_utils করলেই সেই পরীক্ষার print-গুলোও চলে যাবে — কারণ import মানে পুরো ফাইল একবার চালানো। আপনার রিপোর্টের মাঝখানে হঠাৎ পরীক্ষার আউটপুট!

সমাধান হলো একটি বিশেষ নাম: __name__. প্রতিটি module-এর namespace-এ Python নিজে এই ভ্যারিয়েবলটি বসিয়ে দেয়। এর মান নির্ভর করে ফাইলটি কীভাবে চালানো হলো তার ওপর:

সরাসরি চালালে: "__main__"

python bangla_utils.py লিখে চালালে এই ফাইলটিই প্রোগ্রামের শুরু — একে বলে script. তখন তার __name__ হয় "__main__".

import করলে: নিজের নাম

অন্য ফাইল import bangla_utils করলে এটি একটি library হিসেবে লোড হয়, আর __name__ হয় "bangla_utils".

তাই ফাইলের শেষে এই শর্ত — যাকে বলে main guard — লিখলে তার ভেতরের কোড শুধু সরাসরি চালানোর সময় চলে, import-এর সময় নয়:

if __name__ == "__main__":
“আমাকে কি সরাসরি চালানো হয়েছে? তাহলেই এই অংশ চালাও।”

একজন রাঁধুনির কথা ভাবুন, যিনি নিজের হোটেলে রান্না করেন, আবার বিয়েবাড়িতে ভাড়াও খাটেন। নিজের হোটেলে থাকলে তিনি দোকান খোলেন, ক্যাশ সামলান, তারপর রান্না করেন। কিন্তু বিয়েবাড়িতে ডাকা হলে তিনি শুধু রান্না করেন — নিজের দোকান খোলার কাজ সেখানে করেন না। __name__ হলো তাঁর প্রশ্ন: “আমি কি নিজের দোকানে, নাকি কেউ ডেকে এনেছে?”

প্রচলিত ধরন হলো সব “চালানোর” কাজ একটি main() ফাংশনে রাখা, আর guard-এর ভেতরে শুধু সেটিকে ডাকা — অংশ ১-এর cli.py-তে ঠিক এটাই দেখেছেন। এতে দুটি সুবিধা: main()-এর ভেতরের ভ্যারিয়েবল local থাকে (module-এর global namespace নোংরা হয় না, পর্ব ২০), আর পরীক্ষার কোড চাইলে main() নিজেও ডাকতে পারে।

এবার দ্বিতীয় runnable প্রোগ্রাম, যেটি একটু অন্যরকম: অনলাইনে এখানে একটিমাত্র ফাইল চালানো যায়, তাই প্রোগ্রামটি নিজেই কাজের ফোল্ডারে (working directory) bangla_utils.py নামে একটি ছোট module লিখে ফেলে, তারপর সেটি import করে। দুবার import হচ্ছে — খেয়াল করুন “লোড হচ্ছে” কতবার ছাপা হয়, আর guard-এর ভেতরের লাইনটি ছাপা হয় কি না:

import importlib, sys
from pathlib import Path

Path("bangla_utils.py").write_text('''\
print("bangla_utils লোড হচ্ছে...")
_DIGITS = str.maketrans("0123456789", "০১২৩৪৫৬৭৮৯")

def taka(amount):
    return "৳" + f"{amount:,}".translate(_DIGITS)

if __name__ == "__main__":
    print("সরাসরি চালানো হলো")
''', encoding="utf-8")
importlib.invalidate_caches()

import bangla_utils              # প্রথমবার: ফাইলটি চলে
import bangla_utils              # দ্বিতীয়বার: cache থেকে
print(bangla_utils.taka(1250000))
print(bangla_utils.__name__, "bangla_utils" in sys.modules)
print([n for n in dir(bangla_utils) if not n.startswith("__")])

প্রথম অংশ pathlib দিয়ে একটি ফাইল লেখে (ফাইল নিয়ে কাজ পর্ব ২৫-এ); importlib.invalidate_caches() Python-কে বলে “ফোল্ডারে নতুন ফাইল এসেছে, আগের খোঁজার ফল ভুলে যাও” — চলমান প্রোগ্রামে নতুন module ফাইল বানালে এটি লাগে। আউটপুট চার লাইনের। প্রথম লাইন bangla_utils লোড হচ্ছে... — মাত্র একবার, যদিও import দুবার; দ্বিতীয়টি cache থেকে এসেছে। সরাসরি চালানো হলো ছাপা হয়নি, কারণ import করা ফাইলের __name__ এখানে "bangla_utils". দ্বিতীয় লাইন ৳১,২৫০,০০০ — module-এর ফাংশন bangla_utils.taka নামে ডাকা হলো। (এখানে কমা হাজারে-হাজারে; বাংলাদেশে প্রচলিত লাখ-কোটির ভাগ — ১২,৫০,০০০ — বানানো একটি ভালো অনুশীলন।) তৃতীয় লাইন bangla_utils True. শেষ লাইন ['_DIGITS', 'taka'] — underscore দিয়ে শুরু নামটিও বাইরে থেকে দেখা যায়; underscore শুধু একটি সংকেত, তালা নয় (অংশ ৯)।

guard ছাড়া script-এর আরেক বিপদ: Windows ও macOS-এ multiprocessing (পর্ব ৪১) নতুন process চালু করার সময় মূল ফাইলটি আবার import করে। guard না থাকলে প্রতিটি নতুন process আবার নতুন process বানাতে চায় — প্রোগ্রাম error দেয় বা আটকে যায়। তাই যে ফাইল সরাসরি চালানোর জন্য, তার শেষে সবসময় guard রাখার অভ্যাস করুন।

৫ · Python module কোথায় খোঁজে — sys.path আর random.py-র ফাঁদ

অংশ ৩-এর ধাপ ২-এ বলেছিলাম, Python sys.path-এর ফোল্ডারগুলোতে খোঁজে। sys.path একটি সাধারণ list, যাতে ফোল্ডারের ঠিকানা থাকে, আর খোঁজা চলে উপর থেকে নিচে; প্রথম মিলেই থামে — ঠিক পর্ব ২০-এর LEGB-র মতো। সাধারণত এর ক্রম:

  1. যে script চালাচ্ছেন, তার নিজের ফোল্ডার (REPL বা python -c-তে বর্তমান ফোল্ডার)। এই কারণেই একই ফোল্ডারের grading.py কোনো সেটআপ ছাড়াই import হয়।
  2. PYTHONPATH নামের environment variable-এ লেখা ফোল্ডারগুলো, যদি থাকে।
  3. Python-এর নিজের standard library-র ফোল্ডার।
  4. site-packages — pip install দিয়ে বসানো বাইরের library যেখানে থাকে (পর্ব ২৩)।

নিজের কম্পিউটারে দেখতে চাইলে REPL-এ লিখুন import sys; print(sys.path). কোনো module ঠিক কোন ফাইল থেকে এল জানতে print(random.__file__) — এটি ডিবাগিংয়ের একটি খুব কাজের কৌশল।

প্রথম ধাপটিই নতুনদের সবচেয়ে পরিচিত একটি বাগের জন্ম দেয়। একজন শিক্ষার্থী random নিয়ে অনুশীলন করতে নিজের ফাইলের নাম দিলেন random.py. পরে একই ফোল্ডারে একটি লুডু খেলা লিখলেন, যেটি import random করে। কী হয়, ধাপে ধাপে দেখুন:

  1. ফোল্ডারpractice/দুটি ফাইল
  2. sys.path[0]practice/প্রথমেই এখানে
  3. ভুল ফাইল চলেrandom.pyআপনার অনুশীলন
  4. ErrorAttributeErrorrandint নেই
  5. সমাধানdice_practice.pyনাম বদল
practice/ফোল্ডারের ভেতরে
practice/
├── random.py     # শিক্ষার্থীর অনুশীলন: print("আমার random অনুশীলন")
└── ludo.py       # import random; print(random.randint(1, 6))
sys.pathউপর থেকে নিচে খোঁজা
[0] .../practice            ← random.py এখানেই পাওয়া গেল!
[1] .../Python314/Lib       ← আসল random.py এখানে, কিন্তু এ পর্যন্ত আসেই না
[2] .../site-packages
Terminalভুল module চলল
$ python ludo.py
আমার random অনুশীলন
Terminaltraceback-এর শেষ লাইন (Python 3.13+)
AttributeError: module 'random' has no attribute 'randint'
(consider renaming '.../practice/random.py' since it has the same
name as the standard library module named 'random' and prevents
importing that standard library module)
practice/নাম বদলের পরে
practice/
├── dice_practice.py
└── ludo.py

$ python ludo.py
4
  1. দুটি ফাইল, একটি ফোল্ডার। practice/ ফোল্ডারে শিক্ষার্থীর অনুশীলনের ফাইল random.py আর নতুন খেলা ludo.py. দেখতে নিরীহ — ফাইলের নাম তো যা খুশি দেওয়া যায়, তাই না?
  2. খোঁজা শুরু হয় script-এর ফোল্ডারে। python ludo.py চালালে sys.path[0] হয় practice/. import random প্রথমে সেখানেই খোঁজে — আর random.py পেয়ে যায়! খোঁজা থামে। Python-এর আসল random.py বসে আছে তালিকার নিচে, কিন্তু খোঁজা সে পর্যন্ত পৌঁছায়ই না।
  3. ভুল module চলল। Python শিক্ষার্থীর random.py চালাল — তার print ছাপল আমার random অনুশীলন. এটিই প্রথম সূত্র: import করার সময় অপ্রত্যাশিত কিছু ছাপা হলে বুঝবেন ভুল ফাইল লোড হচ্ছে।
  4. Error. random.randint খুঁজতে গিয়ে দেখা গেল এই “random” module-এ randint বলে কিছু নেই — AttributeError. Python 3.13 থেকে বার্তার সাথে একটি পরামর্শও আসে: ফাইলটির নাম বদলান, কারণ এটি standard library-র module-এর নামের সাথে মেলে। পুরোনো version-এ এই পরামর্শ ছিল না, তাই বাগটি আরও রহস্যময় লাগত।
  5. সমাধান — নাম বদল। অনুশীলনের ফাইলের নাম দিন dice_practice.py. এবার import random practice/-এ কিছু পায় না, এগিয়ে গিয়ে আসল standard library-র module পায়। পুরোনো __pycache__/random.*.pyc থেকে গেলেও সমস্যা নেই — মূল .py না থাকলে Python সেটি ব্যবহার করে না; তবু ফোল্ডারটি মুছে দিলে পরিষ্কার থাকে।

একই ফাঁদ পাতে json.py, csv.py, email.py, test.py, string.py, turtle.py — এমনকি numpy.py বা pandas.py (তখন install করা library ঢাকা পড়ে)। নিয়ম: নিজের ফাইলের নাম কোনো পরিচিত module-এর নামে রাখবেন না. নাম রাখার আগে REPL-এ import নাম লিখে দেখুন — চললে বুঝবেন নামটি আগে থেকেই কারও। (sys-এর মতো কিছু module interpreter-এর ভেতরেই তৈরি, তাদের sys.path-এর আগেই খুঁজে পাওয়া যায়, তাই তাদের ঢাকা যায় না — কিন্তু কোনটি এমন, তা মনে রাখার চেয়ে নাম আলাদা রাখাই সহজ।)

sys.path হাত দিয়ে বদলানো: import কাজ না করলে ইন্টারনেটে (আর AI-এর কোডে) প্রায়ই দেখবেন sys.path.append("../") বা sys.path.insert(0, ...). এটি সাময়িকভাবে কাজ করে, কিন্তু প্রোগ্রাম কোন ফোল্ডার থেকে চালানো হলো তার ওপর নির্ভর করে, অন্য কম্পিউটারে ভাঙে, আর আসল সমস্যা — project-এর ভুল কাঠামো — লুকিয়ে রাখে। সঠিক সমাধান: project-কে package হিসেবে সাজানো আর python -m দিয়ে চালানো (অংশ ৭), অথবা পর্ব ২৩-এর মতো project install করা।

৬ · Package — module-এর ফোল্ডার আর __init__.py

Module যখন অনেক হয়ে যায়, তখন সম্পর্কিতগুলোকে একটি ফোল্ডারে রাখা স্বাভাবিক। একটি অনলাইন দোকানের কোড ভাবুন — দাম, স্টক আর কমান্ড-লাইন, তিনটি আলাদা দায়িত্ব:

shop_project/
├── main.py
└── shop/                 ← package
    ├── __init__.py       ← “এই ফোল্ডারটি একটি package”
    ├── pricing.py        ← shop.pricing
    ├── inventory.py      ← shop.inventory
    └── cli.py            ← shop.cli

shop ফোল্ডারটি একটি package, আর এর ভেতরের প্রতিটি ফাইল একটি module, যার পুরো নাম ডট দিয়ে লেখা: shop.pricing, shop.inventory. ডট-ওয়ালা নাম ফোল্ডারের পথেরই প্রতিফলন — shop/pricing.py মানে shop.pricing. Package-এর ভেতরে আরেকটি package (sub-package)-ও রাখা যায়, যেমন shop/payments/bkash.py হলে shop.payments.bkash.

__init__.py ফাইলটি থাকলে ফোল্ডারটি হয় একটি regular package. ফাইলটি খালিও হতে পারে — অধিকাংশ সময় খালিই থাকে। কিন্তু খালি না হলে, এর কোড চলে package প্রথমবার import হওয়ার সময়, আর এখানে সংজ্ঞায়িত নামগুলোই হয় package object-এর নাম। import shop.pricing লিখলে আসলে কী কী ঘটে, ধাপে ধাপে দেখুন:

1# shop/__init__.py2print("shop package লোড হলো")3# shop/pricing.py4VAT = 0.155def with_vat(p): return round(p * (1 + VAT))6# main.py7import shop.pricing8print(shop.pricing.with_vat(200))
cache, খোঁজা আর নাম
sys.modules
কোথায় খোঁজা
main-এর নাম
shop-এর attribute
  1. নাম ভাঙা। import shop.pricing দেখে Python নামটিকে ভাগ করে: আগে shop, তারপর shop.pricing. Child module-এর আগে সবসময় parent package লোড হয়। sys.modules-এ "shop" নেই, তাই খুঁজতে হবে।
  2. Package খোঁজা। sys.path-এর প্রথম ফোল্ডারে (main.py-র ফোল্ডার) shop/ নামে একটি ফোল্ডার পাওয়া গেল, যার ভেতরে __init__.py আছে — অর্থাৎ এটি একটি regular package.
  3. __init__.py চলল। shop নামে module object তৈরি হয়ে sys.modules-এ বসল, তার __path__-এ লেখা হলো ফোল্ডারের ঠিকানা, তারপর __init__.py-র কোড চলল — ছাপা হলো shop package লোড হলো. Package আসলে একটি module, শুধু তার একটি __path__ আছে।
  4. Module খোঁজা ও চালানো। এবার pricing. গুরুত্বপূর্ণ: এটি পুরো sys.path-এ নয়, শুধু shop.__path__-এর ভেতরে খোঁজা হয়। পাওয়া গেল pricing.py; তৈরি হলো, sys.modules["shop.pricing"]-এ বসল, তারপর চলল — VAT আর with_vat তৈরি হলো।
  5. নাম বাঁধা। দুটি কাজ: shop module-এর গায়ে pricing নামের attribute বসল। আর main-এর namespace-এ বাঁধা হলো শুধু shop নামটি — pricing নয়! তাই ব্যবহার করতে লিখতে হয় পুরো পথ। শুধু pricing নামে চাইলে লিখতেন from shop import pricing.
  6. ব্যবহার। লাইন ৮: shop → তার attribute pricing → তার with_vat(200). হিসাব 200 × 1.15 = 230. এখন অন্য কোথাও আবার import shop.pricing লিখলে দুটিই cache থেকে আসবে — __init__.py-র print আর ছাপা হবে না।

__init__.py-তে কী রাখবেন — হালকা রাখুন

যেহেতু package-এর যেকোনো module import করলেই আগে __init__.py চলে, এখানে ভারী কিছু রাখলে সবাই সেই খরচ দেয়। ভালো অভ্যাস:

  • খালি রাখা — ছোট project-এ এটাই যথেষ্ট।
  • প্রয়োজনীয় কয়েকটি নাম সামনে আনা: from .pricing import with_vat লিখলে ব্যবহারকারী from shop import with_vat লিখতে পারেন, ভেতরের ফাইলের কাঠামো না জেনেই। বড় library (যেমন requests) এভাবেই একটি পরিষ্কার “সামনের দরজা” দেয়।
  • একটি version নম্বর বা package-এর docstring.
  • যা রাখবেন না: ফাইল পড়া, নেটওয়ার্ক, ডেটাবেস সংযোগ, বা package-এর সব module একসাথে import করা।

Namespace package — __init__.py ছাড়া ফোল্ডার

Python 3.3 থেকে __init__.py ছাড়া ফোল্ডারও import করা যায়। একে বলে namespace package (PEP 420). এর আসল উদ্দেশ্য বড় প্রতিষ্ঠানের জন্য: একই package নামের অংশ আলাদা আলাদা ফোল্ডারে, এমনকি আলাদাভাবে install করা ভিন্ন ভিন্ন distribution-এ থাকতে পারে, আর Python সবগুলোকে মিলিয়ে একটি package বানায়। নতুনদের জন্য নিয়ম সহজ: নিজের package-এ সবসময় __init__.py রাখুন. ভুলে গেলে কোড প্রায়ই চলবে, কিন্তু কিছু টুল (যেমন কিছু test runner বা package বানানোর টুল) ফোল্ডারটিকে package হিসেবে চিনতে ভুল করতে পারে, আর ভুল জায়গার একই নামের ফোল্ডার নিঃশব্দে package হয়ে যেতে পারে।

৭ · Absolute বনাম relative import — আর python -m

shop/cli.py থেকে pricing ব্যবহার করতে চাইলে দুইভাবে লেখা যায়:

# absolute import — package-এর শুরু থেকে পুরো পথ
from shop.pricing import with_vat
import shop.inventory

# relative import — "আমার নিজের package থেকে"
from .pricing import with_vat       # একই package-এর pricing
from . import inventory             # একই package-এর inventory
from ..utils import taka            # এক ধাপ উপরের package-এর utils

Absolute import সবসময় top-level package-এর নাম থেকে শুরু করে — যেকোনো ফাইল থেকে একই রকম দেখায়, তাই পড়তে সবচেয়ে পরিষ্কার। PEP 8-ও সাধারণত absolute import পছন্দ করে। Relative import-এ সামনের ডটগুলো ফোল্ডারের মতো কাজ করে: একটি ডট মানে “এই ফাইলের নিজের package”, দুটি ডট মানে “এক ধাপ উপরের package”. সুবিধা: package-এর নাম বদলালে ভেতরের import বদলাতে হয় না। বড় package-এর ভেতরে এটি বেশ প্রচলিত। দুটি নিয়ম মনে রাখুন: relative import শুধু from ... import ... রূপে চলে (import .pricing লেখা যায় না), আর শুধু package-এর ভেতরের ফাইলে।

যখন relative import ভাঙে

এবার সবচেয়ে বেশি প্রশ্ন আসা error-টি। shop/cli.py-তে from .pricing import with_vat লেখা, আর আপনি সেটি সরাসরি চালালেন:

python shop/cli.py
ImportError: attempted relative import with no known parent package

কারণ অংশ ৪-এ লুকিয়ে আছে। সরাসরি চালানো ফাইলের নাম হয়ে যায় "__main__" — "shop.cli" নয়। Python-এর চোখে এটি এখন কোনো package-এর অংশ নয়, একটি একা ফাইল। “আমার package” বলতে কোনটি, সে জানে না — তাই .-র কোনো অর্থ নেই। (সাথে আরেকটি সমস্যা: sys.path[0] হয়ে যায় shop/ ফোল্ডার নিজে, তার উপরের ফোল্ডার নয়, তাই absolute import shop-ও খুঁজে পাওয়া যায় না।)

সমাধান হলো ফাইলের পথ না দিয়ে module-এর নাম দিয়ে চালানো, project-এর মূল ফোল্ডার থেকে:

cd shop_project
python -m shop.cli

-m (run as module) মানে “এই নামের module খুঁজে script হিসেবে চালাও”. এখন Python জানে ফাইলটি shop package-এর অংশ, তাই relative import কাজ করে; আর sys.path[0] হয় বর্তমান ফোল্ডার (shop_project/), তাই absolute import-ও কাজ করে। ফাইলের __name__ তবু "__main__" থাকে, তাই main guard-ও ঠিকঠাক চলে। আপনি আগেই এর ব্যবহার দেখেছেন: python -m pip, python -m venv (পর্ব ২৩), python -m http.server — সবই standard library বা install করা module-কে নাম ধরে চালানো।

মূল ধারণা

Package-এর ভেতরের ফাইল ফাইলের পথ দিয়ে নয়, module-এর নাম দিয়ে চালান: python shop/cli.py নয়, python -m shop.cli. এই একটি অভ্যাসেই relative import-এর error আর sys.path নিয়ে কাটাছেঁড়ার প্রায় সব সমস্যা দূর হয়।

৮ · Circular import — দুটি module যখন একে অপরকে চায়

দোকানের কোড বড় হচ্ছে। pricing.py-তে ছাড়ের হিসাবের জন্য জানা দরকার কোন পণ্য স্টকে বেশি আছে, তাই সে inventory থেকে stock আনে। আবার inventory.py-তে স্টকের মোট মূল্য বের করতে দাম লাগে, তাই সে pricing থেকে price আনে। দুটি ফাইল একে অপরকে import করছে — একে বলে circular import. চালালে:

ImportError: cannot import name 'price' from partially initialized module 'shop.pricing'
(most likely due to a circular import) (.../shop/pricing.py)

“partially initialized module” — অর্ধেক তৈরি হওয়া module. কথাটির মানে বুঝতে অংশ ৩-এর একটি খুঁটিনাটি মনে করুন: module object চালানোর আগেই sys.modules-এ বসে। টাইমলাইনটি দেখুন:

1# shop/pricing.py2from shop.inventory import stock3def price(item): ...4# shop/inventory.py5from shop.pricing import price6def stock(item): ...7# main.py8import shop.pricing
কোন module কতটা তৈরি
shop.pricing
shop.inventory
  1. pricing লোড শুরু। main.py shop.pricing import করল। module object তৈরি হয়ে sys.modules-এ বসল (এখনো খালি), আর তার প্রথম লাইন চলতে শুরু করল: from shop.inventory import stock. price-এর def এখনো অনেক দূরে, লাইন ৩-এ।
  2. inventory লোড শুরু। shop.inventory cache-এ নেই, তাই সেটি তৈরি হয়ে চলতে শুরু করল। pricing এখন লাইন ২-এ থেমে অপেক্ষা করছে — inventory শেষ না হলে সে এগোবে না।
  3. অর্ধেক তৈরি module থেকে নাম চাওয়া। inventory-র প্রথম লাইন: from shop.pricing import price. Python sys.modules-এ shop.pricing পেল (ধাপ ১-এ বসানো হয়েছিল), তাই নতুন করে লোড করল না। কিন্তু সেই module এখনো লাইন ২-এ আটকে — price তৈরিই হয়নি। দুজনেই একে অপরের জন্য অপেক্ষা করছে, যেন সরু গলিতে মুখোমুখি দুটি রিকশা।
  4. Error. price নাম না পেয়ে Python ImportError দিল, আর বার্তায় নিজেই অনুমান লিখে দিল: “most likely due to a circular import”. (Package ছাড়া একই ফোল্ডারের দুটি সাধারণ ফাইলে, Python 3.13+-এ বার্তার শেষাংশ একটু অন্যরকম হতে পারে, কিন্তু প্রথম অংশ cannot import name একই থাকে।)
  5. সমাধান। সবচেয়ে ভালো উপায় কাঠামো বদলানো: দুজনের যা দরকার (যেমন পণ্যের তালিকা ও দাম) তা তৃতীয় একটি module-এ, ধরুন catalog.py-তে, সরিয়ে নিন; এখন pricing আর inventory দুজনেই catalog-কে import করে, কেউ কাউকে নয়। তাহলে চক্র ভাঙে আর প্রোগ্রাম চলে।

Circular import সারানোর উপায়গুলো, পছন্দের ক্রমে:

  1. কাঠামো বদলান: circular import প্রায় সবসময় একটি নকশার সংকেত — দুটি module-এর দায়িত্ব পরিষ্কারভাবে ভাগ হয়নি। ভাগ করা অংশ তৃতীয় module-এ নিন, অথবা দুটিকে একটিতে মিলিয়ে দিন। লক্ষ্য: নির্ভরতা একদিকে বয়ে যাক — cli → pricing → catalog, কখনো উল্টো দিকে নয়।
  2. ফাংশনের ভেতরে import: def stock_value(): from shop.pricing import price; ... — import চলে ফাংশন ডাকার সময়, যখন দুটি module-ই পুরো তৈরি। দ্রুত সমাধান, কিন্তু নির্ভরতা লুকিয়ে রাখে; comment দিয়ে কারণ লিখে রাখুন।
  3. module import, নাম নয়: from shop.pricing import price-এর বদলে import shop.pricing, আর ব্যবহারের সময় shop.pricing.price(...). তখন import-এর মুহূর্তে price নামটি লাগে না, লাগে ডাকার মুহূর্তে। অনেক ক্ষেত্রে এতেই চলে, তবে নির্ভর করে কোন কোড কখন চলে তার ওপর।

৯ · module-এর সামনের দরজা — __all__, underscore, dir()/help() আর গোছানো project

Underscore — “এটি ভেতরের জিনিস”

Python-এ private কিওয়ার্ড নেই। বদলে আছে একটি সর্বজনস্বীকৃত রীতি: নাম যদি underscore দিয়ে শুরু হয় (_DIGITS, _parse_row), তবে সেটি module-এর ভেতরের ব্যবহারের জন্য — বাইরের কেউ এর ওপর নির্ভর করবেন না, পরের version-এ এটি বদলে বা মুছে যেতে পারে। একে বলে private name রীতি। অংশ ৪-এর প্রোগ্রামে দেখেছেন, এমন নামও বাইরে থেকে দেখা যায় — Python আটকায় না। শুধু দুটি জায়গায় এর প্রভাব: from x import * underscore-ওয়ালা নাম আনে না, আর অনেক editor auto-complete-এ এগুলো লুকিয়ে রাখে। Python-এর দর্শন এখানে: “আমরা সবাই প্রাপ্তবয়স্ক” — সংকেত দেওয়া হলো, মানা আপনার দায়িত্ব।

__all__ — public নামের তালিকা

module-এর একেবারে উপরে __all__ = ["taka", "to_bangla_digits"] লিখলে আপনি স্পষ্ট ঘোষণা করেন, এই module-এর public API কোনগুলো। __all__-এর প্রযুক্তিগত প্রভাব একটাই — from bangla_utils import * লিখলে শুধু এই নামগুলো আসবে। কিন্তু এর আসল মূল্য দলিল হিসেবে: পাঠক আর টুল এক নজরে বোঝেন কোনটি ব্যবহারের জন্য। Package-এর __init__.py-তে from .pricing import * লেখার সাথে pricing-এ __all__ থাকলে, এটিই সেই নিয়ন্ত্রিত বিশেষ ব্যবহার, যার কথা অংশ ২-এ বলেছিলাম।

dir() আর help() — অচেনা module ঘুরে দেখা

নতুন module হাতে পেলে প্রথমে REPL খুলুন। dir(math) সব নামের সাজানো তালিকা দেয়; help(math.isqrt) ফাংশনের docstring দেখায় — কী নেয়, কী দেয়; help(math) পুরো module-এর বিবরণ। কোনো কিছু ইনস্টল ছাড়াই, ইন্টারনেট ছাড়াই, এটি আপনার হাতের কাছের ডকুমেন্টেশন। চলমান প্রোগ্রাম থেকে নিজের সম্পর্কে এভাবে তথ্য বের করাকে বলে introspection. আর আপনি নিজে module লিখলে ফাইলের প্রথম লাইনে একটি docstring দিন — help(bangla_utils) তখন সেটিই দেখাবে।

importlib.reload — শুধু REPL-এ

REPL-এ একটি module import করে পরীক্ষা করছেন, এর মধ্যে ফাইলে কিছু বদলালেন। আবার import bangla_utils লিখলে কিছুই হবে না — cache! তখন import importlib; importlib.reload(bangla_utils) ফাইলটি আবার চালায়। তবে importlib-এর এই ফাংশনের সীমা আছে: পুরোনো object-গুলো (যেমন আগে from bangla_utils import taka দিয়ে আনা taka) পুরোনোই থেকে যায়। তাই এটি শুধু REPL বা Jupyter-এ দ্রুত পরীক্ষার জন্য; আসল প্রোগ্রামে reload ব্যবহার করবেন না — প্রোগ্রাম নতুন করে চালান।

একটি গোছানো ছোট project

সব মিলিয়ে, একটি ছোট দোকানের project এমন দেখাতে পারে:

shop_project/
├── README.md
├── shop/
│   ├── __init__.py      # খালি, বা কয়েকটি নাম সামনে আনা
│   ├── catalog.py       # পণ্যের তালিকা — কারও ওপর নির্ভর করে না
│   ├── pricing.py       # দাম, VAT, ছাড় — catalog ব্যবহার করে
│   ├── inventory.py     # স্টক — catalog ব্যবহার করে
│   └── cli.py           # main() + main guard — সবাইকে জোড়ে
└── tests/
    └── test_pricing.py  # পর্ব ৩৯

চালানো:  cd shop_project  →  python -m shop.cli

লক্ষ করুন নির্ভরতার দিক: cli সবাইকে ব্যবহার করে, pricing আর inventory শুধু catalog-কে, আর catalog কাউকেই না। কোনো চক্র নেই, তাই circular import-এর সুযোগ নেই। input() আর print() শুধু cli.py-তে, তাই বাকি সব module টেস্ট করা সহজ, আর পরে ওয়েব ইন্টারফেস (পর্ব ৪৮) বানালে cli.py-র জায়গায় আরেকটি ফাইল বসালেই চলবে। পেশাদার project-এ প্রায়ই package-টিকে একটি src/ ফোল্ডারের ভেতরে রাখা হয় (src layout) আর একটি pyproject.toml ফাইল যোগ করা হয় — সেগুলোর কারণ ও নিয়ম পর্ব ২৩-এ।

C/C++ জানলে

C-তে #include হলো preprocessor-এর লেখা কপি-পেস্ট: header-এর পুরো লেখা প্রতিটি .c ফাইলে বসে, তাই একই header দুবার ঢোকা আটকাতে include guard লাগে, আর নাম আলাদা রাখতে static বা নামের আগে prefix. Python-এর import কোনো লেখা কপি করে না — module একবার চলে, sys.modules-এ থাকে, এরপর শুধু নাম বাঁধা হয়; অর্থাৎ include guard ভাষার ভেতরেই তৈরি, আর প্রতিটি ফাইল নিজে থেকেই একটি namespace. C++-এ namespace আর ফাইল আলাদা ধারণা (একটি namespace বহু ফাইলে ছড়াতে পারে); Python-এ ফাইল = module = namespace. বিস্তারিত তুলনার জন্য দেখুন C-তে modular programming আর C++-এর namespace ও header.

পরীক্ষা ও ইন্টারভিউতে
  • module আর package-এর পার্থক্য? — module একটি .py ফাইল; package হলো module-এর একটি ফোল্ডার (সাধারণত __init__.py-সহ), যার নিজেরও একটি __path__ আছে।
  • if __name__ == "__main__": কেন লেখা হয়? — ফাইল সরাসরি চালালে __name__ হয় "__main__", import করলে module-এর নাম; তাই ভেতরের কোড শুধু script হিসেবে চালালেই চলে।
  • একই module দুবার import করলে কোড দুবার চলে? — না; প্রথমবার চালিয়ে sys.modules-এ রাখা হয়, পরেরবার সেখান থেকে আসে।
  • from x import * কেন খারাপ? — কোন নাম কোথা থেকে এল বোঝা যায় না, নিঃশব্দে নাম ঢাকা পড়ে, টুল কাজ করে না; PEP 8 এড়াতে বলে।
  • circular import কীভাবে সারাবেন? — ভাগ করা অংশ তৃতীয় module-এ সরিয়ে কাঠামো বদলানো; দরকারে import ফাংশনের ভেতরে নেওয়া।

১০ · AI-এর যুগে — AI-এর লেখা import আর project কাঠামো যাচাই

AI একটি ফাইলের ছোট প্রোগ্রাম চমৎকার লেখে। কিন্তু “একটি project বানিয়ে দাও” বললেই দুর্বলতা দেখা দেয়: AI এক এক বার্তায় এক একটি ফাইল লেখে, পুরো কাঠামো একসাথে মাথায় রাখে না, আর প্রশিক্ষণের কোডে থাকা পুরোনো অভ্যাস নকল করে। ফলে এমন কোড আসে যা AI-এর উদাহরণে চলে, কিন্তু আপনার ফোল্ডারে চালালে ভাঙে।

AI প্রায়ই যা করেসমস্যাকী চাইবেন
from utils import *, from tkinter import *নামের উৎস হারায়, নিঃশব্দে ঢেকে ফেলেস্পষ্ট নাম বা import x
sys.path.append("..") দিয়ে import “ঠিক” করাচালানোর ফোল্ডার বদলালেই ভাঙে; আসল কাঠামোর ভুল লুকায়package কাঠামো + python -m
দুটি module একে অপরকে import করেcircular import — শুরুতেই ImportErrorনির্ভরতা একদিকে; ভাগ করা অংশ আলাদা module-এ
script-এর শেষে main guard নেই; বাইরের স্তরে সব কোডimport করলেই পুরো প্রোগ্রাম চলে; টেস্ট অসম্ভবmain() + if __name__ == "__main__":
ফাইলের নাম random.py, json.py, test.pystandard library ঢাকা পড়েবর্ণনামূলক, অনন্য নাম
package-এর ফাইলে relative import, কিন্তু চালানোর নির্দেশ python shop/cli.py“no known parent package”চালানোর সঠিক কমান্ড লিখে দিতে বলা

AI-কে project বানাতে বলার সময় কাঠামো আর শর্ত আগেই দিন। নিচের কাঠামোটি কপি করে নিজের কাজ বসান:

আমি Python 3.12+ ব্যবহার করি। একটি ছোট দোকানের হিসাবের project বানিয়ে দাও, এই কাঠামোয়: shop_project/shop/__init__.py, catalog.py (পণ্যের তালিকা), pricing.py (দাম, ১৫% VAT), inventory.py (স্টক), cli.py (কমান্ড পড়া ও ছাপা)। শর্ত: ১. কোথাও "import *" লিখবে না; প্রতিটি নাম স্পষ্টভাবে import করো। ২. sys.path কোথাও বদলাবে না। ৩. নির্ভরতা শুধু একদিকে: cli → pricing/inventory → catalog. কোনো circular import থাকবে না। প্রতিটি ফাইলের শুরুতে comment-এ লেখো সে কোন module-এর ওপর নির্ভর করে। ৪. input() আর print() শুধু cli.py-তে; cli.py-তে main() ফাংশন আর if __name__ == "__main__": guard থাকবে। ৫. কোনো ফাইলের নাম standard library-র module-এর নামের সাথে মিলবে না। ৬. __init__.py হালকা রাখো; module-এর বাইরের স্তরে কোনো ফাইল পড়া বা ভারী কাজ নয়। ৭. শেষে project-এর মূল ফোল্ডার থেকে চালানোর সঠিক কমান্ড লেখো (python -m ব্যবহার করে), আর ফোল্ডারের গাছ-চিত্র দেখাও।

AI-এর দেওয়া project পেলে — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলো আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।

প্র ০১Python-এ module মানে শুধু একটি ফাইল। অথচ Java-তে package ঘোষণা করতে হয়, আর C-তে আলাদা header ফাইল লিখতে হয়। Python এত সরল পথ কেন নিল, আর এর দাম কী?

তিনটি ভাষা তিনটি আলাদা প্রশ্নের উত্তর দিয়েছে। C তৈরি হয়েছিল ১৯৭০-এর দশকে, অল্প মেমরির কম্পিউটারে, যেখানে compiler একবারে একটি ফাইল দেখে। অন্য ফাইলের ফাংশন ডাকতে compiler-কে অন্তত তার “ঘোষণা” জানতে হয় — নাম, parameter-এর type, return type. তাই ঘোষণাগুলো রাখা হলো header ফাইলে, আর preprocessor সেই লেখা প্রতিটি ফাইলে কপি করে বসায়। এটি কার্যকর, কিন্তু ঝামেলার: একই কথা দুই জায়গায় লিখতে হয় (header আর .c), দুটি মিল না থাকলে অদ্ভুত বাগ হয়, include guard লাগে, আর বড় project-এ একই header হাজারবার parse হওয়ায় compile ধীর হয়। C++ এই সমস্যার সমাধানে বহু বছর পরে, C++20-এ, “modules” যোগ করেছে।

Java (১৯৯৫) এক ধাপ এগিয়ে: compile করা .class ফাইলেই type-এর তথ্য থাকে, তাই header লাগে না। কিন্তু Java ফোল্ডার কাঠামো আর package com.example.shop; ঘোষণা দুটোই চায়, আর দুটি মিলতে হয়। নামকরণের রীতি (উল্টো domain নাম) বিশ্বজুড়ে অনন্য নাম নিশ্চিত করতে।

Python dynamic ভাষা — চালানোর আগে compiler-কে কোনো type জানতে হয় না। নাম খোঁজা হয় চালানোর সময়, attribute হিসেবে (math.sqrt মানে module object-এর একটি attribute). ফলে “ঘোষণা” আলাদা করে লেখার কোনো প্রয়োজনই নেই; ফাইলটি চালালেই তার namespace তৈরি হয়ে যায়। Guido van Rossum Python-এর module ব্যবস্থায় Modula-3-এর মতো ভাষা থেকে অনুপ্রেরণা নিয়েছিলেন, কিন্তু সবচেয়ে সরল রূপটি বেছে নেন: ফাইল = module. ফোল্ডার = package (আর পরে, PEP 420-এর পরে, __init__.py-ও ঐচ্ছিক)।

দাম কী? প্রথমত, ভুল ধরা পড়ে দেরিতে: from grading import gpaa-র বানান ভুল ধরা পড়ে চালানোর সময়, compile-এ নয় (এখানে type checker আর linter সাহায্য করে, পর্ব ৩৭, ৪৪)। দ্বিতীয়ত, import মানে কোড চালানো — তাই import-এর পার্শ্বফল, ধীর শুরু আর circular import-এর মতো সমস্যা তৈরি হয়, যা C-র header-এ হয় না। তৃতীয়ত, ফাইলের নামই module-এর নাম, তাই random.py-র ফাঁদ। তবু শিক্ষার্থী থেকে বিজ্ঞানী — সবার জন্য “একটি ফাইল লিখলেই module” সরলতা Python-এর জনপ্রিয়তার একটি বড় কারণ।

মূল উপলব্ধি: module ব্যবস্থার নকশা ভাষার মূল চরিত্র থেকে আসে — static, compile-নির্ভর ভাষার ঘোষণা আগে লাগে; dynamic Python চালানোর সময় নাম খোঁজে, তাই একটি ফাইলই যথেষ্ট, আর তার দাম হলো চালানোর সময় ধরা পড়া ভুল।

প্র ০২Python-এর import ব্যবস্থা একসময় C-তে লেখা একটি জটিল কোড ছিল; এখন এটি নিজেই প্রায় পুরোটা Python-এ লেখা — importlib. এই পরিবর্তন কীভাবে এল, আর এতে কী লাভ?

Python-এর প্রথম দিকে import-এর পুরো যুক্তি — ফাইল খোঁজা, .pyc পরীক্ষা, package সামলানো — ছিল CPython-এর C কোডের ভেতরে (import.c নামের একটি বড় ফাইলে)। কেউ যদি অন্যভাবে module লোড করতে চাইতেন — zip ফাইল থেকে, ডেটাবেস থেকে, ইন্টারনেট থেকে — তবে একমাত্র উপায় ছিল built-in __import__ ফাংশনটিকে বদলে দেওয়া, যা ভঙ্গুর আর বিপজ্জনক।

প্রথম বড় পদক্ষেপ PEP 302 (Just van Rossum ও Paul Moore, Python 2.3, ২০০৩ সালের মুক্তি)। এটি import-কে দুটি ভূমিকায় ভাগ করে: finder — “এই নামের module কোথায় আছে, আমি খুঁজে দিতে পারি?” — আর loader — “পাওয়া module-টি আমি লোড করে চালাতে পারি”. sys.meta_path আর sys.path_hooks-এ নিজের finder যোগ করে যে কেউ নতুন উৎস থেকে import করাতে পারলেন। একই version-এ zip ফাইল থেকে import (zipimport) এসেছিল এই hook ব্যবস্থা দিয়েই।

দ্বিতীয় ধাপের নায়ক Brett Cannon, একজন CPython core developer, যিনি বহু বছর ধরে import-কে বিশুদ্ধ Python-এ নতুন করে লেখার কাজ করেন। তাঁর importlib প্রথম standard library-তে আসে Python 3.1-এ, আর Python 3.3-এ এটি আসল import ব্যবস্থা হয়ে ওঠে: import statement এখন ভেতরে importlib-এর কোডই চালায়। দ্রুত শুরুর জন্য এই Python কোড “frozen” রূপে interpreter-এর ভেতরে বসানো থাকে — তাই import-এর জন্য আগে import লাগে, এই ডিম-আগে-না-মুরগি-আগে সমস্যা এড়ানো যায়।

তৃতীয় ধাপ PEP 451 (Eric Snow, Python 3.4): ModuleSpec. finder এখন সরাসরি loader ফেরত না দিয়ে একটি “spec” ফেরত দেয় — module-এর নাম, কোথায় পাওয়া গেল, কোন loader, package কি না — সব তথ্য এক জায়গায়। module.__spec__-এ আপনি নিজেই দেখতে পারেন।

লাভ কী? এক, পড়া যায়: import-এর আচরণ নিয়ে সন্দেহ হলে যে কেউ importlib/_bootstrap.py খুলে Python কোড পড়তে পারেন। দুই, অন্য implementation (PyPy ইত্যাদি) একই কোড ব্যবহার করতে পারে, তাই আচরণ সব জায়গায় এক রকম। তিন, বাড়ানো সহজ: pytest টেস্ট ফাইল import করার সময় assert-কে বদলে দেয় নিজের import hook দিয়ে; Jupyter, PyInstaller, editable install — সবাই এই hook ব্যবস্থা ব্যবহার করে।

মূল উপলব্ধি: ভালো নকশা একটি জটিল কাজকে স্পষ্ট ভূমিকায় ভাঙে — finder, loader, spec. যখন একটি ভাষার মূল যন্ত্রাংশ নিজেই সেই ভাষায় পড়া যায় ও বাড়ানো যায়, তখন পুরো ecosystem তার ওপর নতুন টুল বানাতে পারে।

প্র ০৩Python 3.3-এ PEP 420 এনে __init__.py ছাড়া “namespace package” চালু করা হলো। কোন সমস্যার সমাধানে এটি এল, আর তবু কেন নিজের package-এ __init__.py রাখতে বলা হয়?

সমস্যাটি বড় প্রতিষ্ঠান আর বড় project-এর। ধরুন একটি কোম্পানির নাম “acme”, আর তাদের অনেকগুলো আলাদা library: acme.payments, acme.reports, acme.sms. প্রতিটি আলাদা দল বানায়, আলাদাভাবে pip install হয়, আলাদা version. কিন্তু ব্যবহারকারী চান সবগুলো একটি নামের নিচে — import acme.payments. Regular package-এ এটি কঠিন, কারণ acme/ ফোল্ডার একটিই হতে পারে, আর তার __init__.py কার? দুটি distribution একই acme/__init__.py install করতে চাইলে সংঘর্ষ।

PEP 420-এর আগে এর জন্য কৌশল ছিল: প্রতিটি distribution-এর acme/__init__.py-তে বিশেষ লাইন লেখা — pkgutil.extend_path বা setuptools-এর pkg_resources.declare_namespace — যা __path__-এ অন্য ফোল্ডারগুলো জুড়ে দিত। এগুলো ভঙ্গুর ছিল, ধীর ছিল, আর দুটি পদ্ধতি একসাথে মেলালে গোলমাল বাধত। Zope ও Plone-এর মতো বড় project এই সমস্যায় বহু বছর ভুগেছে।

PEP 420 (Eric V. Smith-এর লেখা, Python 3.3) ভাষার ভেতরেই সমাধান দিল: __init__.py ছাড়া একটি ফোল্ডার পেলে Python সেটি মনে রাখে, আর sys.path-এর বাকি সব জায়গায় একই নামের ফোল্ডার খুঁজে সবগুলোকে মিলিয়ে একটি namespace package বানায়, যার __path__-এ সব ফোল্ডার থাকে। কোনো বিশেষ কোড লাগে না।

তবু কেন নিজের package-এ __init__.py? কয়েকটি কারণ। এক, স্পষ্টতা: __init__.py দেখলেই বোঝা যায় “এটি একটি package, ইচ্ছা করে”. দুই, দুর্ঘটনা এড়ানো: namespace package হওয়ার শর্ত এত ঢিলা যে sys.path-এর কোনো ফোল্ডারে একই নামের সাধারণ ফোল্ডার (ধরুন docs/ বা data/) থাকলে সেটিও import হয়ে যায়, আর ভুল বার্তা দেয়। তিন, খোঁজার খরচ: namespace package-এর জন্য Python-কে sys.path-এর সব জায়গা দেখতে হয়, regular package প্রথম মিলেই থামে। চার, টুলের সমর্থন: কিছু test runner, coverage বা packaging টুলের পুরোনো version namespace package ঠিকমতো চেনে না। PEP 420 নিজেও বলে, এটি মূলত একাধিক distribution-এ ছড়ানো package-এর জন্য।

মূল উপলব্ধি: namespace package একটি বিশেষ সমস্যার — একটি নাম, অনেক distribution — বিশেষ সমাধান। সাধারণ কাজে স্পষ্ট __init__.py দুর্ঘটনা কমায়; “স্পষ্ট ভালো অস্পষ্টের চেয়ে” — Zen of Python-এর এই কথা এখানেও খাটে।

প্র ০৪Zen of Python-এর শেষ লাইন: “Namespaces are one honking great idea — let's do more of those!” কেন namespace-কে Python-এর এত বড় ধারণা বলা হয়, আর ভাষার কোথায় কোথায় এটি দেখা যায়?

Zen of Python (PEP 20) হলো Python-এর নকশার ১৯টি ছোট সূত্র, যা Tim Peters ১৯৯৯ সালে Python-এর mailing list-এ লিখেছিলেন। (তিনি বলেছিলেন সূত্র ২০টি, কিন্তু শেষটি Guido-র জন্য খালি রেখেছেন।) Python-এ import this লিখলে এগুলো ছাপা হয় — ভাষার ভেতরে লুকানো একটি মজার উপহার। শেষ সূত্রটিই আমাদের প্রশ্ন।

Namespace কেন এত বড়? কারণ বড় সফটওয়্যারের সবচেয়ে পুরোনো সমস্যা হলো নামের সংঘর্ষ. দশজন প্রোগ্রামার, হাজার ফাংশন — load, save, parse, total নাম বারবার দরকার হবে। C-তে সব ফাংশনের নাম একটি বিশাল global জায়গায় থাকে, তাই library-গুলো নামের আগে prefix দেয় (sqlite3_open, png_read) — হাতে বানানো namespace. Namespace থাকলে প্রতিটি নাম একটি “পরিবারের নাম” পায়: json.load আর pickle.load পাশাপাশি নির্ভয়ে থাকে, কারণ পুরো নামটি আলাদা।

Python-এ namespace সব জায়গায়, এই কোর্সেই দেখেছেন: প্রতিটি ফাংশন ডাকের local namespace আর LEGB (পর্ব ২০); প্রতিটি module-এর global namespace (এই পর্ব); package-এর ভেতরে ডট-ওয়ালা নাম; built-in namespace; আর সামনে প্রতিটি class ও object-এর নিজের namespace (পর্ব ২৯) — obj.attr আসলে একটি namespace থেকে নাম খোঁজা। ভেতরে অনেকগুলোই সাধারণ dict: globals() একটি dict, module-এর __dict__ একটি dict, object-এর __dict__-ও। একটি ধারণা দিয়ে ভাষার অনেক অংশ ব্যাখ্যা করা যায় — এটিই এর “honking great” হওয়ার কারণ।

এই ধারণা পাঠযোগ্যতাও আনে। random.choice(items) পড়লেই বোঝা যায় ফাংশনটি কোথাকার; “Explicit is better than implicit” — Zen-এর আরেকটি সূত্র — এর সাথে মেলে। আর from x import * ঠিক উল্টো কাজ করে: দুটি namespace-কে একটিতে গুলিয়ে দেয়। তাই Zen-এর শেষ লাইন আর PEP 8-এর star import নিষেধ একই দর্শনের দুই দিক।

“let's do more of those” — আরও বেশি করি — অংশটিরও প্রভাব দেখা যায়: Python 3-এ comprehension-এর নিজের scope হলো (Python 2-তে list comprehension-এর ভ্যারিয়েবল বাইরে “চুঁইয়ে” পড়ত), nonlocal এল, namespace package এল।

মূল উপলব্ধি: namespace হলো জটিলতা ভাগ করার যন্ত্র — প্রতিটি নাম একটি নির্দিষ্ট প্রেক্ষাপটে অর্থ পায়। Python এই একটি ধারণাকে ফাংশন, module, package, class আর object — সব স্তরে একইভাবে ব্যবহার করে, আর তাই ভাষাটি বড় হলেও সুসংগত থাকে।

প্র ০৫from x import * যদি এত খারাপই হয়, Python ভাষায় রাখল কেন? কোথায় এর সঠিক ব্যবহার আছে, আর __all__ কীভাবে ক্ষতি কমায়?

PEP 8-এর ভাষা স্পষ্ট: wildcard import এড়ানো উচিত, কারণ এতে বোঝা যায় না কোন নাম namespace-এ আছে, যা পাঠক আর অনেক স্বয়ংক্রিয় টুল দুজনকেই বিভ্রান্ত করে। তবু PEP 8 নিজেই একটি ব্যতিক্রমের কথা বলে: একটি ভেতরের module-এর নাম public API হিসেবে আবার প্রকাশ করা — যেমন package-এর __init__.py-তে from .core import *, যখন core-এ __all__ দিয়ে public নাম ঠিক করা আছে।

সমস্যার গভীরে যাই। প্রথম সমস্যা নিঃশব্দ ঢাকা: দুটি star import, দুটিতেই open বা compile বা sum — পরেরটি আগেরটিকে মুছে দেয়। আর library-র নতুন version-এ একটি নতুন নাম যোগ হলে আপনার কোডের একটি নাম হঠাৎ ঢাকা পড়তে পারে — কোড একটুও না বদলে প্রোগ্রাম ভাঙে। এটি সবচেয়ে বিপজ্জনক ধরনের বাগ, কারণ বাগটি আপনার কোডে নেই। দ্বিতীয় সমস্যা পাঠযোগ্যতা: NameError ধরা, “এই নাম কোথায় সংজ্ঞায়িত” খোঁজা — editor-কেও অনুমান করতে হয়। তৃতীয়ত, অপ্রয়োজনীয় নির্ভরতা: module-টি নিজে যা import করেছে, তাও (যদি __all__ না থাকে আর নামটি underscore দিয়ে শুরু না হয়) আপনার namespace-এ চলে আসে।

তাহলে রাখা হলো কেন? Python-এর শুরুর দিন থেকে এটি আছে, আর কয়েকটি জায়গায় সত্যিই কাজের: REPL-এ দ্রুত পরীক্ষা (from math import * লিখে ক্যালকুলেটরের মতো ব্যবহার), public API আবার প্রকাশ (উপরের ব্যতিক্রম), আর ঐতিহাসিকভাবে কিছু library — যেমন tkinter-এর পুরোনো টিউটোরিয়ালে from tkinter import * খুব দেখা যেত। এখন সরানো মানে লাখো পুরোনো script ভাঙা। তবে Python 3 একটি সীমা বসিয়েছে: ফাংশনের ভেতরে import * লেখা SyntaxError, কারণ compiler-কে compile-এর সময়েই জানতে হয় কোন নাম local (পর্ব ২০), আর star import-এ সেটি জানা অসম্ভব।

__all__ কীভাবে ক্ষতি কমায়? module লেখক তালিকা দেন — “শুধু এগুলোই public”. তখন star import শুধু সেই নামগুলো আনে, module-এর ভেতরে import করা os বা sys বা সহায়ক ফাংশন আসে না। আর তালিকাটি দলিলের কাজ করে: ডকুমেন্টেশন টুল আর type checker-ও একে public API হিসেবে ধরে। কিন্তু __all__ ব্যবহারকারীর দিকের সমস্যা — “কোন নাম কোথা থেকে” — দূর করে না।

মূল উপলব্ধি: নিজের কোডে স্পষ্ট import লিখুন — খরচ কয়েকটি অক্ষর, লাভ স্পষ্টতা আর নিরাপত্তা। star import রাখুন REPL আর সযত্নে নিয়ন্ত্রিত __init__.py-র জন্য, যেখানে __all__ সীমা টেনে দেয়।

অনুশীলন

বেশিরভাগ কাজে নতুন কোড লিখতে হবে না — অনুমান করা, পড়া আর মিলিয়ে নেওয়াই আসল কাজ।

  1. কতবার ছাপা হবে? একই ফোল্ডারে তিনটি ফাইল। tools.py-র প্রথম লাইন print("tools"). a.py-তে import tools আর print("a"). main.py-তে import tools, import a, import tools, শেষে print(__name__). python main.py চালালে আউটপুট খাতায় লিখুন।

    আউটপুট তিন লাইন: tools, a, __main__. প্রথম import tools-এ tools.py চলে ও cache-এ যায়। import a-তে a.py চলে; তার ভেতরের import tools cache থেকে আসে, তাই “tools” আবার ছাপা হয় না; তারপর “a”. তৃতীয় import tools-ও cache থেকে। শেষে main.py সরাসরি চালানো, তাই তার __name__ হলো __main__.

  2. কোন import চলবে? কাঠামো: app/ ফোল্ডারে run.py আর store/ package (__init__.py, db.py, api.py). api.py-তে লেখা from .db import connect. (ক) app/ থেকে python store/api.py চালালে কী হবে? (খ) python -m store.api চালালে? (গ) run.py-তে from store.api import handler লিখে python run.py চালালে?

    (ক) ImportError: attempted relative import with no known parent package — সরাসরি চালানো ফাইলের নাম __main__, তাই সে জানে না কোন package-এর অংশ। (খ) চলবে — -m দিয়ে চালালে Python জানে ফাইলটি store package-এর, তাই .db মানে store.db. (গ) চলবে — run.py-র ফোল্ডার app/ হলো sys.path[0], সেখানে store package পাওয়া যায়, আর api লোড হয় store.api নামে, তাই তার relative import-ও ঠিক কাজ করে।

  3. বাগ খুঁজুন: একজন শিক্ষার্থী JSON শিখতে নিজের ফাইলের নাম দিলেন json.py, আর তার ভেতরেই লিখলেন import json ও print(json.dumps({"a": 1})). চালালে error আসে। কেন? কী error আসতে পারে, আর সমাধান কী?

    import json প্রথমে script-এর নিজের ফোল্ডারে খোঁজে, আর সেখানে পায় শিক্ষার্থীর json.py — অর্থাৎ ফাইলটি নিজেকেই import করে! (সরাসরি চালানো ফাইলটি __main__ নামে আছে, তাই json নামে সেটি আরেকবার লোড হয়।) সেই দ্বিতীয় লোডের সময় json module-এ তখনো dumps তৈরি হয়নি, তাই AttributeError: module 'json' has no attribute 'dumps' ধরনের error আসে; Python 3.13+ বার্তায় ফাইলের নাম বদলানোর পরামর্শও দেয়। সমাধান: ফাইলের নাম দিন json_practice.py; পুরোনো __pycache__ ফোল্ডার থাকলে মুছে দিলে আরও পরিষ্কার থাকে।

  4. একটি module বানান (এবং AI-কে যাচাই করুন): bangla_utils.py লিখুন, যাতে থাকবে to_bangla_digits(n) আর taka(amount) — শেষেরটি বাংলাদেশি ধরনে কমা দেবে (১২,৫০,০০০)। ফাইলের শেষে main guard-এর ভেতরে কয়েকটি assert দিন। তারপর একই ফোল্ডারের shop.py থেকে import করে ব্যবহার করুন, আর দেখুন import-এর সময় assert-এর কোনো আউটপুট আসে কি না। একই কাজ কোনো AI-কে দিয়ে করান, আর চেকলিস্ট ধরে তার কোড মিলিয়ে দেখুন।

    একটি সম্ভাব্য সমাধান। শেষ তিন অঙ্ক এক দলে, তারপর বাঁ দিকে দুই-দুই অঙ্কের দল:

    """টাকা আর বাংলা অঙ্কের ছোট সহায়ক।"""
    __all__ = ["to_bangla_digits", "taka"]
    _DIGITS = str.maketrans("0123456789", "০১২৩৪৫৬৭৮৯")
    
    def to_bangla_digits(n):
        return str(n).translate(_DIGITS)
    
    def taka(amount):
        s = str(amount)
        head, tail = s[:-3], s[-3:]
        groups = []
        while len(head) > 2:
            groups.insert(0, head[-2:])
            head = head[:-2]
        if head:
            groups.insert(0, head)
        return "৳" + to_bangla_digits(",".join(groups + [tail]))
    
    if __name__ == "__main__":
        assert taka(1250000) == "৳১২,৫০,০০০"
        assert taka(500) == "৳৫০০"
        print("সব পরীক্ষা পাস")

    python bangla_utils.py চালালে “সব পরীক্ষা পাস” ছাপা হবে; shop.py থেকে import করলে কিছুই ছাপা হবে না — guard-এর কাজ এটাই। (ঋণাত্মক সংখ্যা বা দশমিক সামলানো বাড়তি চ্যালেঞ্জ।) AI-এর কোডে দেখুন: guard আছে কি না, import * বা sys.path আছে কি না, আর ফাইলের নাম কোনো standard module-এর সাথে মেলে কি না।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমপ্রথমবার import grading চালালে Python যে ক্রমে কাজ করে, সেই ক্রমে সাজান (▲▼ চেপে সরান)

  1. 🗂️ sys.modules-এ “grading” আগে থেকে আছে কি না দেখা
  2. 🔎 sys.path-এর ফোল্ডারগুলোতে grading.py খোঁজা
  3. 📦 নতুন module object তৈরি করে sys.modules-এ রাখা
  4. ▶️ grading.py-র কোড উপর থেকে নিচে একবার চালানো
  5. 🏷️ আমার ফাইলে grading নামটি সেই module-এ বাঁধা

একটি Python প্রোগ্রামে import config তিনটি আলাদা ফাইলে লেখা, আর config.py-র প্রথম লাইনে একটি print আছে। প্রোগ্রাম চালালে সেই print কতবার চলবে?

  • তিনবার, প্রতিটি import-এ একবার
  • একবারও না, কারণ import করা ফাইলের print চলে না
  • একবার, প্রথম import-এর সময়
  • প্রতিটি ফাইলের জন্য একবার করে, তবে একই ফাইলে দুবার নয়

Python-এ python report.py লিখে একটি ফাইল সরাসরি চালালে সেই ফাইলের ভেতরে __name__-এর মান কী?

  • "report"
  • "__main__"
  • "report.py"
  • None

একটি ফোল্ডারে random.py নামে নিজের একটি ফাইল আছে। একই ফোল্ডারের অন্য ফাইলে import random লিখে random.randint(1, 6) ডাকলে AttributeError আসে। কারণ কী?

  • Python 3-এ randint সরিয়ে ফেলা হয়েছে
  • random module pip দিয়ে install করা হয়নি
  • import random-এর বদলে from random import randint লিখতে হয়
  • Python প্রথমে script-এর ফোল্ডারে খোঁজে, তাই নিজের random.py লোড হয়েছে

pkg/app.py-তে from .utils import helper লেখা। python pkg/app.py চালালে ImportError: attempted relative import with no known parent package আসে। সঠিক সমাধান কোনটি?

  • pkg-এর উপরের ফোল্ডার থেকে python -m pkg.app চালানো
  • ফাইলের শুরুতে sys.path.append("..") লেখা
  • from .utils-এর বদলে import .utils লেখা
  • pkg ফোল্ডার থেকে __init__.py মুছে ফেলা

একটি Python module-এ __all__ নেই, আর তাতে total, _cache ও report নামগুলো সংজ্ঞায়িত। অন্য ফাইলে from that_module import * লিখলে কোন নামগুলো আসবে?

  • total, _cache, report — সব
  • কিছুই না, কারণ __all__ নেই
  • শুধু total আর report
  • শুধু _cache

Python প্রোগ্রাম চালালে ImportError: cannot import name 'price' from partially initialized module 'shop.pricing' বার্তা এলে সবচেয়ে সম্ভাব্য কারণ কী?

  • shop/pricing.py ফাইলটি নেই
  • circular import — pricing আর অন্য একটি module একে অপরকে import করছে
  • price ফাংশনের বানান ভুল, আর কিছু নয়
  • shop ফোল্ডারে __init__.py আছে বলে

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. module = একটি .py ফাইল = একটি namespace; package = module-এর ফোল্ডার (__init__.py-সহ)। প্রোগ্রাম দায়িত্ব অনুযায়ী ফাইলে ভাগ করুন; input/print এক জায়গায় রাখুন।
  2. import = cache দেখা → খোঁজা → একবার চালানো (আগে sys.modules-এ রেখে) → নাম বাঁধা. দ্বিতীয় import কোড চালায় না; সবাই একই module object পায়।
  3. সরাসরি চালানো ফাইলের __name__ = "__main__". চালানোর কাজ main()-এ, আর ফাইলের শেষে if __name__ == "__main__": main().
  4. sys.path-এর প্রথমেই script-এর ফোল্ডার — নিজের ফাইলের নাম random.py, json.py রাখবেন না। import * আর sys.path জোড়াতালি এড়ান।
  5. Package-এর ফাইল চালান python -m pkg.module দিয়ে, তাহলে relative import কাজ করে। Circular import মানে নকশার সমস্যা — ভাগ করা অংশ তৃতীয় module-এ নিন; নির্ভরতা একদিকে রাখুন।

পরের পর্বে: নিজের module তো বানালেন — এবার অন্যদের লেখা লাখো package কীভাবে আনবেন? pip দিয়ে install, প্রতিটি project-এর জন্য আলাদা virtual environment, আর pyproject.toml দিয়ে নিজের project-কে গুছিয়ে install-যোগ্য করা।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান