ডিবাগিং আর লগিং
ট্রেসব্যাক ঠিক ক্রমে পড়া, print-এর বদলে logging, লেভেল ও getLogger(__name__), log.exception দিয়ে ধরা পড়া এররের পুরো হিসাব, আর breakpoint() দিয়ে ভিতরে ঢোকা।
কীভাবে শিখতে চান?
যে সমস্যাটা আমরা সমাধান করছি
কিছু একটা ভুল হলে আমরা সবাই যা করি:
def discount(price, rate):
print("price:", price)
print("rate:", rate)
result = price * (1 - rate)
print("result:", result)
return result
print(discount(100.0, 0.1))price: 100.0
rate: 0.1
result: 90.0
90.0এতে কাজ হয়। সমস্যাটা হলো এরপর কী হয়।
লাইনগুলো মুছে ফেললে পরের বার আবার লিখতে হয়। না মুছলে সেগুলো কোডের সাথেই থেকে যায়, আর একদিন অন্য কারো টার্মিনালে গিয়ে প্রিন্ট হয়। আর যে প্রোগ্রামটা সার্ভারে চলছে, তার তো কোনো টার্মিনালই নেই — সেখানে print কোথায় যায় তা কেউ জানে না।
এই অধ্যায়টা দুটো জিনিস নিয়ে: কীভাবে একটা প্রোগ্রাম নিজের কাজের হিসাব রাখে, আর প্রোগ্রাম ভাঙলে কীভাবে তার ভিতরে ঢুকে দেখা যায়।
এই অধ্যায় শেষে আপনি পারবেন
- একটা ট্রেসব্যাক ঠিক ক্রমে পড়তে, আর আসল জায়গাটা খুঁজে বের করতে
loggingদিয়ে মেসেজ লিখতে, আর লেভেলগুলোর মধ্যে বেছে নিতেlogging.getLogger(__name__)কেন এভাবে লেখা হয় তা বলতেlog.exceptionদিয়ে ধরা পড়া একটা এররের পুরো ট্রেসব্যাক রাখতে%sদিয়ে মেসেজ লেখা কেন ভালো তা বলতেbreakpoint()দিয়ে চলন্ত প্রোগ্রামের ভিতরে দাঁড়াতে
আগে যা জানা লাগবে: pytest দিয়ে টেস্ট লেখা।
ট্রেসব্যাক নিচ থেকে পড়তে হয়
def parse(text):
return int(text)
def total(rows):
return sum(parse(row) for row in rows)
print(total(["1", "2", "x"]))Traceback (most recent call last):
File "main.py", line 9, in <module>
print(total(["1", "2", "x"]))
^^^^^^^^^^^^^^^^^^^^^^
File "main.py", line 6, in total
return sum(parse(row) for row in rows)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "main.py", line 6, in <genexpr>
return sum(parse(row) for row in rows)
^^^^^^^^^^
File "main.py", line 2, in parse
return int(text)
^^^^^^^^^
ValueError: invalid literal for int() with base 10: 'x'অনেকেই এটা দেখে ঘাবড়ে যান আর উপর থেকে পড়তে শুরু করেন। আসলে পড়তে হয় উল্টো ক্রমে।
শেষ লাইনটা সবচেয়ে গুরুত্বপূর্ণ — কী ঘটেছে, আর কোন ভ্যালু নিয়ে। 'x' একটা সংখ্যা নয়।
তার ঠিক উপরের ব্লকটা দ্বিতীয় গুরুত্বপূর্ণ — কোথায় ঘটেছে: parse-এর ভিতরে, লাইন ২।
উপরের দিকটা হলো সেখানে পৌঁছানোর পথ, নিচের দিকে পড়লে: <module> ডেকেছে total-কে, total ডেকেছে জেনারেটরটাকে, সেটা ডেকেছে parse-কে। এই অংশটা তখনই দরকার, যখন প্রশ্নটা হলো “কোন কলটা এই খারাপ ভ্যালুটা পাঠিয়েছে?”
আর ^^^^ চিহ্নগুলো লাইনের কোন অংশটা ভেঙেছে তা দেখায় — একটা লম্বা লাইনে এটাই সবচেয়ে কাজের।
নিজের কোডের সবচেয়ে নিচের ফ্রেমটা খুঁজুন। ট্রেসব্যাকের অর্ধেকটা যদি লাইব্রেরির ফাইল হয়, তবু সমস্যাটা প্রায় সবসময় আপনার শেষ লাইনটাতেই।
print-এর বদলে logging
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s - %(message)s")
log = logging.getLogger("shop")
log.debug("this is not shown")
log.info("starting")
log.warning("price is unusually high: %s", 9999)
log.error("could not read the file")INFO shop - starting
WARNING shop - price is unusually high: 9999
ERROR shop - could not read the filedebug মেসেজটা নেই, কারণ লেভেল INFO-তে সেট করা। এটাই print-এর সাথে মূল পার্থক্য: মেসেজগুলো কোডে থেকে যায়, কিন্তু কোনগুলো দেখা যাবে তা আপনি ঠিক করেন।
লেভেল পাঁচটা, আর তাদের ক্রমটাই আসল কথা:
import logging
logging.basicConfig(level=logging.WARNING, format="%(levelname)s - %(message)s")
log = logging.getLogger("shop")
log.debug("debug")
log.info("info")
log.warning("warning")
log.error("error")
log.critical("critical")WARNING - warning
ERROR - error
CRITICAL - criticalকোনটা কখন, এক লাইনে:
debug— ভিতরের খুঁটিনাটি, কেবল খোঁজার সময় দরকারinfo— স্বাভাবিক ঘটনা: শুরু হলো, কয়টা সারি পড়া হলো, শেষ হলোwarning— কিছু একটা অস্বাভাবিক, কিন্তু কাজ চলছেerror— একটা কাজ ফেল করল, কিন্তু প্রোগ্রামটা চলছেcritical— প্রোগ্রামটাই আর চলতে পারে না
loggingলেখে stderr-এ,python main.py > out.txtলিখলে কেবল
__name__ — মডিউলের নামই লগারের নাম
import logging
log = logging.getLogger(__name__)
def line_total(price, quantity):
log.debug("line_total(%s, %s)", price, quantity)
return round(price * quantity * 1.15, 2)আর main.py-তে:
import logging
import pricing
logging.basicConfig(level=logging.DEBUG, format="%(levelname)s %(name)s - %(message)s")
log = logging.getLogger(__name__)
log.info("starting")
print(pricing.line_total(15.0, 3))51.75INFO __main__ - starting
DEBUG pricing - line_total(15.0, 3)২২ নম্বর অধ্যায়ের __name__ এখানে ফিরে এসেছে, আর ঠিক একই নিয়মে: চালানো ফাইলটার নাম __main__, আর ইমপোর্ট হওয়া মডিউলের নাম তার নিজের।
সুবিধাটা লগেই দেখা যাচ্ছে — প্রতিটা মেসেজ বলে দেয় সে কোথা থেকে এসেছে। বিশটা মডিউলের প্রোগ্রামে এটাই বড় পার্থক্য গড়ে দেয়।
আরেকটা কম চোখে পড়া সুবিধাও আছে: নামগুলো ডট দিয়ে একটা ট্রি বানায়, তাই shop.pricing-এর লেভেল আলাদাভাবে বদলানো যায়, আর বাকি প্রোগ্রামের লগ যেমন ছিল তেমনই থাকে।
লাইব্রেরিতে basicConfig ডাকবেন না। প্রতিটা মডিউল কেবল getLogger(__name__) লিখবে আর মেসেজ পাঠাবে; কোথায় যাবে আর কতটা যাবে — সেই সিদ্ধান্ত main-এর।
কারণটা দুটো নিয়ম মিলিয়ে বোঝা যায়। ইমপোর্ট মানে ফাইলটা চালানো। আর basicConfig কাজ করে কেবল প্রথমবার — লগিং একবার সেট হয়ে গেলে পরের কলগুলো চুপচাপ কিছুই করে না। ধরুন pricing.py ভুল করে নিজেই basicConfig ডাকে:
import logging
logging.basicConfig(level=logging.WARNING, format="%(message)s")
log = logging.getLogger(__name__)
def line_total(price, quantity):
log.debug("line_total(%s, %s)", price, quantity)
return round(price * quantity * 1.15, 2)উপরের main.py একটুও না বদলে আবার চালালে:
51.75ব্যস, এটুকুই — কোনো লগ লাইন নেই, কোনো এররও নেই। main.py প্রথমে import pricing করেছে, তাই লাইব্রেরির basicConfig আগে চলেছে আর লেভেল WARNING সেট করে দিয়েছে। তারপর main.py-র নিজের basicConfig(level=logging.DEBUG, ...) চুপচাপ উপেক্ষিত হয়েছে। লাইব্রেরিটা তাকে যে ইমপোর্ট করে, তার লগিং দখল করে নিয়েছে।
ধরা পড়া এররের পুরো ট্রেসব্যাক
২৪ নম্বর অধ্যায়ে আমরা এরর ধরতে শিখেছি। কিন্তু ধরা মানে তো তথ্যটা হারিয়ে ফেলা নয়।
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s")
log = logging.getLogger("shop")
def parse(text):
return int(text)
for value in ["1", "x"]:
try:
print(parse(value))
except ValueError:
log.exception("could not parse %r", value)
print("still running")1
still runningERROR - could not parse 'x'
Traceback (most recent call last):
File "main.py", line 14, in <module>
print(parse(value))
^^^^^^^^^^^^
File "main.py", line 9, in parse
return int(text)
^^^^^^^^^
ValueError: invalid literal for int() with base 10: 'x'log.exception গোটা ট্রেসব্যাকটা লগে রেখে দেয় — আর তবু প্রোগ্রামটা চলতে থাকে।
এটাই ২৪ নম্বর অধ্যায়ের সেই দোটানার সমাধান। except ... : pass তথ্যটা হারায়; except ছাড়া প্রোগ্রামটা থেমে যায়। log.exception দুটোই দেয়: কাজ চলতে থাকে, আর কী ভেঙেছিল তার পুরো হিসাব থেকে যায়।
শুধু মেসেজটা চাইলে log.error:
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s")
log = logging.getLogger("shop")
try:
int("x")
except ValueError as err:
log.error("could not parse: %s", err)
print("done")doneERROR - could not parse: invalid literal for int() with base 10: 'x'নিয়মটা সহজ: except ব্লকের ভিতরে log.exception, বাইরে log.error. exception কেবল একটা এরর সামলানোর সময়ই অর্থপূর্ণ।
মেসেজে %s, f-string নয়
লগিং-এর মেসেজ লেখার প্রচলিত রূপটা প্রথমে একটু অদ্ভুত লাগে:
log.debug("line_total(%s, %s)", price, quantity)কারণটা হলো অলসতা (lazy formatting): লেভেলের কারণে মেসেজটা বাদ পড়লে স্ট্রিংটা জোড়াই লাগানো হয় না। f-string দিয়ে লিখলে জোড়া লাগানোর কাজটা আগেই হয়ে যেত, তারপর ফলটা ফেলে দেওয়া হতো।
তবে সীমাটাও জানা দরকার:
import logging
logging.basicConfig(level=logging.WARNING, format="%(levelname)s - %(message)s")
log = logging.getLogger("shop")
def expensive():
print("expensive() was called")
return 42
log.debug("value is %s", expensive())
log.debug("value is %s", "cheap")
print("done")expensive() was called
doneমেসেজটা প্রিন্ট হয়নি, কিন্তু expensive() ঠিকই চলেছে। অলস হলো কেবল ফরম্যাটিং, আর্গুমেন্টের হিসাব নয় — আর্গুমেন্টগুলো log.debug ডাকার আগেই হিসাব হয়ে যায়, কারণ পাইথনে সব কল এভাবেই কাজ করে।
তাই সময়সাপেক্ষ কিছু কেবল ডিবাগের জন্য ডাকতে হলে আগে জিজ্ঞেস করুন: if log.isEnabledFor(logging.DEBUG):।
ফাইলে লেখা
import logging
from pathlib import Path
logging.basicConfig(
level=logging.INFO,
format="%(levelname)s %(name)s - %(message)s",
filename="run.log",
)
log = logging.getLogger("shop")
log.info("starting")
log.warning("something odd")
print("nothing was printed to the screen by logging")
print(Path("run.log").read_text(encoding="utf-8"), end="")nothing was printed to the screen by logging
INFO shop - starting
WARNING shop - something oddfilename দিলে মেসেজগুলো স্ক্রিনে না গিয়ে ফাইলে যায় — আর বাকি কোডের একটা লাইনও বদলাতে হয় না। এখানেই print আর logging-এর দূরত্বটা সবচেয়ে বড়: কী লেখা হবে সেটা ঠিক করে মডিউলগুলো, আর কোথায় যাবে সেটা ঠিক করে main।
বাস্তবে format-এ সময়ও রাখা হয়:
format="%(asctime)s %(levelname)-8s %(name)s - %(message)s"তাতে প্রতিটা লাইনের শুরুতে তারিখ ও সময় বসে। এই অধ্যায়ের উদাহরণগুলোতে সেটা নেই, কারণ তাহলে প্রতিবার চালালে আউটপুট আলাদা আসত — আর কোর্সে যে আউটপুট দেখানো আছে, আপনার মেশিনেও ঠিক সেটাই আসা উচিত।
ভিতরে ঢুকে দেখা — breakpoint()
লগ বলে কী ঘটেছে। কখনো কখনো জানতে হয় এই মুহূর্তে ভিতরে কী আছে।
def discount(price, rate):
result = price * (1 - rate)
breakpoint()
return result
print(discount(100.0, 0.1))breakpoint() লাইনে পৌঁছালে প্রোগ্রামটা থেমে যায় আর একটা প্রম্পট দেয়। সেশনটা দেখতে এরকম — প্রথম লাইনে আপনার নিজের ফাইলের পুরো পাথ থাকবে:
> .../main.py(4)discount()
-> return result
(Pdb) p price
100.0
(Pdb) p rate
0.1
(Pdb) p result
90.0
(Pdb) c
90.0কয়েকটা কমান্ড, আর এটুকুতেই বেশিরভাগ কাজ চলে:
p <নাম>— একটা ভ্যালু প্রিন্ট করে (ppসাজিয়ে প্রিন্ট করে)n— পরের লাইনে যায়, ফাংশনের ভিতরে না ঢুকেs— ফাংশনের ভিতরে ঢোকেc— চলতে থাকে পরের থামা পর্যন্তl— আশেপাশের কোডটা দেখায়q— বেরিয়ে যায়
print-এর তুলনায় সুবিধাটা হলো আপনাকে আগে থেকে ঠিক করতে হয় না কী দেখতে চান। থেমে যাওয়ার পর যেকোনো নাম প্রিন্ট করা যায়, যেকোনো এক্সপ্রেশন চালানো যায়।
একটা সম্পূর্ণ উদাহরণ
pricing.py:
"""Prices and tax, with logging instead of prints."""
import logging
log = logging.getLogger(__name__)
TAX_RATE = 0.15
def line_total(price, quantity):
log.debug("line_total(price=%s, quantity=%s)", price, quantity)
if quantity < 1:
raise ValueError(f"quantity must be at least 1: {quantity}")
return round(price * quantity * (1 + TAX_RATE), 2)
def read_order(rows):
"""Returns the usable lines, logging and skipping the rest."""
lines = []
for number, row in enumerate(rows, start=1):
try:
name, price, quantity = row.split(",")
lines.append((name, line_total(float(price), int(quantity))))
except ValueError:
log.warning("line %d skipped: %r", number, row)
log.info("%d of %d lines usable", len(lines), len(rows))
return linesmain.py:
import logging
import os
import pricing
logging.basicConfig(
level=logging.DEBUG if os.environ.get("SHOP_DEBUG") else logging.INFO,
format="%(levelname)-8s %(name)s - %(message)s",
)
log = logging.getLogger(__name__)
ROWS = ["pen,15.0,3", "bag,eight,1", "ink,120.0,2", "clip,5.0,0"]
def main():
log.info("reading %d rows", len(ROWS))
for name, amount in pricing.read_order(ROWS):
print(f"{name:<6} {amount:>9.2f}")
if __name__ == "__main__":
main()সাধারণভাবে python main.py চালালে — প্রিন্ট হওয়া সারিগুলো:
pen 51.75
ink 276.00আর লগে:
INFO __main__ - reading 4 rows
WARNING pricing - line 2 skipped: 'bag,eight,1'
WARNING pricing - line 4 skipped: 'clip,5.0,0'
INFO pricing - 2 of 4 lines usableএবার SHOP_DEBUG=1 দিয়ে চালালে (একই কোড), লগে:
INFO __main__ - reading 4 rows
DEBUG pricing - line_total(price=15.0, quantity=3)
WARNING pricing - line 2 skipped: 'bag,eight,1'
DEBUG pricing - line_total(price=120.0, quantity=2)
DEBUG pricing - line_total(price=5.0, quantity=0)
WARNING pricing - line 4 skipped: 'clip,5.0,0'
INFO pricing - 2 of 4 lines usableপাঁচটা জিনিস খেয়াল করার মতো।
একই প্রোগ্রাম, দুই পরিমাণের আউটপুট, কোনো কোড বদলানো ছাড়াই। এটাই এই অধ্যায়ের সারাংশ। print দিয়ে করতে হলে লাইনগুলো যোগ করা আর মুছে ফেলার চক্রটা চলতেই থাকত।
bag,eight,1 আর clip,5.0,0 — দুটো আলাদা কারণে বাদ পড়েছে, আর except ValueError দুটোকেই ধরেছে। float("eight") একটা ValueError রেইজ করে, আর line_total নিজেও quantity=0-এর জন্য একটা ValueError রেইজ করে। ২৪ নম্বর অধ্যায়ের ডিজাইনটা এখানে কাজে লাগছে: নিচের ফাংশন রেইজ করেছে, উপরের ফাংশন ধরেছে আর ঠিক করেছে কী করা হবে।
ডিবাগ লগটা দেখাচ্ছে যা সাধারণ লগ দেখায় না। clip সারিটার জন্য line_total সত্যিই ডাকা হয়েছিল — DEBUG ... quantity=0 লাইনটা তার প্রমাণ — আর তারপর সে এরর রেইজ করেছে। bag সারিটায় কোনো DEBUG লাইন নেই, কারণ float("eight") তার আগেই ভেঙেছে। দুটো সারিই “বাদ পড়া”, কিন্তু তারা দুই জায়গায় ভেঙেছে, আর সেটা কেবল ডিবাগ লেভেলেই দেখা যায়।
লেভেলটা আসছে এনভায়রনমেন্ট ভেরিয়েবল থেকে, কোড থেকে নয়। সার্ভারে চলা একটা প্রোগ্রামে এটাই স্বাভাবিক উপায় — একটা এনভায়রনমেন্ট ভেরিয়েবল বদলে আবার চালানো, ফাইল এডিট করে নতুন করে ডিপ্লয় করা নয়।
pricing.py কোথাও basicConfig ডাকেনি। সে কেবল getLogger(__name__) লিখেছে আর মেসেজ পাঠিয়েছে। কোথায় যাবে, কতটা যাবে — সবই main-এর সিদ্ধান্ত। সেজন্যই pricing-কে টেস্ট করার সময় বা অন্য প্রোগ্রামে ব্যবহার করার সময় সে কারো লগিং দখল করে না।
কিছু ভাঙা অবস্থা ও তার সমাধান
লগের কিছুই দেখা যাচ্ছে না basicConfig ডাকা হয়নি, নয়তো লেভেলটা বেশি উঁচুতে। ডিফল্ট লেভেল WARNING, তাই log.info এমনিতে দেখা যায় না।
basicConfig ডেকেছি কিন্তু কিছুই বদলায়নি সে একবারই কাজ করে — আগে কোথাও লগিং সেট হয়ে গেলে পরের কলটা চুপচাপ উপেক্ষিত হয়। প্রোগ্রামের একেবারে শুরুতে একবার ডাকুন, নয়তো force=True দিন।
main-এর লেভেল বা ফরম্যাট কাজ করছে না, অথচ কোনো এরর নেই ইমপোর্ট করা কোনো লাইব্রেরি নিজেই basicConfig ডেকেছে, আর সেটা main-এর কলের আগে চলেছে। তাই main-এর basicConfig চুপচাপ উপেক্ষিত। লাইব্রেরি থেকে basicConfig সরান — লাইব্রেরিতে কেবল getLogger(__name__)।
লগ ফাইলে যাচ্ছে না filename দেওয়ার আগেই লগিং সেট হয়ে গেছে। আবার সেই “একবারই” নিয়ম।
log.exception ব্যবহার করলাম কিন্তু ট্রেসব্যাক নেই সে কেবল একটা except ব্লকের ভিতরেই ট্রেসব্যাক পায়। বাইরে ব্যবহার করলে log.error।
python main.py > out.txt করলাম, লগগুলো ফাইলে নেই logging stderr-এ লেখে। 2> log.txt দিন, নয়তো filename ব্যবহার করুন।
ট্রেসব্যাকটা লম্বা আর সব লাইব্রেরির ফাইল নিচ থেকে উপরে পড়ুন, আর আপনার নিজের কোডের সবচেয়ে নিচের ফ্রেমটা খুঁজুন। সমস্যাটা প্রায় সবসময় সেখানেই।
breakpoint() কিছুই করে না PYTHONBREAKPOINT=0 সেট করা আছে। সরিয়ে দিন।
ধাপ ৪ / ৬ — অনুমান
যাচাই করুন
ট্রেসব্যাকের কোন অংশ বলে দেয় কী ভেঙেছে আর কোথায়?
def clean(row):
return row.strip().split(",")
def read(rows):
return [clean(row) for row in rows]
print(read(["a,b", None]))- Aকেবল `File "main.py", line 9` লাইনটাই দরকার
- Bট্রেসব্যাক কেবল পথ দেখায়, কারণ বলে না
- Cশেষ লাইনটা বলে কী ভেঙেছে, আর তার ঠিক উপরের ব্লকটা বলে কোথায়
- Dপ্রথম লাইনটা বলে কী ভেঙেছে, আর দ্বিতীয় ব্লকটা বলে কোথায়
লেভেল INFO-তে সেট করা। কী দেখা যাবে?
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s")
log = logging.getLogger("shop")
log.debug("one")
log.info("two")
log.warning("three")- AWARNING - three
- BINFO - two WARNING - three
- CDEBUG - one INFO - two WARNING - three
- DINFO - two
log.exception ব্যবহার করা হয়েছে। কী পাওয়া যাবে?
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s")
log = logging.getLogger("shop")
for value in ["1", "x"]:
try:
print(int(value))
except ValueError:
log.exception("could not parse %r", value)
print("still running")- A`1` আর `still running` প্রিন্ট হয়, আর লগে মেসেজের সাথে পুরো ট্রেসব্যাকও থাকে
- Bপ্রোগ্রামটা `ValueError` দিয়ে থেমে যায়
- C`1` প্রিন্ট হয়, তারপর প্রোগ্রামটা চুপচাপ শেষ হয়
- Dকেবল `ERROR - could not parse 'x'` — ট্রেসব্যাক থাকে না
উত্তর দিতে অ্যাকাউন্ট লাগবে
উত্তর মিলিয়ে দেখতে সাইন ইন করুন
প্রশ্নগুলো উপরে আছে, আর মাথায় মাথায় উত্তর ভেবে নেওয়াই আসল কাজ। সঠিক উত্তর, ব্যাখ্যা আর তিন ধাপের ইঙ্গিত দেখতে সাইন ইন করুন।
নিজে করুন
২৮ নম্বর অধ্যায়ে যে library.py টেস্ট করেছেন সেটা নিন, আর তার সব print সরিয়ে logging বসান।
log = logging.getLogger(__name__)মডিউলের উপরেadd()-এ একটাdebugমেসেজ, বইয়ের টাইটেলসহ- খারাপ ডেটা বাদ পড়লে একটা
warning - একটা
main.pyযেbasicConfigডাকে, আর লেভেলটা একটা এনভায়রনমেন্ট ভেরিয়েবল থেকে নেয় library.py-তেbasicConfigথাকবে না
তারপর ছয়টা পরীক্ষা:
- লেভেল
INFOদিয়ে চালান, তারপরDEBUGদিয়ে। কোনো কোড বদলাতে হলো? basicConfig-এfilename="run.log"যোগ করুন। স্ক্রিনে কী থাকে?library.py-তেও একটাbasicConfigডাকুন,main.py-র চেয়ে আলাদা লেভেল দিয়ে। এবার কার লেভেল মানা হয় — আর কেন?- একটা ফাংশনে ইচ্ছা করে
KeyErrorঘটান, তারপরexceptব্লকে একবারlog.error(err)আর একবারlog.exception("...")লিখুন। কোনটা বেশি কাজের, আর কেন? log.debug("%s", expensive())লিখুন, যেখানেexpensive()কিছু একটা প্রিন্ট করে, আর লেভেলWARNINGরাখুন।expensive()কি চলে?- একটা
breakpoint()বসিয়েp,nআরcদিয়ে ঘুরে দেখুন।
চতুর্থ পরীক্ষাটাই এই অধ্যায়ের শেষ কথা। একটা প্রোগ্রাম কেমন চলছে তা জানা যায় সে কী রেখে যায় তা দিয়ে — আর একটা except ব্লক যা কিছুই রেখে যায় না, সে কেবল প্রশ্নটাকে পিছিয়ে দেয়।
এরপর
এখানেই পাইথন কোর্সটা শেষ। ২৯টা অধ্যায় পেরিয়ে আপনি এখন একটা প্রোগ্রাম লিখতে, এরর সামলাতে, টেস্ট করতে আর কী ঘটছে তা দেখতে পারেন — আর শেষের এই দক্ষতাগুলোই একটা প্রোগ্রামকে এক দিনের বেশি টিকিয়ে রাখে।
পরের ধাপটা আর ভাষা শেখা নয়, ভাষা দিয়ে কিছু করা। pandas কোর্সটা ঠিক সেখান থেকেই শুরু করে: ২৫ নম্বর অধ্যায়ের CSV ফাইলগুলোই, কিন্তু হাজার হাজার সারি নিয়ে, আর এমন প্রশ্ন নিয়ে যার উত্তর লুপ দিয়ে বের করা আর বাস্তবসম্মত নয়।
ধাপ ৬ / ৬
কঠিন করা — অধ্যায়ের কুইজ
সহজ থেকে কঠিন — দশটি প্রশ্ন, শেষেরগুলো ইচ্ছে করেই কঠিন।
সাইন ইন করে কুইজ দিন