Python Note 7

Exception & Assertion

  • Avoid bad practices in Python exception handling.
  • Always specify an exception type with except, but don’t be too general.
  • Don’t Use Assertions for checking arguments
  • EXamples

    • lookup exception

      def lookups():
          s = [1, 4, 6]
          try:
              item = s[5]
          except LookupError:
              print("Handled IndexError")
      
          d = dict(a=65, b=66, c=67)
          try:
              value = d['x']
          except LookupError:
              print("Handled KeyError")
      
      if __name__ == '__main__':
          lookups()
      
      ## test result 
      ## Handled IndexError
      ## Handled KeyError
      
    • unicode exception

      def unicode_exception():
      try:
          b'\x81'.decode('utf-8')
      except UnicodeError as e:
          print(e)
          print("encoding:", e.encoding)
          print("reason:", e.reason)
          print("object:", e.object)
          print("start:", e.start)
          print("end", e.end)
      
      if __name__ == '__main__':
          unicode_exception()
      
      ## test result
      ## 'utf-8' codec can't decode byte 0x81 in position 0: invalid start byte
      ## encoding: utf-8
      ## reason: invalid start byte
      ## object: b'\x81'
      ## start: 0
      ## end 1        
      
    • customized exception with parameters

      import sys
      import io
      
      
      class TriangleError(Exception):
      
          def __init__(self, text, sides):
              super().__init__(text)
              self._sides = tuple(sides)
      
          @property
          def sides(self):
              return self._sides
      
          def __str__(self):
              return "'{}' for sides {}".format(self.args[0], self._sides)
      
          def __repr__(self):
              return "TriangleError({!r}, {!r}".format(self.args[0], self._sides)
      
      
      def triangle_area(a, b, c):
          sides = sorted((a, b, c))
          if sides[2] > sides[0] + sides[1]:
              raise TriangleError("Illegal triangle", sides)
          p = (a + b + c) / 2
          a = math.sqrt(p * (p - a) * (p - b) * (p - c))
          return a
      
      
      def triangle_exception():
          try:
              a = triangle_area(3, 4, 10)
              print(a)
          except TriangleError as e:
              try:
                  print(e, file=sys.stdin)
              except io.UnsupportedOperation as f:
                  print(e)
                  print(f)
                  print(f.__context__ is e)
      
      if __name__ == '__main__':
          triangle_exception()
      
      ## test result
      ## 'Illegal triangle' for sides (3, 4, 10)
      ## not writable
      ## True        
      
    • chaining & trackback

      import math
      import traceback
      
      class InclinationError(Exception):
          pass
      
      
      def inclination(dx, dy):
          try:
              return math.degrees(math.atan(dy / dx))
          except ZeroDivisionError as e:
              raise InclinationError("Slope cannot be vertical") from e
      
      
      def traceback_inclination():
          try:
              inclination(0, 5)
          except InclinationError as e:
              print(e.__traceback__)
              traceback.print_tb(e.__traceback__)
              s = traceback.format_tb(e.__traceback__)
              print(s)
      
      if __name__ == '__main__':
          traceback_inclination()
          print("Done.")
      
      ## test result
      
      #<traceback object at 0x000000BE3B4F5108>
      ##  File "/path/to/your_project/__main__.py", line 190, in traceback_inclination
      ##    inclination(0, 5)
      ##  File "/path/to/your_project/__main__.py", line 185, in inclination
      ##    raise InclinationError("Slope cannot be vertical") from e
      #['  File "/path/to/your_project/__main__.py", line 190, 
      ## in traceback_inclination\n    
      ## inclination(0,## 5)\n',
      ##  '  File "/path/to/your_project/__main__.py", line 185, 
      ##  in inclination\n    
      ##  raise 
      ## InclinationError("Slope cannot be vertical") from e\n']
      
      
    • assertion & exception

      from pprint import pprint as pp
      
      def wrap(text, line_length):
          """Wrap a string to a specified line length.
      
          Args:
              text: The string to wrap.
              line_length: The line length in characters.
      
          Returns:
              A wrapped string.
      
          Raises:
              ValueError: If line_length is not positive.
          """
          if line_length < 1:
              raise ValueError("line_length {} is not positive".format(line_length))
      
          words = text.split()
      
          if max(map(len, words)) > line_length:
              raise ValueError("line_length must be at least as long as the longest word")
      
          lines_of_words = []
          current_line_length = line_length
          for word in words:
              if current_line_length + len(word) > line_length:
                  lines_of_words.append([])  ## new line
                  current_line_length = 0
              lines_of_words[-1].append(word)
              current_line_length += len(word) + len(' ')
          lines = [' '.join(line_of_words) for line_of_words in lines_of_words]
          result = '\n'.join(lines)
          assert all(len(line) <= line_length for line in result.splitlines())
          return result
      
      wealth_of_nations = "The annual labour of every nation is the fund which or" \
      "iginally supplies it with all the necessaries and conveniencies of life wh" \
      "ich it annually consumes, and which consist always either in the immediate" \
      " produce of that labour, or in what is purchased with that produce from ot" \
      "her nations. According, therefore, as this produce, or what is purchased w" \
      "ith it, bears a greater or smaller proportion to the number of those who a" \
      "re to consume it, the nation will be better or worse supplied with all the" \
      " necessaries and conveniencies for which it has occasion."
      
      if __name__ == "__main__":
          pp(wrap( wealth_of_nations, 40))
      
      ## test result
      ## ('The annual labour of every nation is the\n'
      ##  'fund which originally supplies it with\n'
      ##  'all the necessaries and conveniencies of\n'
      ##  'life which it annually consumes, and\n'
      ##  'which consist always either in the\n'
      ##  'immediate produce of that labour, or in\n'
      ##  'what is purchased with that produce from\n'
      ##  'other nations. According, therefore, as\n'
      ##  'this produce, or what is purchased with\n'
      ##  'it, bears a greater or smaller\n'
      ##  'proportion to the number of those who\n'
      ##  'are to consume it, the nation will be\n'
      ##  'better or worse supplied with all the\n'
      ##  'necessaries and conveniencies for which\n'
      ##  'it has occasion.')