2012-07-07 83 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綁定。