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


person user1509316    schedule 07.07.2012    source источник


Ответы (7)


Документация предполагает, что то, что вы хотите, невозможно:

Чтобы максимально упростить взаимодействие с модулями, реализующими DB API, значение None записывается как пустая строка.

Это указано в документации для класса writer, предполагая, что это верно для всех диалектов и является внутренним ограничением модуля csv.

Я, например, поддержал бы изменение этого (наряду с различными другими ограничениями модуля csv), но может случиться так, что люди захотят перенести такую ​​работу в другую библиотеку и оставить модуль CSV простым (или, по крайней мере, таким же простым). как есть).

Если вам нужны более мощные возможности чтения файлов, вы можете взглянуть на функции чтения CSV в numpy, scipy и pandas, которые, насколько я помню, имеют больше возможностей.

person BrenBarn    schedule 07.07.2012
comment
Да, подтверждено: просмотр csv_writerow в Modules/_csv.c (if (field == Py_None) ...). Невозможно различить '' и None. Очень жаль, учитывая абстракцию диалекта, вы бы надеялись на большую гибкость. Вы упомянули другие ограничения модуля csv, не могли бы вы уточнить (если есть другие проблемы, мне действительно следует начать искать другие записи для чтения csv)? - person user1509316; 08.07.2012
comment
Одно ограничение, которое меня иногда раздражает, заключается в том, что разделители должны быть одиночными символами. Таким образом, вы не можете разобрать файл, в котором столбцы разделены, скажем, двумя вкладками. Как и в случае с None, с которым вы столкнулись, это довольно легко обойти, но все же раздражает. - person BrenBarn; 08.07.2012
comment
Еще одним является жестко запрограммированное ограничение ascii внутри модуля. - person Spencer Rathbun; 18.01.2013

Вы могли бы, по крайней мере, частично обойти то, что делает модуль csv, создав собственную версию одноэлементного None-подобного класса/значения:

from __future__ import print_function
import csv


class NONE(object):
    ''' None-like class. '''
    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

NONE = NONE()  # Singleton instance of the class.


if __name__ == '__main__':

    try:
        from cStringIO import StringIO  # Python 2.
    except ModuleNotFoundError:
        from io import StringIO  # Python 3.

    data = [['None value', None], ['NONE value', NONE], ['empty string', '']]
    f = StringIO()
    csv.writer(f).writerows(data)

    f = 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 — необходимых, поскольку вы не можете создать подкласс встроенных классов csv, написанных на C, — без больших накладных расходов (поскольку большая часть обработки по-прежнему будет выполняться нижележащими встроенными модулями). Это сделало бы то, что происходит, полностью прозрачным, поскольку все это инкапсулировано в прокси.

from __future__ import print_function
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):
        list(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 next(self.reader)]
    next = __next__  # Python2.x compatibility.


if __name__ == '__main__':

    try:
        from cStringIO import StringIO  # Python 2.
    except ModuleNotFoundError:
        from io import StringIO  # Python 3.

    data = [['None value', None], ['empty string', '']]
    f = StringIO()
    csvWriter(f).writerows(data)

    f = StringIO(f.getvalue())
    print("input : ", data)
    print("ouput : ", [e for e in csvReader(f)])

Полученные результаты:

 input: [['None value', None], ['empty string', '']]
output: [['None value', None], ['empty string', '']]
person martineau    schedule 08.07.2012
comment
Вариант первого решения решил проблему записи для меня. Создал класс NONE(int) с repr, который возвращает пустую строку. Заменил все значения None на NONE (мне все равно пришлось форматировать данные, так что дополнительной работы не было). Затем создайте средство записи csv с QUOTE_NONNUMERIC. Это немного хакерски, но это означает, что в выходном файле вы знаете, что поле в кавычках всегда является строкой, а пустое поле без кавычек всегда равно None. - person trelltron; 08.02.2017
comment
@trelltron: Умно, но потенциальным недостатком является то, что он требует использования QUOTE_NONNUMERIC, что в противном случае может вам не понадобиться, и потенциально может сделать файлы намного больше. Мои решения не требуют использования каких-либо конкретных параметров csv для работы. Также обратите внимание, что мой второй вариант не требует замены всех значений None чем-либо. - person martineau; 25.03.2021

Я не думаю, что было бы возможно делать то, что вы хотите, с простым диалектом, но вы могли бы написать свой собственный подкласс 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,
person kojiro    schedule 07.07.2012

Поскольку у вас есть контроль как над потребителем, так и над создателем сериализованных данных, рассмотрите возможность использования формата, который поддерживает это различие.

Пример:

>>> import json
>>> json.dumps(['foo', '', None, 666])
'["foo", "", null, 666]'
>>>
person John Machin    schedule 07.07.2012

Как указывали другие, вы не можете сделать это с помощью 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++), но это может иметь небольшое значение, поскольку процесс, вероятно, в любом случае связан с низкоуровневым вводом-выводом.

person martineau    schedule 09.07.2012

Я тоже сталкиваюсь с этой проблемой и нахожу это https://bugs.python.org/issue23041.

Решения из вопроса:

  • подкласс csv.DictWriter, используйте словари в качестве типа вашего элемента, а его метод writerow выполняет работу, специфичную для приложения.
  • определить функцию writerow(), которая делает что-то подобное (по сути, обертывая csv.writerow()).
person XiYu Wang    schedule 25.04.2019

Как упоминалось выше, это ограничение модуля 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()}
    :
person jrc    schedule 14.11.2019