Подтвердить что ты не робот

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 по умолчанию равен '' для обеспечения обратной совместимости)

4b9b3361

Ответ 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/С++), но это может немного повлиять, поскольку процесс, вероятно, связан с минимальным уровнем ввода-вывода.