目錄
    Add a header to begin generating the table of contents

    實例1(上):猜數字遊戲!Colab 例子談編程設計和處理異常

    實例1(上):猜數字遊戲!Colab 例子談編程設計和處理異常
    Share on facebook
    Share on twitter
    Share on linkedin
    Share on whatsapp
    目錄
      Add a header to begin generating the table of contents

      上一篇 local vs global 函數的教學裡,我們透過 Python 版本的「猜數字遊戲」介紹如何使用 global 這個 Python 的關鍵字。

      除了 global 以外,在這個猜數字遊戲裡其實有更多的 Python 重點。

      以下我們一起透過逐步建立這個猜數字遊戲的編程,淺談一些編程設計和處理異常的方法吧!

      學習大綱

      我們堅信學習編程的最好方法,就是學習如何逐步思考、設計和執行一個問題。在這個猜數字遊戲裡,我們希望重點介紹一下幾點:

      1. 分析問題:如何設計編程
      2. 實質編程:製做 MVP
      3. 處理異常:減低程式出錯機會
      4. 改良程式:簡化架構(在下一篇教學)

      分析問題:如何設計編程

      所有編程的起點都是從分析問題開始。我們先回顧一下這個猜數字遊戲的描述:

      我是廣告 ^o^
      我們先隨機從 1 到 100 選一個數字(假設這個數字是 75)。我先告訴您這個數字是 1 至 100 中間。如果您猜的是 70(少於答案),我再告訴您這個數字在 70 至 100 中間,如此類推,直至您猜中數字。

      當我們看這個描述時,留意以下這些重點:

      題目字眼編程要點
      隨機選一個數字透過 Python 的 library 在一個指定的數字範圍裡生成隨機數字
      如果您猜的是 70使用 < > = 等的邏輯運算(logical operators)比較以下的數字:
      (1) 用家提供的數字(70)
      (2) 答案(75)
      (3) 現在的範圍(0 至 100)
      我再告訴您這個數字在 70 至 100 中間以用家提供的數字更新現在的範圍
      如此類推使用循環(loop)以不斷要求用家提供數字
      直至您猜中數字完結循環(loop)的條件時猜中數字

      透過把我們原本的問題切割成不同的字眼,並連結成編程要點,我們便可以將問題逐一破解了!這就是所謂的分而治之(divide-and-conquer)。

      偽代碼(pseudo-code)

      要完成設計編程,我們可以把以上的編程要點製成偽代碼(pseudo-code),讓我們可以按著這個架構作基礎開始編程:

      #  匯入 library
      ## 生成隨機數字的 library
      
      #  定義常數(constant)及函數(variable)
      ## 範圍(1 至 100)
      ## 隨機生成答案(70)
      
      #  定義循環(loop)
      while True:
        # 要求用家提供一個數字(例如 70)
      
        #  比較用家提供的數字及範圍與答案
        ## 如果 提供的數字 等於 答案 -> 告訴玩家成功了,並離開循環
        ## 如果 提供的數字 小於 答案 -> 將範圍的底設為提供的數字( 0 至 100 修改成 70 至 100)
        ## 如果 提供的數字 大於 答案 -> 將範圍的底設為提供的數字( 0 至 100 修改成 0 至 70)

      在這個偽代碼的例子裡,我們使用編程注解(comments)代替我們將會輸入的實際 Python 代碼。這是為了像填色本一樣,先有了編程輪廓,慢慢填入 Python 代碼。

      在輪廓裡,我們把編程分成數個分部(例如「匯入 library」、「定義循環」),提醒我們有哪些必須的編程元素。

      我是廣告 ^o^

      我們亦應該提供我們預期的輸出(expected output)。譬如我們使用「如果 小於 -> 結果」的字句,總結我們不同情況下的輸出。

      有了這個輪廓,我們便可以開始 Python 編程了!

      實質編程:製做 MVP

      有了我們偽代碼(pseudo-code),我們便可以開死編寫一個編程 MVP。

      MVP 這裡不是指 Most Valuable Player(最有價值球員),而是一個 agile 架構裡的概念:Minimum Viable Product(最簡可行產品)。

      如果要非常粗略地定義 MVP,就是建立一個「半桶水」的方案。我們暫時不管用戶體驗、異常處理、最佳化表現等的問題,只放眼達成最基本、簡單的目的。

      我是廣告 ^o^

      在我們的例子,就是直接簡單地把偽代碼(文字)轉變成 Python 編程,不加任何其他的元素。

      #  匯入 library
      import random
      
      #  定義常數(constant)及函數(variable)
      min_x = 0
      max_x = 100
      answer = random.randint(min_x,max_x)
      
      #  定義循環(loop)
      while True:
        guess = int(input('請從 ' + str(min_x) + ' 到 ' + str(max_x) + ' 猜一個數字:'))
      
        #  比較用家提供的數字及範圍與答案
        if guess == answer:
          print('恭喜您猜對了!答案是 ' + str(answer))
          break
        elif guess < answer:
          min_x = guess
        elif guess > answer:
          max_x = guess

      在以上編程,我們直接把偽代碼的「##」注解轉為 Python 的代碼。留意我們沒有加入任何其他元素,亦沒有考慮任何編程設計的最佳化(optimization),只是純粹製造出 MVP。

      總括而言,這個階段您不應該多想(overthink),只需要把最基本的要求達成。

      處理異常:減低程式出錯機會

      有了我們的 MVP,我們下一個要處理的問題便是用家體驗。

      在編程裡,我們應該假設用家是一個零經驗,完全不懂該怎操作編程的人。特別是一些要求用家提供資料的編程(例如我們要求玩家猜數字),我們不能假設用家只會提供合乎邏輯的資料。

      我是廣告 ^o^

      比方說,我們要求用家提供電郵地址時,不能假設用家總會提供「[email protected]」格式。用家可能會提供「A@gmail.」、「1234」等不是電郵地址的輸入。

      在我們的猜數字遊戲,我們可以預計用家會「犯下」以下的輸入錯誤:

      • 用家輸入的不是數字(例如「abc」)
      • 用家輸入超出範圍的數字(例如在 0 至 100 的時候猜 120)

      我們逐個問題處理吧!

      用家輸入的不是數字

      min_x = 0
      max_x = 100
      answer = 75
      guess = int(input('請從 ' + str(min_x) + ' 到 ' + str(max_x) + ' 猜一個數字:'))
      
      if guess == answer:
          print('恭喜您猜對了!答案是 ' + str(answer))
      elif guess < answer:
          min_x = guess
      elif guess > answer:
          max_x = guess

      從以上抽出來的編程部分可見,如果我們輸入的是「abc」,Python 會出現異常。這是因為我們使用 int() 功能將用家提供的數字轉換成整數(integer)時,如果用家提供的是文字(string),Python 便不能轉換成整數。

      所以,一個簡單的方法便是先檢查用家的 input() 是否正整數(positive integer),然後再把數字轉換成整數數據(int type)。

      我是廣告 ^o^
      min_x = 0
      max_x = 100
      answer = 75
      guess = input('請從 ' + str(min_x) + ' 到 ' + str(max_x) + ' 猜一個數字:')
      
      # 先檢查 guess 是否正整數
      if guess.isnumeric():
        print(guess + ' 是整數')
        guess = int(guess)
      else:
        print(guess +' 不是整數')
      
      if guess == answer:
          print('恭喜您猜對了!答案是 ' + str(answer))
      elif guess < answer:
          min_x = guess
      elif guess > answer:
          max_x = guess

      以上使用 guess.isnumeric() 可以讓我們檢查用家提供的資料是否正整數,而我們在 guess.isnumeric() 是真(True)值時才將 guess 轉為整數,便不會出現異常。

      問題出現了:當我們只是使用 if 來決定資料是否正確時,如果 guess 不是正整數,根據我們的編程順序,我們不會把資料轉換成正整數,以致以上例子中,出現「TypeError: ‘<‘ not supported between instances of ‘str’ and ‘int’」的錯誤。這是因為 Python 不能告訴您「abc」還是 0 較小。

      min_x = 0
      max_x = 100
      answer = 75
      
      # 先檢查 guess 是否正整數,如果不是,要求用家重新輸入正整數,直到 guess 是一個正整數
      while True:
        guess = input('請從 ' + str(min_x) + ' 到 ' + str(max_x) + ' 猜一個數字:')
        if guess.isnumeric():
          guess = int(guess)
          break
      
      if guess == answer:
          print('恭喜您猜對了!答案是 ' + str(answer))
      elif guess < answer:
          min_x = guess
      elif guess > answer:
          max_x = guess

      所以,這裡我們的解決方法便是重複要求用家輸入資料,直到資料可以被轉換成一個正整數。

      以上的例子裡,我們使用一個 while True 循環,並指明程式只有 guess.isnumeric() 是真(True)時,才將 guess 轉為整數類型,並結束這一個循環。

      如果您在 Google Colab 嘗試運行這段代碼,您會發現不論您輸入甚麼文字、小數點,只有您輸入正整數時程式才會停止要求您輸入資料。

      我是廣告 ^o^

      用家輸入超出範圍的數字

      另一個可能性是如果用家在我們希望他輸入 0 至 100 的數字時,輸入了 120,在原本的設計裡,我們會在下一個循環(iteration)要求用家輸入 0 至 120 的數字。

      這是不正確的資料,因為假如用家不斷地輸入比最大值(upper bound)更高的數字,那麼這個遊戲可不能結束。

      而歸根結底,這是因為我們在 MVP 時,更新 min_x 和 max_x 的段落沒有限制 guess 的值,以致用家提供出了範圍(out of bound)的數字時,我們也會更新 min_x 和 max_x。

      min_x = 0
      max_x = 100
      answer = 75
      guess = int(input('請從 ' + str(min_x) + ' 到 ' + str(max_x) + ' 猜一個數字:'))
      
      if guess == answer:
          print('恭喜您猜對了!答案是 ' + str(answer))
      elif guess < answer:
          min_x = guess
      elif guess > answer:
          max_x = guess
      
      guess = int(input('請從 ' + str(min_x) + ' 到 ' + str(max_x) + ' 猜一個數字:'))

      解決這個問題相對簡單:我們只需要在更新 min_x 和 max_x 的條件裡進一步限制 guess 的數值,便可以避免這個問題。

      min_x = 0
      max_x = 100
      answer = 75
      guess = int(input('請從 ' + str(min_x) + ' 到 ' + str(max_x) + ' 猜一個數字:'))
      
      if guess == answer:
          print('恭喜您猜對了!答案是 ' + str(answer))
      # 限制 min_x,max_x 和 guess 的關係
      elif (guess < answer) and (min_x < guess):
          min_x = guess
      elif (guess > answer) and (max_x > guess):
          max_x = guess
      
      guess = int(input('請從 ' + str(min_x) + ' 到 ' + str(max_x) + ' 猜一個數字:'))

      我們在更新 max_x 時,要求 guess 必須大於 max_x,否則我們便不會更新 max_x(例如本來的 max_x 是 100,guess 是 120,我們的 max_x 會保持在 100)。min_x 也是類似的邏輯。

      我是廣告 ^o^

      這樣,我們便能確保用家不會在輸入錯誤的資料時,遊戲變得更加不利。

      異常處理:總結

      #  匯入 library
      import random
      
      #  定義常數(constant)及函數(variable)
      min_x = 0
      max_x = 100
      answer = random.randint(min_x,max_x)
      
      #  定義循環(loop)
      while True:
        # 先檢查 guess 是否正整數,如果不是,要求用家重新輸入正整數,直到 guess 是一個正整數
        while True:
          guess = input('請從 ' + str(min_x) + ' 到 ' + str(max_x) + ' 猜一個數字:')
          if guess.isnumeric():
            guess = int(guess)
            break
        
        #  比較用家提供的數字及範圍與答案
        if guess == answer:
          print('恭喜您猜對了!答案是 ' + str(answer))
          break
        # 限制 min_x,max_x 和 guess 的關係
        elif (guess < answer) and (min_x < guess):
          min_x = guess
        elif (guess > answer) and (max_x > guess):
          max_x = guess

      經過以上的修改後,我們結合了異常處理的方法。我們的編程現在比 MVP 長了些,但我們已經能夠處理很大部分的用戶輸入問題。

      教學完整代碼

      最後送給大家這篇教學的 Google Colab 完整代碼。如果您不懂得使用免安裝又好用的 Google Colab Notebook,記得閱讀這篇教學了:新手 1/3:5 分鐘免安裝學習 Python?Google Colab Notebook 幫緊您!

      總結

      在這編教學裡,我們介紹了如何從構思問題開始,一步步去編寫 Python 程式,達至 MVP 的效果。我們亦延伸到異常處理,令我們的程式能夠變得更可靠,不用擔心用戶的輸入不對。

      下一篇,我們會介紹「改良程式:簡化架構」的步驟,作為一個小編程企劃裡的最後一步。敬請留意!

      我是廣告 ^o^

      下一篇教學:實例1(下):猜數字遊戲!Colab 例子談改良程式和簡化架構

      人氣文章

      快讓我學更多

      small_c_popup.png
      想學習 Python 嗎?
      快來訂閱我們的電子報!