Python2/Python3通过writerow写入csv文件会有多余的空行 及 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互相转换_第1张图片

如果用Excel打开,两行中间会多出一空行,如下图:

Python2/Python3通过writerow写入csv文件会有多余的空行 及 bytes和str互相转换_第2张图片

问题分析

现在就要搞清楚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++打开如下:

用Excel打开图片如下:

Python2/Python3通过writerow写入csv文件会有多余的空行 及 bytes和str互相转换_第3张图片

 

Tips: 追加写入csv

将文档的打开方式由w(写入)改为a(追加)方式。

 

Python3代码:

方法一:writerdialect='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互相转换_第4张图片

打印出当前操作系统的换行符

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参数

你可能感兴趣的:(Python)