2012-07-07 84 views
12

我想区分None和空字符串时使用Python的csv模块在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] 

但是那个w将部分地挫败我对csv模块的兴趣(用C语言实现的快速反序列化/序列化,特别是在处理大型列表时)。

是否有csv.Dialect或参数csv.writercsv.reader,这将使他们在这个用例''None之间的区别?

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

回答

7

The documentation暗示你想要什么是不可能的:

为了使它尽可能容易与哪些执行DB API模块接口,价值都不是写为空字符串。

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

我一个人会支持改变这个(以及csv模块的各种其他限制),但它可能是人们希望将这类工作卸载到不同的库中,并保持CSV模块简单(或至少和它一样简单)。

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

+0

Yep证实:在Modules/_csv.c中查看csv_writerow(if(field == Py_None)...)。没有办法区分''和None。真是一个耻辱,鉴于方言抽象,你会希望有更多的灵活性。你提到csv模块的其他限制,你介意阐述(如果还有其他问题,我真的应该开始看其他csv阅读写作)? – user1509316 2012-07-08 00:48:19

+0

我发现一个有限的问题是分隔符必须是单个字符。所以你不能解析一个文件,其中列被两个标签分隔。就像你遇到的None事情一样,这很容易解决,但仍然很烦人。 – BrenBarn 2012-07-08 02:21:02

+0

另一个是模块内的硬编码ascii限制。 – 2013-01-18 14:02:11

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, 
+0

其实,你不能继承'csv.reader'和'csv.writer'。 – martineau 2013-04-05 03:12:15

1

当你拥有了消费者和序列化数据的创作者既控制,请考虑使用不支持这种区分的格式。

例子:

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

你至少可以部分地侧步什么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', '']] 
+0

第一个解决方案的变体解决了我写的问题。用__repr__创建一个NONE(int)类,返回一个空字符串。用NONE替换所有的None值(我不得不格式化我的数据,所以没有额外的工作)。然后使用QUOTE_NONNUMERIC创建csv编写器。 这有点不好意思,但这意味着在输出文件中,你知道引用字段总是一个字符串,并且没有引号的空字段总是一个无。 – trelltron 2017-02-08 14:31:22

+0

@Tom:我不确定你的意思是什么“用NONE取代所有的无值”,因为你已经定义了'NONE'是一个'int'子类 - 所以你似乎需要提供一个整数值创建'NONE'的_instances_。你在创建单例时是否这样做?即'NONE = NONE(0)'。 – martineau 2017-02-08 14:51:27

+0

@Tom:没关系。我现在意识到,如果在创建时没有提供任何值,那么你的'NONE'子类将继承'int'类的默认值为'0'的行为。即int()的整数值默认为零。 – martineau 2017-02-08 15:02:55

0

正如其他人所指出的,你不能真正通过csv.Dialect或参数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 

输出:

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

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