DEV Community

codemee
codemee

Posted on

Python csv 模組的檔案開檔時為什麼要指定 newline 參數為 ''?

Python 中使用 csv 模組寫入 CSV 格式資料時,都會要你在開檔時傳入 '' 給 newline 參數,避免多出空白行,這篇文章就來找出原因,才不會搞不清楚為什麼?

寫入檔案時對換行的操作

當你使用 write 或是 writelines 寫入到檔案時,會進行換行字元的處理

newline 換行處理
None(預設值) 把欲寫入檔案文字中的 '\n' 換成系統預設的換行字元,Windows 上就是 '\r\n',Linux 上就是 '\n'
空字串 '' 或 '\n' 不轉換,保留 '\n' 字元
'\r' 或 '\r\n' 把 '\n' 換成指定字串

以底下的程式碼為例:

with open('file.txt', 'w') as file:
    file.write('Hello, world!\n')
    file.write('This is a test file.')
Enter fullscreen mode Exit fullscreen mode

在 Windows 平台執行後以 16 進位查看:

Format-Hex -Path file.txt

   Label: C:\Users\meebo\code\python\test_py3.13\file.txt

          Offset Bytes                                           Ascii
                 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
          ------ ----------------------------------------------- -----
0000000000000000 48 65 6C 6C 6F 2C 20 77 6F 72 6C 64 21 0D 0A 54 Hello, world!��T
0000000000000010 68 69 73 20 69 73 20 61 20 74 65 73 74 20 66 69 his is a test fi
0000000000000020 6C 65 2E                                        le.
Enter fullscreen mode Exit fullscreen mode

可以明確看到 '\n' 被換成 '\x0D\x0A,也就是 '\r\n'。但如果是在 linux 平台,則會看到:

hexdump -C file.txt
00000000  48 65 6c 6c 6f 2c 20 77  6f 72 6c 64 21 0a 54 68  |Hello, world!.Th|
00000010  69 73 20 69 73 20 61 20  74 65 73 74 20 66 69 6c  |is is a test fil|
00000020  65 2e                                             |e.|
00000022
Enter fullscreen mode Exit fullscreen mode

你可以看到 '\n' 對應到 '\x0a',也就是沒有轉換,仍然保留是 '\n'。

如果開檔時設定 newline 為 '',Windows 平台上看到的就會是:

Format-Hex -Path file.txt

   Label: C:\Users\meebo\code\python\test_py3.13\file.txt

          Offset Bytes                                           Ascii
                 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
          ------ ----------------------------------------------- -----
0000000000000000 48 65 6C 6C 6F 2C 20 77 6F 72 6C 64 21 0A 54 68 Hello, world!�Th
0000000000000010 69 73 20 69 73 20 61 20 74 65 73 74 20 66 69 6C is is a test fil
0000000000000020 65 2E                                           e.
Enter fullscreen mode Exit fullscreen mode

你會發現原來被換成 '\r\n' 的地方現在只有 '\n' 了,這就是 ‵newline‵ 在寫入檔案時的作用。

讀入檔案時換行的處理

newline 換行處理
None(預設值) 啟用所謂的宇宙換行模式,文字中出現的 '\r'、'\n' 或是 '\r\n',都會被轉換成 '\n'
空字串 '' 使用宇宙換行模式認得所有的換行字元,但保留原樣不轉換
'\r'、'\r' 或 '\r\n' 只會認定指定的換行字元,但不會轉換,這會影響到計算的行數

以底下這個程式為例:

with open('file.txt', 'w', newline='') as file:
    file.write('Hello, world!\n')
    file.write('This is a test file.\r\n')
    file.write('Another line\r')
    file.write('Last line')
Enter fullscreen mode Exit fullscreen mode

我們刻意指定不轉換換行字元寫入檔案,以 16 進位格式檢視檔案內容:

Format-Hex -Path file.txt

   Label: C:\Users\meebo\code\python\test_py3.13\file.txt

          Offset Bytes                                           Ascii
                 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
          ------ ----------------------------------------------- -----
0000000000000000 48 65 6C 6C 6F 2C 20 77 6F 72 6C 64 21 0A 54 68 Hello, world!�Th
0000000000000010 69 73 20 69 73 20 61 20 74 65 73 74 20 66 69 6C is is a test fil
0000000000000020 65 2E 0D 0A 41 6E 6F 74 68 65 72 20 6C 69 6E 65 e.��Another line
0000000000000030 0D 4C 61 73 74 20 6C 69 6E 65                   �Last line
Enter fullscreen mode Exit fullscreen mode

確實可以看到不同的換行字元都保留原樣在檔案中。如果以如下程式讀取同一個檔案:

with open('file.txt', 'r') as file:
    for i, line in enumerate(file):
        print(f"Line {i}: {line}", end='')
Enter fullscreen mode Exit fullscreen mode

會得到如下的結果:

Line 0: Hello, world!
Line 1: This is a test file.
Line 2: Another line
Line 3: Last line
Enter fullscreen mode Exit fullscreen mode

表示不論檔案中是 '\n'、'\r' 還是 '\r\n',都會被當成換行,所以讀取結果總共有 4 行。但如果開檔時傳入 '\r' 給 newline,結果如下:

Line 0: Hello, world!
Line 1: a test file.
Line 2: Last line
Enter fullscreen mode Exit fullscreen mode

你會看到只有 3 行,這是因為以 '\r' 為換行字元,會讀到以下三行:

'Hello, world!\nThis is a test file.\r'
'\nAnother line\r'
'Last line'
Enter fullscreen mode Exit fullscreen mode

印第 1 行時結果為:

Line 0: Hello, world!
This is a test file.
^
Enter fullscreen mode Exit fullscreen mode

而且因為行尾的 \r,所以列印位置回到開頭的 'T' 處,接著印第 2 行就會蓋掉 'This...' 這一行開頭,變成:

Line 0: Hello, world!
Line 1: a test file.
Another line
^
Enter fullscreen mode Exit fullscreen mode

'Line 1: ' 蓋掉了 'This is ',然後因為第 2 行開頭的 '\n' 導致列印位置移到下一行開頭,印出 'Another Line' 後,又因為 '\r' 把顯示位置移到這行開頭 'A' 處。再印出最後一行:

Line 0: Hello, world!
Line 1: a test file.
Line 3: Last line
Enter fullscreen mode Exit fullscreen mode

'Line 3: ' 一樣蓋掉了原本的 'Another '。

csv 模組在檔案寫入/讀取時對換行的處理

csv 模組在寫入檔案時,會依據所選用的 csv 方言,預設為 'excel',幫你在每一列資料最後加上該方言的換行字元,預設的 'excel' 方言換行字元就是 '\r\n';讀取 csv 檔案時,則是固定將 '\r' 與 '\n' 視為換行字元。我們以底下的程式為例:

import csv

with open('file.csv', 'w') as file:
    writer = csv.writer(file)
    writer.writerow(['Name', 'Age'])
    writer.writerow(['Alice', 30])
    writer.writerow(['Bob', 25])
Enter fullscreen mode Exit fullscreen mode

在 Windows 上實際儲存的 csv 檔若以 16 進位格式查看:

Format-Hex -Path file.csv

   Label: C:\Users\meebo\code\python\test_py3.13\file.csv

          Offset Bytes                                           Ascii
                 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
          ------ ----------------------------------------------- -----
0000000000000000 4E 61 6D 65 2C 41 67 65 0D 0D 0A 41 6C 69 63 65 Name,Age���Alice
0000000000000010 2C 33 30 0D 0D 0A 42 6F 62 2C 32 35 0D 0D 0A    ,30���Bob,25���
Enter fullscreen mode Exit fullscreen mode

你會看到每一行的結尾都變成 '\r\r\n',這是因為 csv 模組會以 '\r\n' 當結尾,而 write 又把 '\n' 轉換成 '\r\n' 所造成。

如果以底下的程式碼讀取 csv 檔:

import csv

with open('file.csv', 'r') as file:
    reader = csv.reader(file)
    for i, row in enumerate(reader):
        print(f'Row {i}: {row}')
Enter fullscreen mode Exit fullscreen mode

會得到以下結果:

Row 0: ['Name', 'Age']
Row 1: []
Row 2: ['Alice', '30']
Row 3: []
Row 4: ['Bob', '25']
Row 5: []
Enter fullscreen mode Exit fullscreen mode

這是因為讀入檔案時,會先把 '\r\r\n' 裡的 '\r\n' 換成 '\n',變成 '\r\n',但是 csv 讀檔會把 '\r' 和 '\n' 都當成換行,所以你可以看到每一列資料後面都會有一列空的資料。

如果一開始存檔的時候,傳入 '' 給 newline,那麼 csv 模組寫入檔案的 '\r\n' 就會保留原樣,像是這樣:

Format-Hex -Path file.csv

   Label: C:\Users\meebo\code\python\test_py3.13\file.csv

          Offset Bytes                                           Ascii
                 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
          ------ ----------------------------------------------- -----
0000000000000000 4E 61 6D 65 2C 41 67 65 0D 0A 41 6C 69 63 65 2C Name,Age��Alice,
0000000000000010 33 30 0D 0A 42 6F 62 2C 32 35 0D 0A             30��Bob,25��
Enter fullscreen mode Exit fullscreen mode

讀取檔案時,會先將 '\r\n' 轉換成 '\n',csv 讀取時就視為單一換行,得到以下結果:

Row 0: ['Name', 'Age']
Row 1: ['Alice', '30']
Row 2: ['Bob', '25']
Enter fullscreen mode Exit fullscreen mode

如果是在 Linux 上,因為預設的換行字元就是 '\n',因此即使傳入 '' 給 newline 開啟檔案,也不會更改 csv 模組寫入的 '\r\n',因此不會有問題。

透過以上的說明,你應該就瞭解了為什麼使用 csv 模組時,通常會要求你開檔時傳入 '' 給 newline 參數了。

Heroku

Deploy with ease. Manage efficiently. Scale faster.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (0)

AWS Security LIVE! Stream

Streaming live from AWS re:Inforce

What’s next in cybersecurity? Find out live from re:Inforce on Security LIVE!

Learn More