Python 无和空字符串的CSV读取器行为

Python 无和空字符串的CSV读取器行为,python,csv,string,nonetype,Python,Csv,String,Nonetype,当使用Python的csv模块在Python数据结构和csv表示之间来回切换时,我想区分None和空字符串(') 我的问题是,当我跑步时: import csv, cStringIO data = [['NULL/None value',None], ['empty string','']] f = cStringIO.StringIO() csv.writer(f).writerows(data) f = cStringIO.StringIO(f.getvalue())

当使用Python的
csv
模块在Python数据结构和csv表示之间来回切换时,我想区分
None
和空字符串(
'

我的问题是,当我跑步时:

import csv, cStringIO

data = [['NULL/None value',None],
        ['empty string','']]

f = cStringIO.StringIO()
csv.writer(f).writerows(data)

f = cStringIO.StringIO(f.getvalue())
data2 = [e for e in csv.reader(f)]

print "input : ", data
print "output: ", data2
我得到以下输出:

input:[['NULL/None value',None],'empty string',''']
输出:['NULL/None值','','','empty string','']]
当然,我可以使用
data
data2
来区分
None
和空字符串,比如:

data = [d if d!=None else 'None' for d in data]
data2 = [d if d!='None' else None for d in data2]
但这在一定程度上会挫败我对
csv
模块的兴趣(用C实现的快速反序列化/序列化,特别是在处理大型列表时)

是否有
csv.方言
csv.writer
csv.reader
的参数,使他们能够在本用例中区分
'


如果没有,是否有兴趣实现一个到
csv.writer
的补丁来实现这种来回转换?(可能是
方言。无需将参数默认为
'
,以确保向后兼容性。)

我不认为仅仅用方言就可以做你想做的事情,但你可以编写自己的csv.reader/write子类。另一方面,我仍然认为这对于这个用例来说是过分的。即使您想要捕捉的不仅仅是
None
,您也可能只想要
str()


当您已经控制了串行数据的消费者和创建者时,请考虑使用支持该区别的格式。

例如:

>>> import json
>>> json.dumps(['foo', '', None, 666])
'["foo", "", null, 666]'
>>>
表明您想要的是不可能的:

为了尽可能方便地与实现DB API的模块进行接口,值None被写为空字符串

这在
writer
类的文档中,表明它适用于所有方言,并且是csv模块的固有限制

就我个人而言,我支持改变这一点(以及csv模块的各种其他限制),但人们可能希望将这类工作转移到另一个库中,并保持csv模块的简单(或至少尽可能简单)


如果您需要更强大的文件读取功能,您可能想看看numpy、scipy和pandas中的CSV读取功能,我记得它们有更多的选项。

您至少可以通过创建自己版本的类似类/值的singleton
None
,部分绕过
CSV
模块的功能:

from __future__ import print_function
import csv


class NONE(object):
    ''' None-like class. '''
    def __repr__(self): # Method csv.writer class uses to write values.
        return 'NONE'   # Unique string value to represent None.
    def __len__(self):  # Method called to determine length and truthiness.
        return 0

NONE = NONE()  # Singleton instance of the class.


if __name__ == '__main__':

    try:
        from cStringIO import StringIO  # Python 2.
    except ModuleNotFoundError:
        from io import StringIO  # Python 3.

    data = [['None value', None], ['NONE value', NONE], ['empty string', '']]
    f = StringIO()
    csv.writer(f).writerows(data)

    f = StringIO(f.getvalue())
    print(" input:", data)
    print("output:", [e for e in csv.reader(f)])
结果:

输入:[['None value',None],'None value',None],'empty string',''']
输出:['None value','','None value','None'],['empty string','']
使用
NONE
而不是
NONE
将保留足够的信息,以便您能够区分它和任何实际的空字符串数据值

更好的选择… 您可以使用相同的方法实现一对相对轻量级的
csv.reader
csv.writer
“代理”类,这是必要的,因为您实际上无法对用C编写的内置
csv
类进行子类化,而不会引入大量开销(因为大部分处理仍将由底层内置程序执行)。这将使所进行的操作完全透明,因为它都封装在代理中

from __future__ import print_function
import csv


class csvProxyBase(object): _NONE = '<None>'  # Unique value representing None.


class csvWriter(csvProxyBase):
    def __init__(self, csvfile, *args, **kwrags):
        self.writer = csv.writer(csvfile, *args, **kwrags)
    def writerow(self, row):
        self.writer.writerow([self._NONE if val is None else val for val in row])
    def writerows(self, rows):
        list(map(self.writerow, rows))


class csvReader(csvProxyBase):
    def __init__(self, csvfile, *args, **kwrags):
        self.reader = csv.reader(csvfile, *args, **kwrags)
    def __iter__(self):
        return self
    def __next__(self):
        return [None if val == self._NONE else val for val in next(self.reader)]
    next = __next__  # Python2.x compatibility.


if __name__ == '__main__':

    try:
        from cStringIO import StringIO  # Python 2.
    except ModuleNotFoundError:
        from io import StringIO  # Python 3.

    data = [['None value', None], ['empty string', '']]
    f = StringIO()
    csvWriter(f).writerows(data)

    f = StringIO(f.getvalue())
    print("input : ", data)
    print("ouput : ", [e for e in csvReader(f)])

正如其他人指出的那样,你不能通过
csv.dialogue
csv.writer
和/或
csv.reader
的参数来实现这一点。但是正如我在一篇评论中所说的,你可以通过有效地将后两个子类化来实现它(因为它们是内置的,你显然不能真正做到)。什么是“子类”编写时要做的就是截取
None
值并将其更改为一个唯一的字符串,然后在读回这些值时反转过程。下面是一个完整的示例:

import csv, cStringIO
NULL = '<NULL>'  # something unlikely to ever appear as a regular value in your csv files

class MyCsvWriter(object):
    def __init__(self, *args, **kwrds):
        self.csv_writer = csv.writer(*args, **kwrds)

    def __getattr__(self, name):
        return getattr(self.csv_writer, name)

    def writerow(self, row):
        self.csv_writer.writerow([item if item is not None else NULL
                                      for item in row])
    def writerows(self, rows):
        for row in rows:
            self.writerow(row)

class MyCsvReader(object):
    def __init__(self, *args, **kwrds):
        self.csv_reader = csv.reader(*args, **kwrds)

    def __getattr__(self, name):
        return getattr(self.csv_reader, name)

    def __iter__(self):
        rows = iter(self.csv_reader)
        for row in rows:
            yield [item if item != NULL else None for item in row]

data = [['NULL/None value', None],
        ['empty string', '']]

f = cStringIO.StringIO()
MyCsvWriter(f).writerows(data)  # instead of csv.writer(f).writerows(data)

f = cStringIO.StringIO(f.getvalue())
data2 = [e for e in MyCsvReader(f)]  # instead of [e for e in csv.reader(f)]

print "input : ", data
print "ouput : ", data2

这有点冗长,可能会降低csv文件的读写速度(因为它们是用C/C++编写的),但这可能没有什么区别,因为这个过程可能是低级别I/O绑定的。

我也遇到了这个问题,并发现了这个问题

问题的解决办法:

  • 子类csv.DictWriter,使用字典作为元素类型,并让其writerow方法执行特定于应用程序的工作
  • 定义一个writerow()函数,该函数执行类似的操作(本质上是包装csv.writerow())

如上所述,这是
csv
模块的一个限制。解决方案是只需通过简单的字典理解重写循环中的行,如下所示:

reader = csv.DictReader(csvfile)
for row in reader:
    # Interpret empty values as None (instead of '')
    row = {k: v if v else None for k, v in row.items()}
    :

Yep确认:查看Modules/_csv.c中的csv_writerow(if(field==Py_None)…..。无法区分“”和None。真的很遗憾,考虑到方言抽象,您希望更灵活一些。您提到了csv模块的其他限制,您介意详细说明吗(如果还有其他问题,我真的应该开始考虑其他csv阅读和写作)?有时,我发现一个令人恼火的限制是分隔符必须是单个字符。因此,您无法解析列由两个制表符分隔的文件。就像您遇到的“无”一样,这很容易解决,但仍然令人恼火。另一个限制是模块内的硬编码ascii限制。第一个限制的变体是lution为我解决了写入问题。创建了一个类NONE(int)和一个返回空字符串的repr。将所有NONE值替换为NONE(我必须格式化数据,因此没有额外的工作)。然后用QUOTE_NONNUMERIC创建csv编写器。这有点老套,但这意味着在输出文件中,带引号的字段始终是字符串,而不带引号的空字段始终是无。@trellt
reader = csv.DictReader(csvfile)
for row in reader:
    # Interpret empty values as None (instead of '')
    row = {k: v if v else None for k, v in row.items()}
    :