#45247: 解釋一下兩個函數在做什麼 (有雷,想自己動腦的別看)


sam851015@gmail.com (多挖鼻孔有益身心健康)

學校 : 臺中市立惠文高級中學
編號 : 277705
來源 : [123.192.228.253]
最後登入時間 :
2025-03-11 12:39:29
k886. Python駭客題-判斷數字2 | From: [123.192.228.253] | 發表日期 : 2025-02-01 21:28

這題很考驗對 python 內建模組的理解程度,特別是正規表達式

這兩個函數分別導入 re 和 string 兩個內建模組

 

re 是 python 用來做正規表達式 (Regular Expression) 的模組,可以用來分析字串結構。

string 也可以用來分析字串,包含一些常量和函數,功能相對單純一些。

(注意這個 string 和內建的 str 物件是不同的東西,別搞混了! string 是模組 module,str 是內置類型 built-in type)

 

--

 

先講 Dave 的函數,比較簡單

他從 string 裡面 import 了 digits 這個常量,這個常量其實就相當於 "0123456789",它記錄了所有的十進制的阿拉伯數字數字。(要特別強調是因為有十進制但不是阿拉伯數字的文字,不懂的可以參考 k318 的解題報告)

所以他的函數其實是先檢查傳入的參數 s 是否為空字串,若不為空就遍歷該字串,逐一檢查每個字元是否都在 string.digits 內。

 

--

 

Carol 的函數就比較複雜了,他用了正規表達式,如果沒有學過的話基本上可以直接放棄這題,詳細介紹可以參考 wiki,我就不細說它的用法了,真細講下去沒完沒了

 

re.match 這個函數接受三個參數,分別是 pattern, string, flag

  • pattern:規則,這裡要傳入想比較的正規表達式。
  • string: 傳入要分析的目標字串。
  • flag:額外規則,預設為 0,可以控制正規表達式的匹配方式。

re.match 如果有找到符合規則的字串,就會直接返回他找到的第一個字串,若找不到的話會回傳 None;所以這邊才會寫成 is not None。

 

 

一個字一個字解釋這個正規表達式 r'^\d+$' 

  • 最開頭的 r 是 python 中特別的字串表達方式,意思是原始字符串 row string,主要作用是避免反斜線 \ 被當成跳脫字元處理,有興趣可以自己試試看 print(r'\\aaaa')print('\\aaaa') 的區別,這邊要特別弄 row string 是因為在正規表達式中,反斜線 \ 是很重要的符號,有特別的意義,不能當成轉譯字符看待,所以在字串最前面添加 r。接下來被引號包起來的部分就是正規表達式的規則。
  • ^ 意思是一定得從字串的開頭開始匹配,如果開頭就不符合規則,直接返回 None。
  • \d 是指匹配數字字符,也就是我們熟悉的 0123456789,但其他也能用於表達數字的字符它也能接受,例如全形數字 0123456789、孟加拉數字 ০১২৩৪৫৬৭৮৯、泰米爾語數字 ௦௧௨௩௪௫௬௭௮௯ ......等一大堆稀奇古怪的東西。
  • + 的意思是匹配前面的子運算式一次或多次,在這裡和 \d 組合在一起,變成 \d+ ,相當於匹配連續不間斷的數字字符。
  • $ 就是匹配到字串的結束位置,注意對 $ 來說,字串的結束位置並不等於最後一個字符,如果字串結尾是 \n 就會被它自動忽略(沒加這個就不忽略)。(這也是出bug的地方,對,就這一個字,讓程式出bug)

 

用人話來說, r'^\d+$' 意思就是「匹配從字串開頭到字串結尾都是數字字符的字串」

 

Carol 在函數的最後還傳入了一個 flag 參數 re.A,意思是僅匹配在 ASCII 範圍內的字符,一共 128 個,這會修改 \d 對數字的定義,所以那些全形數字、孟加拉數字、......各種神祕語言的數字就都不包含在內了,因為他們都不在 ASCII 的範圍內。

 

看起來似乎很完美,如果字串從頭到尾都是數字才會回傳 True,也把條件篩很清楚,只匹配在 ASCII 內的數字。

 

傳入 "123456"  -> 返回 True

傳入 "123abc" -> 返回 False

傳入 "௪௫௬555" -> 返回 False

傳入 "a1b2c3" -> 返回 False

 

無敵了......吧?

但其實字串並非只能是這些東西,ASCII 的表格裡面有好多奇怪的字符呢!我們也可以傳入

"123\t" -> 返回 False (\t 就是 tab)

"123\r" -> 返回 False (\r 是換行符的一種)

"123\n" -> 返回 True (\n 應該...不用解釋吧,就是我們習慣的換行符)

 

看起來似乎沒問題?又似乎有點不對勁?

回頭看一下題目的要求

 

該作業要求學生實作一個函數 isNumber(s),其輸入參數 s 為一個字串(str),該函數需要回傳一個布林值(bool)用以判斷字串 s 是否為「數字」?若一個字串至少包含一個字元,且之中的每個字元皆為阿拉伯數字 0123456789 其中之一,則我們稱該字串為「數字」。

 

必須要每個字元都是數字才是正確答案,但在 "123\n" 中顯然並非如此,\n 並不是數字,這個字串並非所有字元都是數字,可為什麼 Carol 的函數會判定它為 True?

 

問題就出在它的正規表達式中的 $

前面有提到,$ 是「匹配到字串的結尾為止」,對 $ 來說,字串的結尾就是 \n ,如果遇到 \n 就會忽略它,只判斷在 \n 前面的內容,所以在讀取 "123\n" 時,邏輯是這樣的:

  • 先讀取第一個字元 "1",是數字沒問題
  • 下一個字元是 "2",是數字,且它前面也是數字,確實是連續的數字
  • 下一個字元是 "3",是數字,且它前面也是數字,確實是連續的數字
  • 下一個字元是 "\n",規則提到只需要檢查到「字串的結尾就可以」,\n 就是結尾,所以忽略掉
  • 回頭檢視,前面只剩下 "123" ,確實是從頭到尾都是數字的字符串,所以 re.match 返回 "123"

 

於是就這麼戲劇化的輸出錯誤結果了。

 

--

 

找到 bug 了,那能修嗎?當然能,沒有問題的。

簡單的刪掉 $ 肯定是不行,因為我們要的就是「匹配從字串開頭到字串結尾都是數字字符的字串」,把 $ 刪掉的話就是匹配從字串開頭開始連續的字串,會讓像 "123abc" 這種格是的字串也返回 True ,那就不是我們所期望的了。

 

$ 不能用,但同時我們需要另一個能表達「匹配到字串結尾」且「不會忽略換行符 \n 」的東西替代它,那就可以使用 \Z

\Z 的用途和 $ 差不多,區別在於它不會忽略換行符 \n,所以我們只需要把原本的規則改成 r'^\d+\Z' 就是正確的函數了。

 

 

參考資料:

string | Common string operations | Python 3.13.1 documentation

re | Regular expression operations | Python 3.13.1 documentation

regex101: build, test, and debug regex (這是用來測試正規表達式用的網站)

 

 

 

 
ZeroJudge Forum