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', '']]
もちろん、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.Dialect
とNone
を区別できるようにするcsv.writer
およびcsv.reader
への''
またはパラメーターはありますか?
そうでない場合、csv.writer
へのパッチを実装して、この種のやり取りを可能にすることに関心がありますか? (おそらく、下位互換性を確保するためにDialect.None_translate_to
パラメータがデフォルトで''
に設定されています)
ドキュメント は、必要なものが不可能であることを示唆しています。
DB APIを実装するモジュールとのインターフェースをできるだけ簡単にするために、値Noneは空の文字列として書き込まれます。
これはwriter
クラスのドキュメントにあり、すべての方言に当てはまり、csvモジュールの本質的な制限であることを示唆しています。
私はこれを変更することをサポートします(csvモジュールの他のさまざまな制限とともに)、この種の作業を別のライブラリにオフロードし、CSVモジュールをシンプルに(または少なくとも同じくらいシンプルに)したいと思うかもしれません。そのまま)。
より強力なファイル読み取り機能が必要な場合は、numpy、scipy、pandasのCSV読み取り関数を確認することをお勧めします。
独自のバージョンのシングルトン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.reader
およびcsv.writer
「プロキシ」クラス— Cで記述された組み込みcsv
クラスを実際にサブクラス化できないために必要です—多くのオーバーヘッドを導入せずに(処理の大部分は、基礎となるビルトイン)。これはすべてプロキシ内にカプセル化されているため、完全に透過的に行われます。
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', '']]
単なる方言であなたが望むことをすることは可能ではないと思いますが、あなた自身の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,
シリアル化されたデータのコンシューマーと作成者の両方を制御できるので、その区別をサポートするフォーマットの使用を検討してください。
例:
>>> import json
>>> json.dumps(['foo', '', None, 666])
'["foo", "", null, 666]'
>>>
上記のように、これは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()}
:
他の人が指摘したように、csv.Dialect
またはcsv.writer
やcsv.reader
へのパラメータを使用してこれを実際に行うことはできません。ただし、1つのコメントで述べたように、後者の2つを実質的にサブクラス化して実装します(組み込みなので、実際には実行できません)。書き込み時に「サブクラス」が行うことは、単に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++で書かれているため)。
私もこの問題に遭遇し、これを見つけます https://bugs.python.org/issue23041 。
問題の解決策:
- csv.DictWriterをサブクラス化し、要素タイプとして辞書を使用し、そのwriterowメソッドにアプリケーション固有の処理を実行させます。
- 同様のことを行うwriterow()関数を定義します(本質的にはcsv.writerow()をラップします)。