使用None和空字符串的csv reader行为

时间:2012-07-07 22:55:47

标签: python csv string nonetype

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

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

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', '']]
output:  [['NULL/None value', ''], ['empty string', '']]

当然,我可以使用datadata2来区分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.Dialectcsv.writer的{​​{1}}或参数是否可以让他们在此用例中区分csv.reader''?< / p>

如果没有,是否有兴趣实施补丁None来实现这种来回? (可能csv.writer参数默认为Dialect.None_translate_to以确保向后兼容性)

7 个答案:

答案 0 :(得分:12)

您可以通过创建自己的单身csv版本(至少类/值)来至少部分地支持None模块的功能:

class NONE(object):
    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        # (optional)

NONE = NONE()  # singleton instance of the class

import csv
import cStringIO

data = [['None value', None], ['NONE value', NONE], ['empty string', '']]
f = cStringIO.StringIO()
csv.writer(f).writerows(data)
f = cStringIO.StringIO(f.getvalue())
print " input:", data
print "output:", [e for e in csv.reader(f)]

结果:

 input: [['None value', None], ['NONE value', NONE],   ['empty string', '']]
output: [['None value', ''],   ['NONE value', 'NONE'], ['empty string', '']]

使用NONE代替None可以保留足够的信息,以便您能够区分它与任何实际的空字符串数据值。

更好的选择......
您可以使用相同的方法来实现一对相对轻量级的csv.readercsv.writer“代理”类 - 这是必需的,因为您实际上无法对写入的内置csv类进行子类化在C中 - 没有引入大量开销(因为大部分处理仍将由底层内置函数执行)。这将使得完全透明,因为它全部封装在代理中。

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):
        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 self.reader.next()]

if __name__ == '__main__':
    import cStringIO as StringIO
    data = [['None value', None], ['empty string', '']]
    f = StringIO.StringIO()
    csvWriter(f).writerows(data)
    f = StringIO.StringIO(f.getvalue())
    print " input:", data
    print "output:", [e for e in csvReader(f)]

结果:

 input: [['None value', None], ['empty string', '']]
output: [['None value', None], ['empty string', '']]

答案 1 :(得分:10)

The documentation表明你想要的东西是不可能的:

  

为了使与实现DB API的模块接口尽可能简单,将值None写为空字符串。

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

我支持改变这个(以及csv模块的各种其他限制),但可能是人们想要将这种工作卸载到不同的库中,并保持CSV模块简单(或者至少这么简单。)

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

答案 2 :(得分:1)

我认为用单纯的方言做你想做的事情是不可能的,但你可以编写自己的csv.reader / write子类。另一方面,我仍然认为这个用例太过分了。即使您想捕获的不只是None,您可能只想要str()

>>> data = [['NULL/None value',None],['empty string','']]
>>> i = cStringIO.StringIO()
>>> csv.writer(i).writerows(map(str,row) for row in data)
>>> print i.getvalue()
NULL/None value,None
empty string,

答案 3 :(得分:0)

由于您可以控制序列化数据的使用者和创建者,因此请考虑使用支持该区别的格式。

示例:

>>> import json
>>> json.dumps(['foo', '', None, 666])
'["foo", "", null, 666]'
>>>

答案 4 :(得分:0)

正如其他人所指出的那样,你无法通过csv.Dialectcsv.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

输出:

input :  [['NULL/None value', None], ['empty string', '']]
ouput :  [['NULL/None value', None], ['empty string', '']]

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

答案 5 :(得分:0)

我也遇到了这个问题,找到了这个https://bugs.python.org/issue23041

问题解决方案:

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

答案 6 :(得分:0)

如上所述,这是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()}
    :