天天看點

Python2/Python3通過writerow寫入csv檔案會有多餘的空行 及 bytes和str互相轉換前言Python2代碼Python3代碼:福利【重點】:bytes和str互相轉換總結:檔案打開模式

Table of Contents

前言

Python2代碼

Python2原來代碼【有問題的】

現象

問題分析

Python2解決辦法​​​​​

Python2完整代碼

Tips: 追加寫入csv

Python3代碼:

方法一:writer加dialect='unix'

方法二:打開方式加newline=''

列印出目前作業系統的換行符

Python3為什麼不能以bytes模式打開csv檔案?

csv子產品writerow函數代碼

Python3以bytes模式打開csv檔案

福利【重點】:bytes和str互相轉換

總結:

檔案打開模式

前言

項目需要,先處理txt文檔,從中提取數值,然後将其轉存為csv檔來做資料分析。在存檔為csv檔案時出問題了,以下為我的經驗總結,供各位參考。希望大家可以少走彎路。

Python2代碼

Python2原來代碼【有問題的】

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import csv

csvRow = ['01:01:00', '33000', '27589', '27070', '27070', '25855']
with open("test.csv","a+") as csvfile:
    writer = csv.writer(csvfile)
    for item in range(0, 3):
        writer.writerow(csvRow)
           

現象

用Notepad++打開檔案(補上這個,怕有些朋友不會View->Show Symbol->Show All Characters ),發現每行末尾都有CR, CRLF,如下截圖。

Python2/Python3通過writerow寫入csv檔案會有多餘的空行 及 bytes和str互相轉換前言Python2代碼Python3代碼:福利【重點】:bytes和str互相轉換總結:檔案打開模式

如果用Excel打開,兩行中間會多出一空行,如下圖:

Python2/Python3通過writerow寫入csv檔案會有多餘的空行 及 bytes和str互相轉換前言Python2代碼Python3代碼:福利【重點】:bytes和str互相轉換總結:檔案打開模式

問題分析

現在就要搞清楚CR, CRLF分别是誰輸出的。本文後續會深入分析原因(樓梯在此方法二:打開方式加newline='')

心急的朋友可以先參考這個連結https://bugs.python.org/issue7198

Python2解決辦法​​​​​

将文檔的打開方式改為二進制方式。

原來的打開方式:with open("test.csv","a+") as csvfile:

更改後打開方式:with open("test.csv","ab+") as csvfile:

Python2完整代碼

如下供各位參考:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import csv

csvRow = ['01:01:00', '33000', '27589', '27070', '27070', '25855']
with open("test.csv","ab+") as csvfile:
    writer = csv.writer(csvfile)
    for item in range(0, 3):
        writer.writerow(csvRow)
           

更新後檔案圖檔,用Notepad++打開如下:

Python2/Python3通過writerow寫入csv檔案會有多餘的空行 及 bytes和str互相轉換前言Python2代碼Python3代碼:福利【重點】:bytes和str互相轉換總結:檔案打開模式

用Excel打開圖檔如下:

Python2/Python3通過writerow寫入csv檔案會有多餘的空行 及 bytes和str互相轉換前言Python2代碼Python3代碼:福利【重點】:bytes和str互相轉換總結:檔案打開模式

Tips: 追加寫入csv

将文檔的打開方式由w(寫入)改為a(追加)方式。

Python3代碼:

方法一:writer加dialect='unix'

headerRow = ['ISN', 'BUILD_PHASE']
oneRow = ['81E2-TESTTEST','PRE-PVT']
with open("test.csv","a+") as csvfile:
    writer = csv.writer(csvfile, dialect='unix')    # 将dialect設定為unix,換行符即為'\n'
    writer.writerow(headerRow)
    writer.writerow(oneRow)
           

參考csv子產品說明,有這麼一段話

Readers and writers support a dialect argument, which is a convenient

handle on a group of settings.  When the dialect argument is a string,

it identifies one of the dialects previously registered with the module.

If it is a class or instance, the attributes of the argument are used as

the settings for the reader or writer:

    class excel:

        delimiter = ','

        quotechar = '"'

        escapechar = None

        doublequote = True

        skipinitialspace = False

        lineterminator = '\r\n'

        quoting = QUOTE_MINIMAL

筆者翻譯:reader和writer都支援dialect參數,處理一組設定時會很友善。

當dialect參數是一個字元串時,它識别為子產品先前注冊的其中一種方言。

如果它是類或執行個體,則參數的屬性被用來作為reader和writer的設定,如上面的class excel等。

例如,如果dialect參數為excel類時,換行符為’\r\n’.

如果dialect參數為unix, 換行符為’\n’【以下可參考csv子產品說明文檔】

class excel(Dialect):
    """Describe the usual properties of Excel-generated CSV files."""
    delimiter = ','
    quotechar = '"'
    doublequote = True
    skipinitialspace = False
    lineterminator = '\r\n'
    quoting = QUOTE_MINIMAL
register_dialect("excel", excel)

class excel_tab(excel):
    """Describe the usual properties of Excel-generated TAB-delimited files."""
    delimiter = '\t'
register_dialect("excel-tab", excel_tab)

class unix_dialect(Dialect):
    """Describe the usual properties of Unix-generated CSV files."""
    delimiter = ','
    quotechar = '"'
    doublequote = True
    skipinitialspace = False
    lineterminator = '\n'
    quoting = QUOTE_ALL
register_dialect("unix", unix_dialect)
           

方法二:打開方式加newline=''

headerRow = ['ISN', 'BUILD_PHASE']
oneRow = ['81E2-TESTTEST','PRE-PVT']

# 大家可以将換行符指定為'\n'或'\r'或者不指定,試試效果
with open("test.csv","a+", newline='') as csvfile:  #換行符指定為空字元,不是None【空字元!=None】
    writer = csv.writer(csvfile)
    writer.writerow(headerRow)
    writer.writerow(oneRow)
           

以下這段話選擇builtins.py子產品中open函數:

On output, if newline is None, any '\n' characters written are translated to the system default line separator, os.linesep. If newline is '' or '\n', no translation takes place. If newline is any of the other legal values, any '\n' characters written are translated to the given string.

筆者翻譯:在輸出時,如果換行是None,則寫入的任何'\ n'字元都被轉換為系統預設行分隔符,os.linesep。 如果換行符是''或'\ n',不進行轉換。 如果換行符是任何其他合法值【注意:不能将其随心所意地設定為其他英文字母,大家可以自行實驗】,所有'\ n'字元都将被轉換為給定的字元串。

如果newline采用預設值None(即不進行指定任何指定),在Windows系統下,os.linesep即為'\r\n',那麼此時就會将’\n’轉換為’\r\n’(即CRLF),是以會多一個’\r’(回車)出來,如下圖。在csv中打開就會多一行。

Python2/Python3通過writerow寫入csv檔案會有多餘的空行 及 bytes和str互相轉換前言Python2代碼Python3代碼:福利【重點】:bytes和str互相轉換總結:檔案打開模式

列印出目前作業系統的換行符

import os
print("os.linesep:%r" %os.linesep)
           

Python3為什麼不能以bytes模式打開csv檔案?

有朋友會問:為什麼不能像Python2那樣,将open方式加個b不就好了嗎?

在内置子產品builtins.py中open函數中有這麼一段話:

Python distinguishes between files opened in binary and text modes,

    even when the underlying operating system doesn't. Files opened in

    binary mode (appending 'b' to the mode argument) return contents as

    bytes objects without any decoding. In text mode (the default, or when

    't' is appended to the mode argument), the contents of the file are

    returned as strings, the bytes having been first decoded using a

platform-dependent encoding or using the specified encoding if given.

筆者翻譯過來就是:

Python區分檔案的打開方式:二進制模式和文本模式,

即使底層作業系統沒有作此區分。 以二進制模式打開的檔案

(mode參數要加'b')傳回内容為不用解碼的bytes對象。

在文本模式下(預設情況,或者mode參數加了't'),檔案的内容傳回為

string對象--使用作業系統平台的編碼或使用指定的編碼(如果給定)來将bytes先解碼。

簡單而言:

Python3有兩種表示字元序列的類型:bytes和str。bytes即為原始的8位值,即純8位值。str包含Unicode字元。

Python3給open函數添加了名為encoding的新參數,其預設值為’utf-8’,檔案的預設打開方式為文本模式。如果像如下這句代碼加個’b’來以二進制打開,那麼寫入時會提示錯誤“a bytes-like object is required, not 'str'”,因為writerow寫入的是字元串格式【這個可以參考csv子產品下的writerow函數代碼】。

# Python3為什麼不能以bytes模式打開csv檔案?【請注意:如下代碼執行會出錯】
headerRow = ['ISN', 'BUILD_PHASE']
oneRow = ['81E2-TESTTEST','PRE-PVT']

with open("test.csv","ab+") as csvfile: # 打開方式為bytes模式,下面寫入會出錯
    writer = csv.writer(csvfile)
    writer.writerow(headerRow)
    writer.writerow(oneRow)
           

csv子產品writerow函數代碼

【怕某些朋友找不到,貼上csv子產品writerow函數代碼,注意這句“if isinstance(item, text_type):”判斷item是否為text_type。

如果是text_type,才會執行下面encode(編碼)語句。如果不是,那就不會被寫入csv。】

class CSVWriter(CSVBase):
    def __init__(self, fn, **kwargs):
        self.stream = _csv_open(fn, 'w')
        self.writer = csv.writer(self.stream, **self.defaults)

    def writerow(self, row):
        if sys.version_info[0] < 3:
            r = []
            for item in row:
                if isinstance(item, text_type):
                    item = item.encode('utf-8')
                r.append(item)
            row = r
        self.writer.writerow(row)
           

Python3以bytes模式打開csv檔案

# Python3可以以bytes模式打開csv檔案
# 隻是這時寫入的資料格式為bytes,string格式需要利用encode編碼為bytes對象
strTest = 'ISN'
with open("test.csv","ab+") as csvfile: # 打開方式為bytes模式
    csvfile.write(os.urandom(10))
    csvfile.write(strTest.encode())
           

福利【重點】:bytes和str互相轉換

之前說了Python3的兩種字元表示類型:bytes和str。我們在編寫Python程式時,核心部分應該隻處理Unicode類型,編碼和解碼操作應該放在外圍。下面的兩個輔助函數來自《Effective Python》,個人覺得很好,特分享給大家。

def to_str(bytes_or_str):
    """
    接受str或bytes,總是傳回str
    :param bytes_or_str: 
    :return: Instance of str
    """
    if isinstance(bytes_or_str, bytes):
        value = bytes_or_str.decode('utf-8')
    else:
        value = bytes_or_str
    return value    # Instance of str


def to_bytes(bytes_or_str):
    """
    接受str或bytes,總是傳回bytes
    :param bytes_or_str: 
    :return: Instance of bytes
    """
    if isinstance(bytes_or_str, str):
        value = bytes_or_str.encode('utf-8')
    else:
        value = bytes_or_str
    return value    # Instance of bytes
           

總結:

雖然是一個小問題,但是涉及到一些csv基本知識點,以及Python2與Python3的差別。希望各位看官可以明白。

檔案打開模式

檔案打開模式之間的差別可以參考如下連結:

Python File(檔案) 方法_檔案打開模式_mode參數