#set document(title: "14.5 Raising exceptions", author: "OpenStax / XYZ Homework") #set page(width: 8.5in, height: auto, margin: 1in) #import "@preview/cetz:0.5.2" #set text(font: ("STIX Two Text", "Libertinus Serif", "New Computer Modern"), size: 10.5pt, lang: "en") #show math.equation: set text(font: ("STIX Two Math", "New Computer Modern Math")) #set par(justify: true, leading: 0.62em, spacing: 0.9em) #set enum(spacing: 1.1em) // room between list items so tall inline fractions don't collide #set list(spacing: 1.1em) #set table(stroke: 0.5pt + rgb("#c7ccd3")) #let BLUE = rgb("#183B6F") // brand navy — section bars + example/solution labels (white on navy 11.09:1) #let ORANGE = rgb("#A94509") // brand primary-700 — AA-safe deep orange for TEXT (5.93:1 on white; raw brand #F37021 is 2.94:1 and must never carry text) #let RED = rgb("#DC2626") // brand error-600 #let GREEN = rgb("#059669") // brand success-600 (decoration only; small green text uses green-text #007942) #show heading.where(level: 1): it => block(width: 100%, above: 0pt, below: 16pt, fill: gradient.linear(BLUE, rgb("#2C5AA0")), inset: (x: 14pt, y: 12pt), radius: 3pt, text(fill: white, weight: "bold", size: 19pt, it.body)) #show heading.where(level: 2): it => block(width: 100%, above: 18pt, below: 10pt, fill: BLUE, inset: (x: 10pt, y: 6pt), radius: 2pt, text(fill: white, weight: "bold", size: 12pt, it.body)) #show heading.where(level: 3): it => text(fill: ORANGE, weight: "bold", size: 12.5pt, it.body) #show heading.where(level: 4): it => text(fill: BLUE, weight: "bold", size: 10.5pt, it.body) #let examplebox(label, title, body) = block(width: 100%, breakable: true, fill: rgb("#EFF1F5"), stroke: 0.5pt + rgb("#CFDDF0"), radius: 4pt, inset: 10pt, above: 12pt, below: 12pt)[ #block(below: 6pt)[#box(fill: BLUE, inset: (x: 6pt, y: 2pt), radius: 2pt, text(fill: white, weight: "bold", size: 8.5pt, label)) #h(0.4em) #strong[#title]] #body] // rail = decorative left rule (raw brand token); labelcolor = AA-safe label text shade #let notebox(label, rail, labelcolor, tint, body) = block(width: 100%, breakable: true, fill: tint, stroke: (left: 3pt + rail), inset: (left: 10pt, rest: 8pt), radius: (right: 4pt), above: 11pt, below: 11pt)[ #text(fill: labelcolor, weight: "bold", size: 7.5pt, tracking: 0.5pt)[#upper(label)] #linebreak() #body] #let solutionbox(body) = block(above: 4pt, below: 8pt)[ #text(fill: BLUE, weight: "bold", size: 8.5pt)[Solution] #linebreak() #body] #let figph(msg) = block(width: 100%, height: 60pt, fill: rgb("#f6f7f9"), stroke: (paint: rgb("#c7ccd3"), dash: "dashed"), radius: 4pt, inset: 10pt)[ #align(center + horizon, text(fill: rgb("#889"), style: "italic", size: 9pt, msg))] // Standardize inlined figure sizes: measure the natural CeTZ canvas, then scale to a // consistent envelope (aspect-aware; see build_typst.py FIG_* constants). Unlike the // print preamble, dimensions are FLOORED: in an editor a user can trim a figure to a // degenerate 1-D shape (a bare line), and w/h or tw/w would then divide by zero. #let _STD_W = 3.5 #let _WIDE_W = 5.6 #let _MAX_H = 3.4 #let _ASPECT_WIDE = 2.2 #let _UPSCALE_MAX = 1.15 #let stdfig(body) = context { let m = measure(body) let w = calc.max(m.width / 1in, 0.01) let h = calc.max(m.height / 1in, 0.01) let tw = if w / h > _ASPECT_WIDE { _WIDE_W } else { _STD_W } let s = calc.min(tw / w, _MAX_H / h, _UPSCALE_MAX) align(center, box(scale(x: s * 100%, y: s * 100%, reflow: true, body))) } #show figure: set block(breakable: false) #set figure(gap: 8pt) #show figure.caption: set text(size: 8.5pt, fill: rgb("#555")) == 14.5#h(0.6em)Raising exceptions === Learning objectives By the end of this section you should be able to - Use a raise statement to indicate that user input is invalid. - Explain the flow of execution when an exception is raised. === The raise statement A program can raise an exception when an error is detected. Raising an exception forces a program to deal with the error. If the exception is not handled using try and except, the program displays an error message and terminates. #examplebox("Example 1")[Invalid pizza size][ The following class represents a pizza for sale. The SIZES dictionary maps an abbreviation to a size's name and price. Line 11 checks if the size received by the constructor is in the dictionary. Line 12 raises a ValueError with specific error message. class Pizza:     SIZES = {         "S": ("Small", 5.99),         "M": ("Medium", 7.99),         "L": ("Large", 9.99),         "XL": ("X-Large", 11.99),     }     def \_\_init\_\_(self, size):         if size not in Pizza.SIZES:           raise ValueError(f"Unknown size '{size}'")         self.size = size     def \_\_str\_\_(self):         name, price = Pizza.SIZES\[self.size\]         return f"{name} pizza for \${price}" if \_\_name\_\_ == "\_\_main\_\_":     p1 = Pizza("L")     print(p1)     p2 = Pizza("Z")     print(p2) Large pizza for \$9.99 Traceback (most recent call last):   File "pizza.py", line 23, in \     p2 = Pizza("Z")   File "pizza.py", line 12, in \_\_init\_\_     raise ValueError(f"Unknown size '{size}'") ValueError: Unknown size 'Z' Raising an exception in a constructor prevents an invalid object from being constructed. In the example, p1 is constructed but p2 is not constructed. ] #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ #emph[Predicting output with raise] For each code snippet, what is the output? ] #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ #emph[Language objects] The file top27.csv is based on Wikipedia's #link("https://openstax.org/r/100languages")[list of languages by number of native speakers]. A linguist would like to work with this data in an object-oriented way and has already defined a Language class. However, the code is not working correctly because of errors in the CSV file. (The CSV program cannot be changed because the program's purpose is to detect these kinds of errors.) Your task is to modify the constructor to raise a ValueError in the following situations: - If the line does not have exactly four values (separated by commas). - If the number of speakers is not a positive floating point number. Note: The provided top27.csv includes mistakes for English, Korean, and French. Your program should raise a ValueError only for these languages. No other errors (including IndexError) should be raised. class Language: def \_\_init\_\_(self, line): data = line.split(',') self.name = data\[0\] self.speakers = float(data\[1\]) self.family = data\[2\] self.branch = data\[3\] def \_\_str\_\_(self): return f"{self.name} ({self.speakers} million)" if \_\_name\_\_ == "\_\_main\_\_": valid = \[\] data = open("top27.csv") data.readline() \# Skip first line for line in data: try: lang = Language(line) valid.append(lang) except ValueError as err: print(err) print(line) for lang in valid: print(lang) ] #notebox("Note", rgb("#8a94a6"), rgb("#556666"), rgb("#f7f8fa"))[ #emph[Looking up keys] When looking up a value in a dictionary, a key has to match exactly. Ex: Given fruit = {"apple": 1, "banana": 2}, the expression fruit\["Apple"\] raises a KeyError because "Apple" is not the same as "apple". Likewise, fruit\["NANA"\] raises a KeyError because "nana" does not exactly match "banana". Implement the lookup() function. This function tries to find a key in the dictionary that matches the search term. A key matches if the search term is a substring of the key, ignoring case. Ex: The key "banana" matches the search term "NANA". If no matching keys are found, lookup() should raise a KeyError with the message "search term not found". If multiple matches are found, lookup() should raise a KeyError with the message "multiple keys found". Otherwise, lookup() should return both the key and the value for the matching item in the dictionary. Given the books.csv file, the output should be: Found: ('Pride and Prejudice', 9780141439518) Found: ('The Hobbit', 9780547928227) Error: search term not found Error: multiple keys found def lookup(dictionary, search\_term): \# TODO find the matching key return key, dictionary\[key\] if \_\_name\_\_ == "\_\_main\_\_": \# Read books from the file books = {} for line in open("books.csv"): title, isbn = line.split(",") books\[title\] = int(isbn) \# Try looking up some books for term in \["PREJ", "hob", "1984", "The"\]: try: result = lookup(books, term) print("Found:", result) except KeyError as err: print("Error:", err) ]