Cvv поведение читателя с None и пустой строкой
Я хотел бы выделить None
и пустые строки при переходе между структурой данных Python и представлением csv с использованием модуля Python 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
или параметры для csv.writer
и csv.reader
, которые позволят им различать ''
и None
в этом прецеденте?
Если нет, будет ли интерес к реализации патча к csv.writer
, чтобы включить этот вид назад и вперед? (Возможно, параметр Dialect.None_translate_to
по умолчанию равен ''
для обеспечения обратной совместимости)
Ответы
Ответ 1
Документация показывает, что вы не хотите:
Чтобы максимально упростить взаимодействие с модулями, реализующими API БД, значение None записывается как пустая строка.
Это в документации для класса writer
, предполагая, что это верно для всех диалектов и является внутренним ограничением модуля csv.
Я бы поддержал это изменение (наряду с различными другими ограничениями модуля csv), но может быть, люди захотят разгрузить эту работу в другую библиотеку и сохранить модуль CSV простым (или на менее прост, чем есть).
Если вам нужны более мощные возможности чтения файлов, вы можете посмотреть функции чтения CSV в numpy, scipy и pandas, которые, как я помню, имеют больше параметров.
Ответ 2
Вы могли бы, по крайней мере, частично выполнить то, что делает модуль 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
"proxy" — необходимо, так как вы не можете на самом деле подклассифицировать встроенные классы 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', '']]
Ответ 3
Я не думаю, что было бы возможно сделать то, что вы хотите, с простым диалектом, но вы могли бы написать свой собственный подкласс 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,
Ответ 4
Как вы контролируете как пользователя, так и создателя сериализованных данных, рассмотрите возможность использования формата, который поддерживает это различие.
Пример:
>>> import json
>>> json.dumps(['foo', '', None, 666])
'["foo", "", null, 666]'
>>>
Ответ 5
Как указывали другие, вы не можете сделать это с помощью 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/С++), но это может немного повлиять, поскольку процесс, вероятно, связан с минимальным уровнем ввода-вывода.