這篇的目的很直接:幫你判斷在 token 清洗時,
什麼情況該用 `isalnum()`,
什麼情況不該再直覺寫 `[^A-Za-z0-9]`,
避免把中文或其他有效字元誤刪掉。
這份文件專門解釋:
1. `ch.isalnum()` 到底會保留什麼。
2. `re.sub(r”[^A-Za-z0-9]”, “”, token)` 真正會刪掉什麼。
3. 為什麼在目前 Open_Test這種 token 清洗情境下,
`isalnum()` 通常比 ASCII-only regex 更安全。
## 1. 結論先講
如果你的目的只是:
– 去掉 `_`、`#`、`-`、`.` 這類符號
– 保留真正有內容的字元
– 不要誤刪中文
那通常比較適合寫成:
“`python
alnum_only = “”.join(ch for ch in token if ch.isalnum())
“`
而不是:
“`python
re.sub(r”[^A-Za-z0-9]”, “”, token)
“`
原因很簡單:
– `isalnum()` 是 **Unicode-aware**(包含 CJK)
– `[^A-Za-z0-9]` 是 **ASCII-only**(不含 CJK)
CJK 一般就是指:
C = Chinese
J = Japanese
K = Korean
中文通常會說「中日韓」。
這裡先補一個最短版本:
– `ASCII`:字元集很小,主要是英文、數字、常見符號
– `Unicode`:字元集很大,包含中文、韓文、日文、重音字母、全形數字等多語言字元
所以:
– `[^A-Za-z0-9]` 是典型的 ASCII 思維,只認英文半形英數
– `isalnum()` / `isalpha()` 這類 Python 字串方法,通常是用 Unicode 規則判斷
所以 `isalnum()` 會保留中文,ASCII regex 不會。
—
## 2. `isalnum()` 是什麼意思
`isalnum` 是 `alphanumeric` 的縮寫,也就是「字母或數字」。
所以:
“`python
ch.isalnum()
“`
意思是:
這個字元是不是字母或數字?
注意:這裡的「字母或數字」不是只看英文和 `0-9`。
Python 的 `str.isalnum()` 會依 Unicode 規則判斷,因此中文也算。
例如:
print("a".isalnum())
print("7".isalnum())
print("大".isalnum())
print("_".isalnum())
print("#".isalnum())輸出:
![別把中文洗掉:Python `isalnum()` vs `[^A-Za-z0-9]` 含/不含 CJK中日韓 - 儲蓄保險王](https://savingking.com.tw/wp-content/uploads/2026/05/20260525083421_0_59e977.png)
補充:`isalpha()` 也不是只認英文。
Python 的 `str.isalpha()` 同樣是 Unicode-aware,所以中文、韓文、日文這類文字通常也會回傳 `True`。
例如:
print("A".isalpha())
print("大".isalpha())
print("韓".isalpha())
print("한".isalpha())
print("7".isalpha())輸出:
![別把中文洗掉:Python `isalnum()` vs `[^A-Za-z0-9]` 含/不含 CJK中日韓 - 儲蓄保險王](https://savingking.com.tw/wp-content/uploads/2026/05/20260525083710_0_53ba06.png)
`isdigit()` 則只看是不是數字。
所以可以先把三者粗略理解成:
– `isalpha()`:只看是不是字母,中文/日文/韓文也算
– `isdigit()`:只看是不是數字
– `isalnum()`:字母或數字都算,中文/日文/韓文也算
如果只看概念層級,也可以把 `isalnum()` 近似理解成:
“`python
ch.isalpha() or ch.isdigit()
“`
也就是:
– 是字母,就算 `True`
– 是數字,也算 `True`
– 兩者都不是,才是 `False`
最小對照範例:
samples = ["A", "大", "韓", "한", "7", "_"]
for ch in samples:
print(
repr(ch),
"isalpha=", ch.isalpha(),
"isdigit=", ch.isdigit(),
"isalnum=", ch.isalnum(),
)你會看到:
![別把中文洗掉:Python `isalnum()` vs `[^A-Za-z0-9]` 含/不含 CJK中日韓 - 儲蓄保險王](https://savingking.com.tw/wp-content/uploads/2026/05/20260525082752_0_4776cd.png)
– `”大”`、`”韓”`、`”한”` 都是 `isalpha=True`
– `”7″` 是 `isdigit=True`
– `isalnum()` 會把前兩類都算進去
## 3. `[^A-Za-z0-9]` 是什麼意思
這是正則表達式中的一個字元集合。
“`python
[^A-Za-z0-9]
“`
意思是:
匹配任何一個「不是 A-Z、不是 a-z、也不是 0-9」的字元。
所以如果你這樣寫:
“`python
re.sub(r”[^A-Za-z0-9]”, “”, token)
“`
就等於:
把所有不是英文或數字的字元全部刪掉。
這種寫法的最大問題是:
**中文也會被刪掉。**
例如:
import re
print(repr(re.sub(r"[^A-Za-z0-9]", "", "大吉岭") ) )
輸出:
![別把中文洗掉:Python `isalnum()` vs `[^A-Za-z0-9]` 含/不含 CJK中日韓 - 儲蓄保險王](https://savingking.com.tw/wp-content/uploads/2026/05/20260525084051_0_a793f0.png)
也就是整個中文 token 被清空了。
如果你想再往前走一步,手動把常見漢字主區段補進去,也可以寫成:
re.sub(r'[^a-z0-9\u4e00-\u9fff]+', '', token.lower())上面這條是建立在「輸入 token 已先做 `lower()`」的前提下,所以只需要保留 `a-z`。
如果不先轉小寫、而是直接對原始 token 清洗,
為了更符合本篇前面 `[^A-Za-z0-9]` 的比較場景,通常應寫成:
re.sub(r'[^A-Za-z0-9\u4e00-\u9fff]+', '', token)這條規則的意思是:
– 保留英文小寫 `a-z`
– 保留數字 `0-9`
– 保留常見 CJK 主漢字區 `\u4e00-\u9fff`
– 其他字元都刪掉
若使用未先 `lower()` 的版本,則上面第一點要改成:
– 保留英文大小寫 `A-Z` / `a-z`
這比純 `[^A-Za-z0-9]` 更接近「保留中英文數字」的需求,但它仍然和 `isalnum()` 不一樣:
– 它是手動白名單範圍
– `isalnum()` 是 Unicode 類別判斷
– 這條 regex 會保留常見漢字,
但不會自然涵蓋所有 Unicode 字母數字
所以可以把它們想成三個層次:
1. `[^A-Za-z0-9]`:只保留 ASCII 英數
2. `[^A-Za-z0-9\u4e00-\u9fff]+` 或 `[^a-z0-9\u4e00-\u9fff]+`:手動擴成英文 + 數字 + 常見漢字
3. `ch.isalnum()`:依 Unicode 規則保留字母或數字
—
## 4. 兩者直接對比
下面這段最值得直接在 Jupyter 跑:
import re
samples = [
"_###",
"bmc_ip",
"大吉岭",
"測試123",
"abc-123",
"éclair",
]
for token in samples:
keep_by_isalnum = "".join(ch for ch in token if ch.isalnum())
keep_by_regex = re.sub(r"[^A-Za-z0-9]", "", token)
print(f"token={token!r}")
print(f" isalnum -> {keep_by_isalnum!r}")
print(f" regex -> {keep_by_regex!r}")
print()預期重點:
![別把中文洗掉:Python `isalnum()` vs `[^A-Za-z0-9]` 含/不含 CJK中日韓 - 儲蓄保險王](https://savingking.com.tw/wp-content/uploads/2026/05/20260525084438_0_c46ad4.png)
– `_###`:兩者都能清掉
– `bmc_ip`:兩者都能拿掉底線
– `大吉岭`:只有 `isalnum()` 保留中文
– `測試123`:`isalnum()` 保留完整內容,regex 只剩數字
– `éclair`:`isalnum()` 會保留重音字母 `é`,ASCII regex 會把它刪掉;
這不是在示範西班牙文,而是在示範「非 ASCII 的拉丁字母」
這就是兩者最核心的差別。
## 5. 為什麼 `isalnum()` 比較適合目前專案
在 `expand_synonyms` 的情境裡,
我們不是要做「只接受英文 token」的清洗。
我們真正想做的是:
– 把明顯噪聲符號拿掉
– 看 token 去掉符號後,還有沒有實質內容
– 不要誤傷中文或其他有效文字
因此:
alnum_only = "".join(ch for ch in token if ch.isalnum())
if not alnum_only:
# 幾乎可視為 noise / placeholder
...這種邏輯就很合理。
例如:
– `_###` -> `””` -> 可視為噪聲
– `bmc_ip` -> `”bmcip”` -> 不是噪聲
– `大吉岭` -> `”大吉岭”` -> 不是噪聲
如果改用 ASCII-only regex:
– `_###` -> `””`
– `bmc_ip` -> `”bmcip”`
– `大吉岭` -> `””`
最後一筆就會誤判。
## 6. `replace()` 為什麼不是更好的主方案
有時候會想到這樣寫:
token.replace("_", "").replace("#", "").replace("-", "")這不是不能用,但它更適合:
– 你明確知道只要刪幾個固定符號
– 而且未來不太會出現其他新符號
缺點是:
1. 要手動列出每個要刪的字元
2. 很容易漏掉其他符號
3. 寫久了會變成很長一串 `replace()`
4. 表達不出真正規則是「只保留有內容的字元」
反過來說:
"".join(ch for ch in token if ch.isalnum())表達的是:
**只保留字母或數字,其他都忽略。**
這比列舉一堆要刪掉的符號,更接近我們要的規則語意。
—
## 7. 最推薦的包裝方式
如果你覺得這一行太長,
可以包成小函式,會比直接寫 regex 更好懂:
def keep_alnum_chars(token: str) -> str:
return "".join(ch for ch in token if ch.isalnum())主程式就可以寫:
alnum_only = keep_alnum_chars(token)
if not alnum_only:
return True如果名稱想更貼近用途,也可以叫:
def strip_non_alnum_chars(token: str) -> str:
return "".join(ch for ch in token if ch.isalnum())## 8. Jupyter 驗證範例
### 範例 A:最基本對比
import re
samples = ["_###", "bmc_ip", "大吉岭", "測試123", "abc-123"]
for token in samples:
kept = "".join(ch for ch in token if ch.isalnum())
cleaned = re.sub(r"[^A-Za-z0-9]", "", token)
print(repr(token), "-> isalnum:", repr(kept), ", regex:", repr(cleaned))![別把中文洗掉:Python `isalnum()` vs `[^A-Za-z0-9]` 含/不含 CJK中日韓 - 儲蓄保險王](https://savingking.com.tw/wp-content/uploads/2026/05/20260525085326_0_e194ab.png)
### 範例 B:逐字檢查 `isalnum()`
samples = ["_###", "bmc_ip", "大吉岭", "測試123", "abc-123"]
for token in samples:
print("token:", repr(token))
for ch in token:
print(f" {ch!r}: isalnum={ch.isalnum()}")
print()![別把中文洗掉:Python `isalnum()` vs `[^A-Za-z0-9]` 含/不含 CJK中日韓 - 儲蓄保險王](https://savingking.com.tw/wp-content/uploads/2026/05/20260525085501_0_67f8b6.png)
### 範例 C:做成 noise 判斷
def keep_alnum_chars(token: str) -> str:
return "".join(ch for ch in token if ch.isalnum())
def is_noise_placeholder(token: str) -> bool:
alnum_only = keep_alnum_chars(token)
return not alnum_only
samples = [
"_###",
"__##_--",
"bmc_ip",
"大吉岭",
"smc0_ip",
"abc-123",
]
for token in samples:
print(
f"{token!r:12} | "
f"alnum_only={keep_alnum_chars(token)!r:15} | "
f"is_noise={is_noise_placeholder(token)}"
)![別把中文洗掉:Python `isalnum()` vs `[^A-Za-z0-9]` 含/不含 CJK中日韓 - 儲蓄保險王](https://savingking.com.tw/wp-content/uploads/2026/05/20260525085627_0_049c9a.png)
這段會證明:
– `_###`、`__##_–` 這類純符號垃圾會被抓出來
– `大吉岭` 不會被誤判成噪聲
– `bmc_ip`、`smc0_ip` 也不會被誤判
## 9. 需要知道的 caveat
`isalnum()` 比 ASCII regex 安全很多,但它不是「只保留英文 + 半形數字」。
它還可能保留:
– 全形數字
– 重音字母
– 某些 Unicode 數字字元
– 其他語系字母
例如:
samples = ["123", "éclair", "ⅠⅡ", "大吉岭"]
for token in samples:
kept = "".join(ch for ch in token if ch.isalnum())
print(repr(token), "->", repr(kept))![別把中文洗掉:Python `isalnum()` vs `[^A-Za-z0-9]` 含/不含 CJK中日韓 - 儲蓄保險王](https://savingking.com.tw/wp-content/uploads/2026/05/20260525085804_0_566c10.png)
所以正確說法不是:
– `isalnum()` 完美無敵
而是:
– 在「保留有效文字、移除符號垃圾」這個需求上,
`isalnum()` 比 `[^A-Za-z0-9]` 更符合目前專案
再補一個很實務的點:
– `isalnum()` 會保留重音字母,因為像 `é` 這種字元本身就是合法的 Unicode 字母
– 但如果你的需求不是「保留原字形」,而是想把重音拿掉、做更一致的 ASCII-ish 正規化,那就不能只靠 `isalnum()`,而應該另外搭配 `unicodedata`
例如:
import unicodedata
def strip_accents(text: str) -> str:
normalized = unicodedata.normalize("NFKD", text)
return "".join(ch for ch in normalized if not unicodedata.combining(ch))
token = "éclair"
print(strip_accents(token))輸出:
![別把中文洗掉:Python `isalnum()` vs `[^A-Za-z0-9]` 含/不含 CJK中日韓 - 儲蓄保險王](https://savingking.com.tw/wp-content/uploads/2026/05/20260525090102_0_e99304.png)
也就是說:
– `isalnum()` 解的是「哪些字元算有效字母/數字」
– `unicodedata.normalize(…)` + 移除 combining marks 解的是「要不要把重音折平」
這兩件事是不同層的需求,不應混成同一件事。
—
## 10. 一句話總結
– `[^A-Za-z0-9]`:只認 ASCII 英數,中文會被刪掉
– `ch.isalnum()`:Unicode-aware,中文也會保留
– 在目前 `expand_synonyms` / token noise 判斷情境下,優先用 `isalnum()` 比較安全
推薦hahow線上學習python: https://igrape.net/30afN


]*>.*?底下插入一個圖檔.*?</w:p>’, flags = re.DOTALL) ; new_xml, n = pattern.subn(”, xml, count=1)' title='Python正則替換:全面掌握 re.sub 與 re.subn 的差異與實戰 #substitute(替換); . 預設匹配「除\n以外的任意單一字元」; pattern = re.compile(r'<w:p[^>]*>.*?底下插入一個圖檔.*?</w:p>’, flags = re.DOTALL) ; new_xml, n = pattern.subn(”, xml, count=1)' loading='lazy' width=350 height=233 />

![使用 Python 檢驗字符串格式:掌握正則表達式(Regular Expression)的起始^與終止$符號, pattern = r’^GATR[0-9]{4}$’ 使用 Python 檢驗字符串格式:掌握正則表達式(Regular Expression)的起始^與終止$符號, pattern = r’^GATR[0-9]{4}$’](https://i1.wp.com/savingking.com.tw/wp-content/uploads/2024/07/20240712093637_0.png?quality=90&zoom=2&ssl=1&resize=350%2C233)


![Python: pandas.DataFrame (df) 的取值: df [單一字串] 或df [list_of_strings] 選取一個或多個columns; df [切片] 或 df [bool_Series] 選取多個rows #bool_Series長度同rows, index也需要同df.index ,可以使用.equals() 確認: df.index.equals(mask.index) Python: pandas.DataFrame (df) 的取值: df [單一字串] 或df [list_of_strings] 選取一個或多個columns; df [切片] 或 df [bool_Series] 選取多個rows #bool_Series長度同rows, index也需要同df.index ,可以使用.equals() 確認: df.index.equals(mask.index)](https://i2.wp.com/savingking.com.tw/wp-content/uploads/2025/04/20250420212553_0_6fb2c3.png?quality=90&zoom=2&ssl=1&resize=350%2C233)

近期留言