Python 的切片捷徑 s[::-1] 和 ''.join(reversed(s)) 都會在字碼點或 UTF-16 字碼單位的層級上走訪字串,因此當字串中含有以代理對儲存的表情符號、由基底字母加上組合附加符號組成的字元,或是用零寬度連接符串接的家庭表情符號時,一旦反向執行就會被拆散。以純 ASCII 字串 (例如 "hello") 來說,切片會回傳 "olleh",這正是多數教學想教你的答案,在英文文字的情況下故事就到這裡結束。但只要字串中出現任何超出基本多文種平面的內容,或是由多個字碼點組成的字元時,麻煩就隨之而來。正確的反向操作必須以使用者可見的字素簇為單位來走訪,而不是原始的字碼單位,而 Python 的標準函式庫並沒有內建字素分段器。如果你只是要在自己控制的腳本中翻轉一段簡短的 ASCII 標籤,切片技巧就夠用了。但只要你的文字包含表情符號、帶附加符號的拉丁字母,或是含有 CRLF 換行符號的貼上內容,你就需要一個能反向處理完整字素簇、而不是原始字碼單位的工具。這正是具有 Unicode 感知能力的 文字反向器 的設計目的,而且你完全不需要自己寫任何一行 Python 程式碼就能完成。

how to reverse text in python
如何在不寫程式碼的情況下於 Python 中反向文字

Python 內建的反向方法實際上做了什麼

兩種常見的 Python 作法都簡單到足以記住,這也是它們經常出現在教學、訪談準備和速查表的原因。切片形式會接受起始索引、結束索引和步進值,以 −1 為步進就會由後往前走訪字串:

reversed_text = original[::-1]

函式形式則會反向一個序列並回傳一個迭代器,接著由 str.join 將其重新組合成字串:

reversed_text = ''.join(reversed(original))

兩種版本的時間複雜度都是 O(n),並會產生一個全新的字串物件,原始字串保持不變,這通常正是你想要的行為。以 "hello" 這類字串來說,結果就是 "olleh",在 ASCII 的情況下這確實就是全部的故事。一旦字串中含有超出基本多文種平面的內容,或是由多個字碼點組成的字元時,麻煩就開始了。

CPython 的內部字串表示法是由 UTF-16 字碼單位所組成。像 😀 (U+1F600) 這類字元會以代理對的形式儲存:由兩個字碼單位組成,只有合在一起才有意義。切片和迭代器都會把這兩個字碼單位當成獨立的項目來處理。反向之後,低位代理會被排到高位代理之前,這在瀏覽器或終端機嘗試呈現之前,技術上已經是錯位的狀態。有些解碼器較為寬鬆,會自動把代理重新配對;有些則會顯示替換字元,或把這對代理保留為兩個無法辨讀的字元。

為什麼天真的反向操作會損壞表情符號與附加符號

Unicode 標準基本上不會把一個顯示字元視為一個字碼點。文字中有幾個常見的類別是由多個必須一起行動的字碼點所組成:

  • 組合標記。字母 é 可以用單一字碼點 U+00E9 表示,也可以用 U+0065 加上 U+0301 的組合來表示。反向這組組合會把附加符號放到字母之前,只有在某些渲染器會自動校正的情況下,視覺上看起來才一樣;其他渲染器則不會。
  • 膚色修飾符。像 👋🏽 這類表情符號是由一個基底表情符號和一個 Fitzpatrick 修飾符組成。反向之後,你會得到一個獨立的修飾符,後面跟著一隻空手。
  • 零寬度連接符 (ZWJ) 序列。像 👨‍👩‍👧‍👦 這類家庭或職業表情符號,是由好幾個表情符號以 U+200D 連接而成。反向操作會把家庭成員散落在字串各處。
  • 國旗配對。國旗表情符號由兩個地區指標字碼點組成;把它們反過來會變成另一個國家,或變成兩個不相關的字母。
  • 變體選擇器與標籤序列。有些象形文字必須在尾端帶有 U+FE0F 或 U+E0xxx 標籤時,才會以彩色或文字形式呈現,這些都很容易在粗糙的反向操作中遺失。
  • CRLF 換行。Windows 文字經常使用兩個字碼單位的序列 CR LF。粗糙的反向會把它們拆成 LF 然後 CR,大多數系統會再把它們標準化回兩個換行,但在二進位記錄檔或版本控制的差異比較中,仍可能看到這個痕跡。

解決方法收錄在 Unicode 標準附錄 #29:文字分段 中,它定義了使用者感知字元 (字素簇) 的邊界。正確的反向操作必須以字素簇為單位來走訪,而不是字碼點或字碼單位。截至最新穩定版本,Python 的標準函式庫並沒有內建字素分段器,因此多數以 Python 撰寫的替代方案會求助於第三方的 grapheme 套件,或退而求其次使用能涵蓋常見情況、但會漏掉較新序列的正規表示式規則。如果你想深入了解這個問題中與表情符號相關的部分,可以參考這份關於在不破壞表情符號的情況下反向文字的指南。

如何在不寫 Python 的情況下於瀏覽器中反向文字

如果你只需要一段文字的反向副本,完全不需要寫腳本。Lizely 上的文字反向器會在你的瀏覽器中執行反向操作,因此不會將任何資料傳送到伺服器。三步驟的工作流程如下:

  1. 在文字方塊中輸入或貼上最多 100,000 個字元。上限是以 UTF-16 字碼單位計算,因此充滿表情符號的字串會比拉丁文字字串更快速地耗盡額度。超過上限會明確顯示錯誤,而不是默默縮短你的貼上內容;空白的輸入也會以同類型訊息被拒絕。
  2. 選擇符合你需求的反向模式。針對 Unicode 字素簇選擇 字元,針對非空白連續區段選擇 單字,針對邏輯行順序選擇 行。每種模式都會把你未要求翻轉的部分完整保留在原位。
  3. 選擇反向文字,將產生的順序與原始內容核對,若看起來正確就複製結果。編輯輸入或切換模式會清除先前的輸出與複製確認,因此舊的結果不會與新的結果混淆。

格式錯誤的 UTF-16 代理序列會在任何反向操作執行之前就被拒絕,這能防止孤立的代理字元污染輸出。一旦輸入通過驗證,字元模式會呼叫瀏覽器的 Intl.Segmenter 來判斷字素邊界,並把每個段落當作整體反向。不支援 Intl.Segmenter 的瀏覽器則會退回到一組保守的本地規則,把完整的字碼點保持在一起,並把常見的組合標記、變體選擇器、表情符號修飾符、ZWJ 序列、地區指標國旗配對、表情符號標籤序列以及 CRLF 配對都視為單一單位。這個備援機制比 Array.from(text).reverse() 更安全,但如果你處理的是罕見文字系統或新近指定的字元,請使用完整支援 Intl.Segmenter 的最新瀏覽器,並自行驗證輸出。

選擇字元、單字或行模式

這三種模式解決不同的問題,選錯模式是「輸出看起來不對」這類抱怨最常見的原因。下表摘要說明每種模式會反向哪些內容,以及會保留哪些內容。

模式 反向的內容 保留的內容 最適合的情境
字元 使用者可見的字素簇 簇內的所有內容;不會跨越邊界拆開 反向一句句子、標籤或貼上的片段,內容中可能包含表情符號、帶附加符號的字母或混合文字系統
單字 非空白的連續區段 空格、Tab 及其他空白字元維持在原本位置 翻轉句子或清單中詞組的順序,但不動到附加在每個詞組上的標點
行 邏輯行的內容 CRLF、LF 與 CR 換行符以原本形式保留,並在輸入有結尾換行時一併保留 反向清單、記錄檔節錄、檢查清單或記錄區塊,同時保留基本的文字檔結構

單字模式採用刻意嚴格的定義:單字是指任何連續的非空白字元區段,而附加在詞組上的標點會留在該詞組上。它不會做特定語言的斷詞、不會查字典、不會做詞幹化、不會進行雙向重排,也不會去除標點。如果你在單字模式中貼上 "hello, world!",會得到 "world! hello," — 逗號會黏在 "hello" 上,驚嘆號會黏在 "world" 上。行模式會反轉行的順序,但不會反轉每一行內部的字元;如果你兩者都需要,請先執行字元模式,再自行把結果重新排成行。如果想更深入了解詞序這個情境,請參考這份關於在任何段落中反轉詞序的指南。

哪些留在本地、哪些會離開頁面

反向操作是一種呈現上的轉換,不是加密、翻譯、轉寫,也不是任何形式的混淆。任何能看到輸出的人都能讀懂內容;這個工具無法保護密碼、權杖、編碼資料或任何其他敏感資訊,也不會移除不可見字元,或將組合式與分解式 Unicode 形式標準化。輸入中的雙向控制字元會原封不動地保留到輸出中,這是正確的行為,但如果你貼上由右至左的文字而顯示結果與預期不同,值得留意這一點。空白的輸入以及超過 100,000 字元上限的輸入,都會明確產生錯誤,而不是產生空結果或悄悄截斷。

複製功能取決於瀏覽器的剪貼簿權限。如果你拒絕寫入,工具會回報複製被拒,而不是假裝動作成功。你貼上的內容不會被上傳到 Lizely 或任何其他服務 — 每一步都在當前頁面狀態中完成 — 因此來源資料會一直保留在輸入框中,直到你編輯或清除為止,而產生的文字只要重新載入分頁就會消失。

當反轉是用錯的工具

如果你真正的目標是在 PowerPoint、Excel、Google Docs、Photoshop 或 Canva 等編輯器中翻轉文字,無程式碼的瀏覽器工作流程仍然是最快的答案,但具體的貼上並翻轉步驟差異頗大,值得另開文章說明;前面連結的反轉單字指南詳細介紹了試算表的情況。如果你的目標是檢查字串是否為回文,手動反轉再比較通常比使用專門的回文檢查工具來得慢,因為回文檢查工具可以為你正規化大小寫、空白與標點符號。

如果你想打亂每一行而不是翻轉它們,公平的隨機排序是另一種操作。如果根本的問題是「這個字串有多少個字元」而不是「字串反轉後是什麼」,即時計數會是更快的答案。至於其他情況,安全的做法是:貼上文字,選擇符合你要處理單位的模式,產生結果,並在把輸出貼到程式碼、識別碼、記錄檔或聊天對話之前,與原始來源進行核對。反轉只會保留該工具被設計來保留的內容;它不會承諾更多,也不會假裝自己是清理器、編碼器或翻譯器。