एक्सेप्शन्स — टूटी हुई स्थिति को संभालना
try/except से किसी ख़ास एरर को पकड़ना, except Exception: pass लगभग हमेशा गलत क्यों है, else और finally, ख़ुद raise करना, और सही स्तर का एरर चुनना।
आप कैसे सीखना चाहते हैं?
वह समस्या जिसे हम हल कर रहे हैं
पिछले अध्याय के आख़िरी सवाल में एक प्रोग्राम तीन लाइनें ठीक से प्रोसेस करके चौथी पर रुक गया था। छोटा करके देखें तो ऐसा:
numbers = ["10", "twenty", "30"]
total = 0
for text in numbers:
total += int(text)
print(total)ValueError: invalid literal for int() with base 10: 'twenty'(एरर से पहले पायथन traceback की कुछ लाइनें भी प्रिंट करता है; इस अध्याय में आम तौर पर सिर्फ़ आख़िरी लाइन दिखाई गई है।)
10 ठीक से जुड़ा था। 30 कभी जुड़ा ही नहीं। और print(total) कभी चला ही नहीं।
एक ख़राब लाइन की वजह से पूरा काम बंद। अब तक हमारे पास दो ही रास्ते थे — या तो सारा डेटा एकदम सही हो, या प्रोग्राम रुक जाए।
numbers = ["10", "twenty", "30"]
total = 0
for text in numbers:
try:
total += int(text)
except ValueError:
print("skipping", text)
print(total)skipping twenty
40एक तीसरा रास्ता भी है: समस्या को होने दें, फिर उसका जवाब दें।
इस अध्याय के अंत तक आप कर पाएंगे
try/exceptसे एक ख़ास एरर पकड़ना- बताना कि ख़ाली
except:याexcept Exception: passलगभग हमेशा ग़लत क्यों है as errसे एरर को हाथ में लेकर उसका मैसेज पढ़ना- बताना कि
elseऔरfinallyक्या करते हैं - ख़ुद
raiseकरना, और समझना कि कब करना चाहिए - एरर का परिवार पहचानना, और सही नाप का एरर पकड़ना
पहले से क्या आना चाहिए: फ़ाइल पढ़ना और लिखना।
एरर भी एक ऑब्जेक्ट है
as लिखें तो एरर हाथ में आ जाता है:
try:
int("twenty")
except ValueError as err:
print(type(err))
print(err)<class 'ValueError'>
invalid literal for int() with base 10: 'twenty'अब तक टर्मिनल में जो लाल लाइनें देखते आए हैं, वे असल में यही ऑब्जेक्ट हैं — और प्रिंट करने पर जो आता है, वही उसका मैसेज है।
इसका एक असली फ़ायदा है: एरर ख़ुद सबसे अच्छी व्याख्या साथ लेकर चलता है। अपने शब्दों में "कुछ ग़लत हो गया" लिखने से err को प्रिंट कर देना लगभग हमेशा ज़्यादा काम का है।
सही एरर पकड़ें
try:
int("twenty")
except TypeError:
print("caught")ValueError: invalid literal for int() with base 10: 'twenty'except सिर्फ़ वही टाइप पकड़ता है जो लिखा है। यहाँ TypeError लिखा है, और हुआ है ValueError — इसलिए except ब्लॉक जैसे है ही नहीं।
यह खीझ भरा लग सकता है, लेकिन यही काम की चीज़ है। आप किस ग़लती के लिए तैयार हैं, यह except वाली लाइन ही बता देती है — और जिसके लिए तैयार नहीं, वह पहले की तरह प्रोग्राम रोक देती है, जो सही है।
इसीलिए यह न लिखें:
numbers = ["10", "twenty", "30"]
total = 0
for text in numbers:
try:
total += int(text)
except Exception:
pass
print(total)40जवाब सही है, फिर भी कोड ख़राब है। except Exception: pass सारे एरर निगल जाता है — जिनके बारे में आपने सोचा था और जिनके बारे में नहीं सोचा, दोनों। कल numbers की जगह ग़लती से एक डिक्शनरी आ जाए, या अंदर के कोड में स्पेलिंग की ग़लती हो, तो यह प्रोग्राम चुपचाप ग़लत जवाब देगा।
नियम: जिस एरर के लिए आपके पास योजना है, ठीक वही पकड़ें। और पकड़ें तो कुछ करें — कम से कम बताएँ कि क्या हुआ।
कई टाइप
def get(data, key):
try:
return data[key]
except KeyError:
return "no such key"
except IndexError:
return "no such position"
print(get({"pen": 15}, "pen"))
print(get({"pen": 15}, "bag"))
print(get([1, 2, 3], 9))15
no such key
no such positionकई except एक के बाद एक लिखे जा सकते हैं, और जो पहले मेल खाता है वही चलता है — बाकी देखे भी नहीं जाते।
जवाब एक जैसा हो तो साथ लिखे जा सकते हैं:
for value in ["10", None, "x"]:
try:
print(int(value))
except (ValueError, TypeError) as err:
print(type(err).__name__, "-", err)10
TypeError - int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
ValueError - invalid literal for int() with base 10: 'x'type(err).__name__ एरर के टाइप का नाम देता है — लॉग लिखते समय काम आता है।
एरर का एक परिवार है
print(issubclass(FileNotFoundError, OSError))
print(issubclass(ValueError, Exception))
print(issubclass(KeyError, LookupError))
print(issubclass(IndexError, LookupError))True
True
True
Trueएरर एक पेड़ की तरह सजे हैं, और माता-पिता को पकड़ें तो संतान भी पकड़ी जाती है:
try:
raise FileNotFoundError("gone")
except OSError as err:
print("caught as OSError:", err)caught as OSError: goneकाम के कुछ रिश्ते: FileNotFoundError और PermissionError दोनों OSError हैं; KeyError और IndexError दोनों LookupError हैं; और लगभग सब कुछ Exception है।
यहीं से नियम आता है: जितना छोटे नाप का एरर पकड़ा जा सके, उतना अच्छा। except Exception लिखना सबसे आसान है और सबसे कम काम का।
else और finally
def read(text):
try:
value = int(text)
except ValueError:
print("bad:", text)
return None
else:
print("good:", value)
return value
finally:
print("done with", text)
print(read("10"))
print(read("ten"))good: 10
done with 10
10
bad: ten
done with ten
Noneelse तब चलता है जब try ब्लॉक में कुछ न टूटे। इससे try का अंदर का हिस्सा छोटा रखा जा सकता है — सिर्फ़ वह लाइन जो टूट सकती है — और सफल होने पर जो करना है वह else में जाता है। try के अंदर ज़्यादा कोड रखें तो ऐसी चीज़ें भी पकड़ी जाती हैं जिन्हें पकड़ना नहीं था।
finally हमेशा चलता है, चाहे जो हो। return के बाद भी:
def risky():
try:
return "from try"
finally:
print("finally still runs")
print(risky())finally still runs
from tryreturn ने वैल्यू तय कर ली है, लेकिन फ़ंक्शन से निकलने से पहले finally चलता है। सफ़ाई के काम के लिए यही जगह है — हालाँकि फ़ाइल के मामले में with यह काम पहले ही कर देता है।
ख़ुद raise करना
पकड़ना जितना ज़रूरी है, फेंकना भी उतना ही।
def line_total(price, quantity):
if quantity < 0:
raise ValueError(f"quantity cannot be negative: {quantity}")
return price * quantity
print(line_total(15.0, 3))
print(line_total(15.0, -1))45.0
ValueError: quantity cannot be negative: -1raise के बिना विकल्प क्या था? None लौटाना, या 0 — और तब ग़लती सफ़र करती। किसी टोटल के हिसाब में घुस जाती, और बहुत बाद में एक अजीब नंबर बनकर सामने आती, जिसकी जड़ ढूँढना मुश्किल होता।
एरर ग़लती को उसकी पैदाइश की जगह पर ही रोक देता है, और मैसेज में वजह भी बता देता है।
मैसेज लिखते समय जिस वैल्यू ने समस्या पैदा की उसे अंदर रखें। "invalid quantity" और "quantity cannot be negative: -1" — दूसरा पढ़कर काम शुरू किया जा सकता है।
पकड़कर फिर से फेंका भी जा सकता है, बेहतर मैसेज के साथ। parse.py:
def parse(text):
try:
return int(text)
except ValueError:
raise ValueError(f"not a number: {text!r}")
print(parse("10"))
print(parse("ten"))10
Traceback (most recent call last):
File ".../parse.py", line 3, in parse
return int(text)
^^^^^^^^^
ValueError: invalid literal for int() with base 10: 'ten'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File ".../parse.py", line 9, in <module>
print(parse("ten"))
^^^^^^^^^^^^
File ".../parse.py", line 5, in parse
raise ValueError(f"not a number: {text!r}")
ValueError: not a number: 'ten'इस बार पूरा आउटपुट दिखाया गया है, क्योंकि यहाँ आख़िरी लाइन पूरी कहानी नहीं बताती। (आपकी मशीन पर File लाइनों में फ़ाइल का पूरा पाथ होगा; यहाँ उसे ... से छोटा किया गया है।) पायथन ने दो traceback प्रिंट किए: पहला int() का असली एरर, दूसरा हमारा नया ValueError। बीच की लाइन During handling of the above exception, another exception occurred: बताती है कि दूसरा एरर पहले को सँभालने के दौरान हुआ। कोई जानकारी नहीं खोई — लेकिन पढ़कर ऐसा लगता है जैसे except ब्लॉक के अंदर ही कोई बग था।
जानबूझकर नया एरर फेंकें तो साफ़ बताएँ: except ValueError as err: लिखें और फिर raise ValueError(f"not a number: {text!r}") from err। तब बीच की लाइन बन जाती है The above exception was the direct cause of the following exception: — यानी "पहला ही दूसरे की वजह है", जो ठीक हमारा मतलब है।
{text!r} वैल्यू को repr करके रखता है, इसलिए कोट्स दिखते हैं — not a number: 'ten' और not a number: ten का फ़र्क़ तब समझ आता है जब वैल्यू असल में एक ख़ाली स्ट्रिंग या फ़ालतू स्पेस हो।
पहले पूछना, या बाद में माफ़ी माँगना
prices = {"pen": 15}
if "bag" in prices:
print(prices["bag"])
else:
print("not stocked")
try:
print(prices["bag"])
except KeyError:
print("not stocked")not stocked
not stockedदोनों सही हैं। पायथन में दूसरा ज़्यादा चलता है, और उसकी एक वजह है: पहला दो बार ढूँढता है — एक बार in में, एक बार [...] में — और बीच में कुछ बदल जाए तो फिर भी टूट सकता है।
फिर भी जो नियमित रूप से होता है, उसके लिए if लिखना ही साफ़ है। अपवाद (exception) अपवाद के लिए — नाम ही याद दिला देता है।
एक पूरा उदाहरण
orders.txt, जिसमें जानबूझकर दो ख़राब लाइनें हैं:
pen,15.0,3
bag,eight,1
ink,120.0,2
clip,5.0main.py:
"""Read an order file, and keep going when a line is wrong."""
from pathlib import Path
TAX_RATE = 0.15
def parse_line(raw, number):
"""Returns one order line, or raises ValueError saying which line was wrong."""
parts = raw.split(",")
if len(parts) != 3:
raise ValueError(f"line {number}: expected 3 fields, got {len(parts)}")
name, price, quantity = parts
try:
return name, float(price), int(quantity)
except ValueError as err:
raise ValueError(f"line {number}: {err}")
def read_order(path):
"""Returns the good lines and the complaints, never raising for bad data."""
try:
text = path.read_text(encoding="utf-8")
except FileNotFoundError:
return [], [f"no such file: {path}"]
lines = []
problems = []
for number, raw in enumerate(text.splitlines(), start=1):
if not raw.strip():
continue
try:
lines.append(parse_line(raw, number))
except ValueError as err:
problems.append(str(err))
return lines, problems
def main():
lines, problems = read_order(Path("orders.txt"))
total = 0.0
for name, price, quantity in lines:
amount = round(price * quantity * (1 + TAX_RATE), 2)
total += amount
print(f"{name:<6} {amount:>9.2f}")
print(f"{'total':<6} {total:>9.2f}")
print()
print(f"{len(lines)} lines used, {len(problems)} skipped")
for problem in problems:
print(" -", problem)
if __name__ == "__main__":
main()pen 51.75
ink 276.00
total 327.75
2 lines used, 2 skipped
- line 2: could not convert string to float: 'eight'
- line 5: expected 3 fields, got 2चार चीज़ें देखने लायक हैं।
parse_line फेंकता है, read_order पकड़ता है। नीचे वाला फ़ंक्शन नहीं जानता कि पूरे प्रोग्राम को क्या करना चाहिए — वह बस जानता है कि यह लाइन इस्तेमाल लायक नहीं। क्या किया जाए, यह फ़ैसला ऊपर वाले फ़ंक्शन का है। एरर नीचे फेंका जाता है, ऊपर पकड़ा जाता है — यही बँटवारा exception सिस्टम का असली काम है।
लाइन नंबर मैसेज के अंदर है। could not convert string to float: 'eight' अपने-आप नहीं बताता कि कहाँ देखना है; line 2: ... बताता है। जो जानकारी पकड़ते समय आपके पास है और बाद में नहीं रहेगी, उसे मैसेज में डालना ही नियम है।
read_order ख़ुद कभी नहीं टूटता। वह दो चीज़ें लौटाता है — जो काम आया और जो नहीं आया। फ़ाइल न हो तब भी उसी आकार का जवाब आता है, इसलिए main को दो तरह के रास्ते नहीं लिखने पड़ते।
अंदर का try ब्लॉक ठीक एक लाइन का है। float(price) और int(quantity) — सिर्फ़ वही हिस्सा जो टूट सकता है, उसके अंदर है। parts = raw.split(",") बाहर है, क्योंकि उसका टूटना अपेक्षित नहीं, और टूटे तो मैं जानना चाहता हूँ।
जब कुछ टूट जाए
एरर पकड़ा नहीं जा रहा, जबकि except लिखा है टाइप मेल नहीं खाया। मैसेज की आख़िरी लाइन का पहला शब्द ही एरर का नाम है — वही लिखें, या उसके माता-पिता को पकड़ें।
except है, फिर भी प्रोग्राम रुक जाता है टूटने वाली लाइन try ब्लॉक के अंदर नहीं है। इंडेंटेशन देखें।
SyntaxError: expected 'except' or 'finally' block एक try लिखा गया है जिसका कोई except नहीं। try अकेला खड़ा नहीं हो सकता।
सब कुछ "चल रहा है" लेकिन जवाब ग़लत हैं कहीं except Exception: pass है। उसे ढूँढें — समस्या वहीं है।
raise किया, लेकिन कोई मैसेज नहीं दिख रहा raise ValueError लिखा गया है, raise ValueError("...") नहीं। ब्रैकेट और मैसेज दें।
finally में return लिखने पर एरर ग़ायब हो जाता है यही होता है, और इसीलिए finally में return न लिखें। वहाँ सिर्फ़ सफ़ाई का काम।
चरण 4 / 6 — अनुमान
अपनी समझ की जाँच करें
एक except लिखा है। प्रोग्राम का क्या होगा?
try:
int("twenty")
except TypeError:
print("caught")- A`caught` प्रिंट होता है
- Bकुछ भी प्रिंट नहीं होता, प्रोग्राम चुपचाप ख़त्म होता है
- C`TypeError` के साथ रुक जाता है
- Dप्रोग्राम `ValueError` के साथ रुक जाता है — `except` जैसे है ही नहीं
पूरा लूप try के अंदर है। क्या प्रिंट होगा?
values = ["10", "x", "30"]
total = 0
try:
for text in values:
total += int(text)
except ValueError:
print("gave up")
print(total)- Agave up 40
- Bgave up 10
- C40
- Dgave up 0
try और except — दोनों में return है। क्या प्रिंट होगा?
def read(text):
try:
return int(text)
except ValueError:
return 0
finally:
print("checked", text)
print(read("7"))
print(read("seven"))- Achecked 7 7 checked seven 0
- B7 0
- Cchecked 7 7 0
- D7 checked 7 0 checked seven
उत्तर देने के लिए अकाउंट आवश्यक है
अपने उत्तर जाँचने के लिए साइन इन करें
प्रश्न ऊपर दिए गए हैं, और मन में उत्तर सोचना ही मुख्य कार्य है। सही उत्तर, व्याख्या और तीन-स्तरीय संकेत देखने के लिए साइन इन करें।
आपकी बारी
पिछले अध्याय की summary.py पर लौटें, जो people.txt पढ़ती थी।
अब उसे ऐसा बनाएँ कि वह कभी क्रैश न हो, और आख़िर में बता दे कि कौन-सी लाइनें छोड़ी गईं और क्यों। एक parse_line(raw, number) फ़ंक्शन बनाएँ जो ख़राब लाइन के लिए लाइन नंबर के साथ ValueError फेंके।
फिर फ़ाइल को जानबूझकर बिगाड़ें — एक लाइन से कॉमा हटा दें, एक में नंबर की जगह अक्षर रखें, और एक लाइन बिल्कुल ख़ाली रखें। जैसे:
rafi,12
nadia 30
sami,ten
lina,25ख़ाली लाइन कोई शिकायत नहीं है — पिछले अध्याय की तरह वह चुपचाप छोड़ दी जाती है। इसलिए प्रोग्राम आख़िर तक चलेगा, और दो शिकायतें देगा। अगर मैसेज पूरे उदाहरण की तरह लिखें (expected 2 fields, got ... और line {number}: {err}), तो आउटपुट का आख़िरी हिस्सा ठीक ऐसा होगा:
2 lines used, 2 skipped
- line 2: expected 2 fields, got 1
- line 3: invalid literal for int() with base 10: 'ten'फिर चार प्रयोग:
except ValueErrorकी जगहexcept Exception: passलिखें। आउटपुट क्या होता है, और वह ज़्यादा ख़तरनाक क्यों है?parse_lineके मैसेज से लाइन नंबर हटा दें। अब एक ख़राब लाइन ढूँढने में कितना समय लगता है?tryब्लॉक बड़ा करें — लूप का पूरा शरीर उसके अंदर डाल दें। अब एक लाइन ख़राब हो तो क्या होता है?- एक
finallyजोड़ें जो हर लाइन के बाद कुछ प्रिंट करे, और देखें कि वह अच्छी और ख़राब दोनों तरह की लाइनों पर चलता है या नहीं।
तीसरा प्रयोग इस अध्याय की सबसे काम की आदत सिखाता है: try ब्लॉक जितना छोटा, पकड़ना उतना ईमानदार।
चरण 6 / 6
चुनौती — अध्याय की क्विज़
सरल से कठिन — दस प्रश्न, अंतिम वाले जानबूझकर चुनौतीपूर्ण बनाए गए हैं।
क्विज़ देने के लिए साइन इन करें