মডিউল আর ইমপোর্ট
নিজের লেখা ফাইলকে আরেক ফাইল থেকে ব্যবহার করা, import করলে আসলে কী ঘটে, `if __name__ == "__main__"` কেন লাগে, স্ট্যান্ডার্ড লাইব্রেরি, আর ফাইলের নাম দিয়ে মডিউল ঢেকে ফেলার ফাঁদ।
- 1সমস্যা
- 2বোঝা
- 3উদাহরণ
- 4অনুমান
- 5নিজে করা
- 6কঠিন করা
যে সমস্যাটা আমরা সমাধান করছি
গত অধ্যায়ে আমরা একটা line_total ফাংশন লিখেছিলাম। এখন দ্বিতীয় একটা প্রোগ্রাম লিখতে গিয়ে সেটাই আবার দরকার।
সবচেয়ে সহজ উপায় — কপি করে বসিয়ে দেওয়া। সেটা কাজও করে, প্রথম দিন।
তারপর করের হার বদলায়। আপনি একটা ফাইলে ঠিক করেন, অন্যটার কথা মনে থাকে না। এখন দুটো প্রোগ্রাম একই অর্ডারের দুটো আলাদা মোট দেখাচ্ছে, আর কোনটা ঠিক তা বলার উপায় নেই — কারণ দুটোই "ঠিক", যার যার ফাইল অনুযায়ী।
সমস্যাটা কপি করার পরিশ্রম নয়। সমস্যাটা হলো সত্যটা এখন দুই জায়গায়।
import pricing
print(pricing.TAX_RATE)
print(pricing.line_total(15.0, 3))0.15
51.75একটা ফাইল, আর যত প্রোগ্রাম দরকার তারা সবাই সেটাকেই ব্যবহার করে। হার বদলালে এক জায়গায় বদলায়।
এই অধ্যায়টা সেই এক লাইন — import — আর তার পেছনে আসলে কী ঘটে।
এই অধ্যায় শেষে আপনি পারবেন
- নিজের লেখা একটা ফাইলকে আরেকটা ফাইল থেকে ইমপোর্ট করতে
import x,from x import yআরimport x as z-এর পার্থক্য বলতে- বলতে ইমপোর্ট করার মুহূর্তে আসলে কী ঘটে, আর সেটা কতবার ঘটে
if __name__ == "__main__":কেন লেখা হয় তা ব্যাখ্যা করতে- স্ট্যান্ডার্ড লাইব্রেরি থেকে মডিউল আনতে
- পাইথন কোথায় খোঁজে তা জানতে, আর ফাইলের নাম দিয়ে একটা মডিউল ঢেকে ফেলার ফাঁদ এড়াতে
পূর্বশর্ত: স্কোপ আর পরিবর্তনীয় ডিফল্ট।
মডিউল মানে কেবল একটা ফাইল
pricing.py নামের একটা ফাইল থাকলেই সেটা pricing নামের একটা মডিউল। আলাদা করে কিছু ঘোষণা করতে হয় না, কোনো বিশেষ লাইন লিখতে হয় না — ফাইলটা থাকাই যথেষ্ট।
import pricing লিখলে গোটা মডিউলটা আসে, আর ভিতরের জিনিসে পৌঁছাতে হয় বিন্দু দিয়ে: pricing.TAX_RATE, pricing.line_total(...)।
বিন্দুটা বাড়তি টাইপিং মনে হতে পারে, কিন্তু সে একটা কাজ করে — পড়ার সময় বলে দেয় জিনিসটা কোথা থেকে এসেছে। বড় ফাইলে সেটা অনেক।
শুধু যেটা দরকার সেটাই আনা
from pricing import line_total
print(line_total(15.0, 3))
print(TAX_RATE)51.75
NameError: name 'TAX_RATE' is not definedfrom ... import ... কেবল যে নামটা চাওয়া হয়েছে সেটাই নিয়ে আসে, আর সেটাকে সরাসরি ব্যবহার করা যায় — বিন্দু লাগে না।
কিন্তু বাকি নামগুলো আসে না। TAX_RATE চাওয়া হয়নি, তাই সে নেই — যদিও একই ফাইলে সে বসে আছে।
কোনটা কখন? মোটামুটি নিয়ম: দুই-তিনটা নাম দরকার হলে from, আর অনেক কিছু দরকার হলে গোটা মডিউল। আর একটা জিনিস কখনো করবেন না — from pricing import *। ওটা সব নাম নিঃশব্দে টেনে আনে, তারপর line_total কোথা থেকে এলো তা খুঁজে বের করার আর কোনো উপায় থাকে না।
নাম বদলে নেওয়া
import pricing as p
print(p.line_total(15.0, 3))51.75as মডিউলটাকে এই ফাইলের ভিতরে একটা ছোট নাম দেয়।
এটা তখনই লিখবেন যখন নামটা সত্যিই লম্বা, অথবা প্রথা এমনই। অলসতার জন্য এক অক্ষরের নাম দিলে পড়ার সুবিধাটাই নষ্ট হয় — p.line_total(...) দেখে কেউ বলতে পারবে না জিনিসটা কোথাকার।
ইমপোর্ট করার মানে হলো ফাইলটা চালানো
এই জায়গাটা বেশিরভাগ মানুষ পরে গিয়ে আবিষ্কার করে, প্রায়ই খারাপ সময়ে।
tools.py:
print("tools is being set up")
def ping():
return "pong"main.py:
print("main starts")
import tools
import tools
print(tools.ping())
print("main ends")main starts
tools is being set up
pong
main endsদুটো জিনিস লক্ষ্য করুন।
ইমপোর্ট করা মানে ফাইলটা উপর থেকে নিচ পর্যন্ত চালানো। def লাইনগুলো ফাংশন তৈরি করে, আর বাকি যা আছে — যেমন ওই print — সেগুলো সত্যিই ঘটে। মডিউলের ভিতরে টপ-লেভেলে print বা input রাখলে যে-ই তাকে ইমপোর্ট করবে তার ঘাড়ে সেটা এসে পড়বে।
দ্বিতীয় import tools কিছুই করেনি। একটা মডিউল প্রোগ্রামে একবারই চলে; এরপর থেকে পাইথন আগেরটাকেই ফিরিয়ে দেয়। তাই একই মডিউল দশ জায়গায় ইমপোর্ট করা নিরাপদ ও সস্তা।
__name__ আর সেই বিখ্যাত লাইনটা
প্রতিটা মডিউলের ভিতরে __name__ নামের একটা নাম থাকে, যেটা পাইথন নিজে বসায়।
tools.py:
def ping():
return "pong"
print("module name is", __name__)
if __name__ == "__main__":
print("running directly")python tools.py চালালে:
module name is __main__
running directlyকিন্তু অন্য একটা ফাইল থেকে import tools করলে:
module name is tools
imported, name was toolsনিয়মটা সরল: যে ফাইলটাকে আপনি চালাচ্ছেন তার __name__ হয় "__main__"; যেগুলো ইমপোর্ট হচ্ছে তাদের __name__ হয় তাদের নিজের নাম।
তাই if __name__ == "__main__":-এর ভিতরের অংশটা চলে কেবল সরাসরি চালালে, ইমপোর্ট করলে নয়।
এটার দরকার আগের অংশ থেকেই বোঝা যায়। ইমপোর্ট মানে ফাইলটা চালানো — তাই ফাইলের শেষে main() লিখে রাখলে, যে-ই তাকে ইমপোর্ট করবে তার প্রোগ্রাম হঠাৎ আপনার প্রোগ্রামটাও চালিয়ে দেবে। ওই লাইনটা সেই দরজাটা বন্ধ করে।
স্ট্যান্ডার্ড লাইব্রেরি
পাইথনের সাথেই অনেকগুলো মডিউল আসে। কিছু ইনস্টল করতে হয় না।
import math
import statistics
print(math.sqrt(144))
print(math.floor(3.7), math.ceil(3.2))
print(statistics.mean([10, 20, 60]))12.0
3 4
30আরেকটা, যেটা প্রায়ই দরকার হয়:
import random
random.seed(7)
print(random.randint(1, 100))
print(random.choice(["pen", "bag", "ink"]))42
penrandom.seed(7) না থাকলে প্রতিবার আলাদা সংখ্যা আসত — সেটাই তো উদ্দেশ্য। বীজ বসালে ক্রমটা নির্দিষ্ট হয়ে যায়, তাই উপরের আউটপুটটা আপনার মেশিনেও একই আসবে। পরীক্ষা লেখার সময় এটা অপরিহার্য: এলোমেলো কিছু পরীক্ষা করতে হলে আগে তাকে পুনরাবৃত্তিযোগ্য বানাতে হয়।
এটা "এলোমেলো" নয়, "এলোমেলোর মতো"।randomএকটা হিসাব থেকে সংখ্যা বানায়, তাই একই বীজ থেকে সবসময় একই ক্রম। পাসওয়ার্ড বা টোকেনের জন্য এটা ব্যবহার করবেন না — তার জন্যsecretsমডিউল আছে।
পাইথন কোথায় খোঁজে
import pricing লিখলে পাইথন ক্রম ধরে খোঁজে: আগে ইন্টারপ্রেটারের ভিতরে গাঁথা মডিউলগুলো, তারপর যে স্ক্রিপ্টটা চলছে তার নিজের ফোল্ডার, তারপর স্ট্যান্ডার্ড লাইব্রেরি, তারপর ইনস্টল করা প্যাকেজ।
এই ক্রমটার একটা ফল আছে, আর সেটা মানুষকে ভোগায়।
আপনার ফোল্ডারে random.py নামের একটা ফাইল আছে, আর আপনি লিখলেন:
import random
print(random.choice([1, 2, 3]))AttributeError: module 'random' has no attribute 'choice'আপনার ফাইলটা আসল random-এর আগে পড়েছে, তাই সেটাই ইমপোর্ট হয়েছে — আর তার ভিতরে choice নেই।
বার্তাটা বিভ্রান্তিকর, কারণ সে বলছে random-এ choice নেই, যেটা শুনতে অসম্ভব। কিন্তু সে আপনার random-এর কথা বলছে।
নিয়ম: আপনার ফাইলের নাম কোনো মডিউলের নামের সাথে মেলানো যাবে না। random.py, string.py, json.py, csv.py — এগুলো নিরীহ নাম বলে মনে হয়, আর ঠিক সেজন্যই ফাঁদটা কাজ করে।
একটা ব্যতিক্রম, যা নিয়মটাকেই বোঝায়।math.pyনামের ফাইল বানিয়েimport mathকরলে আসলmath-ই আসবে, আপনারটা নয়। কারণmathইন্টারপ্রেটারের ভিতরে গাঁথা, আর সেটা তালিকার সবার আগে। অর্থাৎ ফাঁদটা কিছু নামে কাজ করে, কিছুতে করে না — যা মনে রাখার চেয়ে নামটা বদলে ফেলাই সহজ।
ফোল্ডারে সাজানো
ফাইল বাড়তে থাকলে তাদের ফোল্ডারে ভাগ করা যায়। ফোল্ডারটার ভিতরে একটা __init__.py ফাইল রাখুন — খালি হলেও চলবে — তাহলে ফোল্ডারটা একটা প্যাকেজ হয়ে যায়।
shop/
__init__.py
pricing.py
main.pyতারপর বিন্দু দিয়ে পথটা লেখা যায়:
from shop.pricing import line_total
print(line_total(15.0, 3))51.75আপাতত এটুকুই যথেষ্ট। এই অধ্যায়ের বাকি অংশে আমরা সমতল ফাইলেই থাকব।
একটা সম্পূর্ণ উদাহরণ
তিনটে ফাইল, আর তাদের মধ্যে নির্ভরতা এক দিকেই।
pricing.py:
"""Prices and tax. Nothing here prints, and nothing here asks for input."""
TAX_RATE = 0.15
def line_total(price, quantity):
"""One line of an order, tax included."""
return round(price * quantity * (1 + TAX_RATE), 2)
def order_total(lines):
"""`lines` is a list of (name, price, quantity) tuples."""
return round(sum(line_total(price, qty) for _, price, qty in lines), 2)report.py:
"""Turning numbers into lines of text. It imports pricing; pricing imports nothing."""
import pricing
def receipt(lines):
rows = [f"{name:<8} {pricing.line_total(price, qty):>8.2f}"
for name, price, qty in lines]
rows.append("-" * 17)
rows.append(f"{'total':<8} {pricing.order_total(lines):>8.2f}")
return rows
if __name__ == "__main__":
print("report.py has no data of its own to show")main.py:
"""The only file that is meant to be run."""
import statistics
from report import receipt
ORDER = [
("pen", 15.0, 3),
("bag", 850.0, 1),
("ink", 120.0, 2),
]
def main():
for row in receipt(ORDER):
print(row)
print()
quantities = [qty for _, _, qty in ORDER]
print("mean quantity:", statistics.mean(quantities))
if __name__ == "__main__":
main()python main.py চালালে:
pen 51.75
bag 977.50
ink 276.00
-----------------
total 1305.25
mean quantity: 2চারটে জিনিস দেখার মতো।
নির্ভরতা এক দিকে যায়। main জানে report-কে, report জানে pricing-কে, আর pricing কাউকে জানে না। এই ক্রমটা একটা তালিকা বানায়, আর তালিকার শেষ মাথার ফাইলটা — pricing — একা একা পরীক্ষা করা যায়। pricing-কে যদি আবার report-কে ইমপোর্ট করতে হতো, দুজনের কেউই আর একা দাঁড়াতে পারত না।
pricing.py কিছু ছাপে না, কিছু জিজ্ঞেসও করে না। সে কেবল মান নেয় আর মান ফেরত দেয় — একবিংশ অধ্যায়ের সেই পিওর ফাংশনগুলো। ইমপোর্ট মানে ফাইলটা চালানো, তাই এই সংযমটা ভদ্রতা নয়, প্রয়োজন।
report.py-তেও if __name__ == "__main__": আছে, আর সেটা কোনো কাজের কিছু করে না — এই ফাইলের নিজের কোনো তথ্য নেই বলে তার দেখানোরও কিছু নেই। একে সরিয়ে ফেলা যেত। রেখে দেওয়া হয়েছে কারণ ফাইলটা যদি কোনোদিন সরাসরি চালানো হয়, তখন সে অন্তত কথা বলে — নীরবে কিছু না করে থেমে যাওয়ার চেয়ে ভালো।
main.py ছাড়া কোনো ফাইল নিজে থেকে কিছু করে না। পুরো প্রোগ্রামের কেবল একটাই প্রবেশপথ, আর সেটা পড়লেই বোঝা যায় কী ঘটবে।
কিছু ভাঙা অবস্থা ও তার সমাধান
ModuleNotFoundError: No module named 'priceing' বানান ভুল, নয়তো ফাইলটা অন্য ফোল্ডারে। যে স্ক্রিপ্টটা চালাচ্ছেন তার পাশেই ফাইলটা আছে কিনা দেখুন।
ImportError: cannot import name 'line_totl' from 'pricing' মডিউলটা পাওয়া গেছে, ভিতরের নামটা নয়। বার্তাটা প্রায়ই কাছের নামটা বলে দেয় — Did you mean: 'line_total'?
AttributeError: module 'random' has no attribute 'choice' প্রায় সবসময়ই আপনার নিজের একটা ফাইল আসল মডিউলটাকে ঢেকে দিয়েছে। ফোল্ডারে ওই নামের ফাইল খুঁজুন, আর তার নাম বদলান। __pycache__ ফোল্ডারটাও মুছে দিন।
ImportError: cannot import name 'A' from partially initialized module 'a' (most likely due to a circular import) দুটো ফাইল একে অপরকে ইমপোর্ট করছে। সাধারণ সমাধান হলো যে অংশটা দুজনেরই দরকার সেটাকে তৃতীয় একটা ফাইলে সরানো, যেটা কাউকে ইমপোর্ট করে না।
ইমপোর্ট করামাত্র কিছু একটা ছাপা হলো বা ইনপুট চাইল মডিউলের টপ-লেভেলে কোড আছে। সেটাকে একটা ফাংশনে ঢোকান, আর if __name__ == "__main__":-এর ভিতর থেকে ডাকুন।
সম্পাদনা করলাম, কিন্তু পুরোনো আচরণই দেখছি প্রোগ্রামটা আবার চালান — একটা চলতে থাকা প্রোগ্রাম মডিউলটাকে আর নতুন করে পড়ে না। নোটবুক ব্যবহার করলে কার্নেল রিস্টার্ট করুন।
ধাপ ৪ / ৬ — অনুমান
যাচাই করুন
line_total চাওয়া হয়েছে, TAX_RATE নয়। কী হবে?
# pricing.py
TAX_RATE = 0.15
def line_total(price, quantity):
return round(price * quantity * (1 + TAX_RATE), 2)
# main.py
from pricing import line_total
print(line_total(15.0, 3))
print(TAX_RATE)- A`51.75` ছাপা হয়, তারপর `NameError`
- B`51.75` আর `0.15` — দুটোই ছাপা হয়
- C`ImportError`
- D`51.75` ছাপা হয়, তারপর `None`
import tools দুবার লেখা হয়েছে। কী ছাপা হবে?
# tools.py
print("tools is being set up")
def ping():
return "pong"
# main.py
print("main starts")
import tools
import tools
print(tools.ping())
print("main ends")- Amain starts tools is being set up pong main ends
- Bmain starts tools is being set up tools is being set up pong main ends
- Ctools is being set up main starts pong main ends
- Dmain starts pong main ends
python main.py চালানো হচ্ছে। কী ছাপা হবে?
# tools.py
print("name:", __name__)
if __name__ == "__main__":
print("direct")
# main.py
import tools
print("done")- Aname: tools done
- Bname: __main__ direct done
- Cname: tools direct done
- Dname: main done
উত্তর দিতে অ্যাকাউন্ট লাগবে
উত্তর মিলিয়ে দেখতে সাইন ইন করুন
প্রশ্নগুলো উপরে আছে, আর মাথায় মাথায় উত্তর ভেবে নেওয়াই আসল কাজ। সঠিক উত্তর, ব্যাখ্যা আর তিন ধাপের ইঙ্গিত দেখতে সাইন ইন করুন।
নিজে করুন
একটা ফোল্ডারে তিনটে ফাইল বানান।
textutil.py — দুটো ফাংশন: initials(name) যা "rafi ahmed" থেকে "RA" বানায়, আর shorten(text, limit) যা লম্বা লেখাকে ছেঁটে শেষে ... বসায়। এই ফাইলে কোনো print থাকবে না।
table.py — textutil ইমপোর্ট করে, আর rows(people) নামে একটা ফাংশন দেয় যা নামের একটা লিস্ট থেকে সাজানো লাইনের লিস্ট ফেরত দেয়।
main.py — table ইমপোর্ট করে, কিছু নাম দিয়ে ডাকে, আর ছাপে। তিনটে ফাইলেই if __name__ == "__main__": রাখুন।
তারপর চারটে পরীক্ষা, প্রতিটাই নিজের চোখে দেখার মতো:
textutil.py-র একদম উপরেprint("loaded")লিখুন, তারপরpython main.pyচালান। লাইনটা কি দেখা যায়? কখন —main.py-র প্রথমprint-এর আগে না পরে?main.py-তেimport tableদুবার লিখুন।loadedকতবার ছাপা হয়?- ফোল্ডারে
statistics.pyনামে একটা খালি ফাইল বানান, তারপরmain.py-তেimport statisticsকরেstatistics.mean([1, 2])ডাকুন। বার্তাটা পড়ুন — সে কোনstatistics-এর কথা বলছে? textutil.py-তেimport tableযোগ করুন, যাতে দুজনে দুজনকে ইমপোর্ট করে। কোন বার্তাটা আসে?
শেষ দুটো পরীক্ষা এমন দুটো ভুল, যেগুলো বার্তা পড়ে চেনা না থাকলে ঘণ্টার পর ঘণ্টা কাটতে পারে। একবার নিজে বানালে পরেরবার কয়েক সেকেন্ড।
ধাপ ৬ / ৬
কঠিন করা — অধ্যায়ের কুইজ
সহজ থেকে কঠিন — দশটি প্রশ্ন, শেষেরগুলো ইচ্ছে করেই কঠিন।
সাইন ইন করে কুইজ দিন