天天看點

你不知道的 Python RawString 和 open檔案的newline換行符,遇坑折騰半天終于搞定,總結此文!

背景

一次工作中,我需要完成某個檔案的字元串替換。

需求是這樣的:檔案A有個占位符,需要利用Python3,把占位符替換成檔案B的内容。檔案都不大,可以一次性讀到記憶體處理。

我想,這不是簡單的​

​open​

​​ ​

​read​

​​ ​

​replace​

​​ ​

​write​

​就搞定了嘛?

結果,還真有點麻煩!

思路

  1. 全量讀取檔案A,儲存到變量templace
  2. 全量讀取檔案B,儲存到變量text
  3. 利用python的​

    ​re.sub​

    ​實作正則替換,儲存到新變量result
  4. 把變量result内容寫入檔案A
with open('A', encoding='utf8') as f:
  template = f.read()
with open('B', encoding='utf8') as f:
  text = f.read()
result = re.sub(r'占位辨別符', text, template, 1)
with open('A', 'w', encoding='utf8') as      

遇到的問題

檔案B内有換行符,也有字元串​

​\n​

​​,按上文的方式處理後,所有的字元串​

​\n​

​都變成了換行符!

舉個例子,template是​

​我是:{}​

​​(其中​

​{}​

​就是占位符),text是下面的文本:

哈哈
哈哈\n哈哈      

替換後,如下圖所示:

你不知道的 Python RawString 和 open檔案的newline換行符,遇坑折騰半天終于搞定,總結此文!

可以看到,當我列印​

​re.sub​

​​結果時,所有的​

​\n​

​​都變成了換行符,字元串​

​\n​

​消失了!

這的确令人煩躁,本來五分鐘可以搞定,結果要花多餘的時間處理這個問題。如果你學會了本文,以後都不用再去費腦筋了~

思考過程

一開始遇到這個問題,是在寫入檔案後發現的,是以并沒定位的這麼準确,當時跟換行符相關的,我懷疑了以下方面:

  1. 字元串定義沒有使用 Raw String(例如​

    ​r'xxx'​

    ​這種方式)。
  2. 正則替換出了問題。
  3. 寫入檔案時,​

    ​newline​

    ​參數導緻。

如果我們能把這3個問題全都弄清楚,以後定位就非常快了!

Raw String

Python中,如果字元串常量的定義前加了個​

​r​

​,就表示 Raw String 原始字元串。

Raw String 特點在于,字元串常量裡的​

​\​

​将不具有轉義作用,它僅僅代表它自己。

例如,你定義個普通字元串​

​"\n"​

​,這個字元串長度其實是1,它隻包含了1個換行符,對應的 ASCII 是10。

如果你定義了原始字元串​

​"\n"​

​​,這個字元串長度就是2,它包含了字元​

​\​

​​和字元​

​n​

​。

如果字元串沒轉義字元,那麼 Raw String 跟普通 String 完全一緻

轉義字元有這些:

你不知道的 Python RawString 和 open檔案的newline換行符,遇坑折騰半天終于搞定,總結此文!

也就是說​

​r'\haha'​

​​跟​

​'\haha'​

​​是完全一緻的,因為​

​\h​

​​不是轉義字元,是以這種情況下,沒必要加​

​r​

​。

你不知道的 Python RawString 和 open檔案的newline換行符,遇坑折騰半天終于搞定,總結此文!

誤區:注意引号問題

有一個令人疑惑的點:理論上講,​

​r'\'​

​​應該就是​

​'\\'​

​​,但是當你使用​

​r'\'​

​時,Python會報錯。

這是因為Python在編譯時,讀取字元串時,如果字元串以單引号開頭,遇到​

​\'​

​​後,不論你是不是Raw String,都會繼續認為是字元串,不會把​

​'​

​當作結束符。估計是一個曆史遺留問題。我們隻能接受現實。但是其實編譯後,​

​\​

​​和​

​'​

​又變成了2個字元。

如何證明呢?你給字元後面加個空格,發現它們是相等的:​

​r'\ '​

​​和​

​'\\ '​

​​。但是單獨的字元​

​r'\'​

​就報錯了。

你不知道的 Python RawString 和 open檔案的newline換行符,遇坑折騰半天終于搞定,總結此文!

但是這種情況隻有字元串末尾是奇數個​

​\​

​​時(例如​

​r'\'​

​​或​

​r"\"​

​​)才會發生,如果字元串最後一個字元不是​

​\​

​​,或者字元串最後是連續偶數個​

​\​

​​,是沒問題的,例如​

​r"\\"​

​可以被合法定義。

你不知道的 Python RawString 和 open檔案的newline換行符,遇坑折騰半天終于搞定,總結此文!

啟發

定義字元串時,如果你是這麼定義:​

​"哈哈\n哈哈"​

​,那麼這個字元串長度是5,包含了1個換行符。

如果你是這麼定義:​

​r"哈哈\n哈哈"​

​​,那麼這個字元串長度是6,不包含換行符,包含字元​

​\​

​​和​

​n​

​。

同樣,當你寫入檔案時,如果是​

​f.write('\n')​

​​,就表明寫入了換行符,但如果是​

​f.write(r'\n')​

​​,就表明寫入了字元串​

​"\n"​

​。

在正規表達式中,我們通常用 Raw String,但我們依然使用​

​\n​

​表達換行,是因為正則文法中,會把2個連續的字元​

​\​

​和​

​n​

​當作換行符,這是正規表達式庫做的轉義,而非定義字元串時Python做的轉義。

正則替換的問題

這是導緻本文問題的根本原因。使用​

​re.sub​

​​時,所有的字元串​

​r"\n"​

​都被當作了換行符。

怎麼辦呢?

隻要我們替換前,把原始檔案對應的字元串的​

​r"\n"​

​​都改為​

​r"\\n"​

​​,手動多加了一次轉義符,那麼​

​re.sub​

​​時,就不會把​

​r"\n"​

​​當作一個整體改成換行符了,反而會把​

​r"\\"​

​​當作一個整體,替換為字元​

​\​

​​。這樣​

​r"\n"​

​字元串就保留下來了!當然,其它轉義字元,也統統保留下來了。這就是正确的解法了。

open 檔案的 newline 參數

with open(filename, 'r', newline=None) as      

這個主要是因為不同作業系統的換行符不同,是以有了這個參數。Windows 是 CRLF 即 ​

​\r\n​

​​,Unix 是 LF 即​

​\n​

​​,舊版 Macintosh 是 CR 即​

​\r​

​。

你不知道的 Python RawString 和 open檔案的newline換行符,遇坑折騰半天終于搞定,總結此文!

通常情況下,我們不需要加這個參數,Python 會自動為我們做這些事情:

  • 讀取檔案時,自動把文本中的各種換行符統一轉換為​

    ​"\n"​

    ​。
  • 寫入檔案時,根據目前的作業系統,自動把​

    ​"\n"​

    ​​轉換為對應的換行符,通過​

    ​os.linesep​

    ​可以檢視目前作業系統換行符。

當然,你也可以主動設定 newline 參數:

  • 讀取檔案時,如果 newline 是空字元串​

    ​''​

    ​,則Python不會做任何自動轉換,讀到什麼就是什麼。
  • 讀取檔案時,如果 newline 是非空字元串,則Python會把換行符轉化為這個非空字元串,例如你可以指定為​

    ​'\r'​

    ​​或​

    ​'\r\n'​

    ​或其它。
  • 寫入檔案時,如果 newline 是空字元串​

    ​''​

    ​,則Python不會做任何自動轉換,現在換行符是什麼,就寫入什麼。
  • 寫入檔案時,如果 newline 是非空字元串,則Python會把​

    ​\n​

    ​​轉化為這個非空字元串,例如你可以指定為​

    ​'\r'​

    ​​或​

    ​'\r\n'​

    ​或其它。

注意,​

​newline​

​​ 參數隻對文本檔案有效,如果是二進制讀寫,​

​newline​

​ 是無用的。

寫在最後