Чтение данных из файла CSV и преобразование из строки в правильный тип данных, включая столбец со списком целых чисел

Когда я читаю данные обратно из файла CSV, каждая ячейка интерпретируется как строка.

  • Как автоматически преобразовать прочитанные данные в правильный тип?
  • Или лучше: как я могу сообщить читателю csv правильный тип данных каждого столбца?

(Я написал двумерный список, в котором каждый столбец имеет разный тип (bool, str, int, список целых чисел), в файл CSV.)

Пример данных (в файле CSV):

IsActive,Type,Price,States
True,Cellphone,34,"[1, 2]"
,FlatTv,3.5,[2]
False,Screen,100.23,"[5, 1]"
True,Notebook, 50,[1]

Ответ 1

Как объясняет docs, считыватель CSV не выполняет автоматическое преобразование данных. У вас есть опция QUOTE_NONNUMERIC, но это приведет только к преобразованию всех некаблированных полей в поплавки. Это очень похоже на поведение других читателей csv.

Я не верю, что модуль csv Python будет вообще полезен для этого случая. Как уже указывали другие, literal_eval() - гораздо лучший выбор.

Следующее работает и преобразуется:

  • строки
  • ИНТ
  • плавает
  • списки
  • словари

Вы также можете использовать его для booleans и NoneType, хотя они должны быть отформатированы соответственно для literal_eval() для передачи. LibreOffice Calc выводит логические значения заглавными буквами, когда в Python булевы являются заглавные. Кроме того, вам нужно будет заменить пустые строки None (без кавычек)

Я пишу импортера для mongodb, который делает все это. Ниже приведен код, который я написал до сих пор.

[ПРИМЕЧАНИЕ. Мой csv использует вкладку в качестве разделителя полей. Возможно, вы захотите добавить и обработку исключений]

def getFieldnames(csvFile):
    """
    Read the first row and store values in a tuple
    """
    with open(csvFile) as csvfile:
        firstRow = csvfile.readlines(1)
        fieldnames = tuple(firstRow[0].strip('\n').split("\t"))
    return fieldnames

def writeCursor(csvFile, fieldnames):
    """
    Convert csv rows into an array of dictionaries
    All data types are automatically checked and converted
    """
    cursor = []  # Placeholder for the dictionaries/documents
    with open(csvFile) as csvFile:
        for row in islice(csvFile, 1, None):
            values = list(row.strip('\n').split("\t"))
            for i, value in enumerate(values):
                nValue = ast.literal_eval(value)
                values[i] = nValue
            cursor.append(dict(zip(fieldnames, values)))
    return cursor

Ответ 2

Вам нужно сопоставить свои строки:

data = """True,foo,1,2.3,baz
False,bar,7,9.8,qux"""

reader = csv.reader(StringIO.StringIO(data), delimiter=",")
parsed = (({'True':True}.get(row[0], False),
           row[1],
           int(row[2]),
           float(row[3]),
           row[4])
          for row in reader)
for row in parsed:
    print row

приводит к

(True, 'foo', 1, 2.3, 'baz')
(False, 'bar', 7, 9.8, 'qux')

Ответ 3

Я знаю, что это довольно старый вопрос, помеченный , но здесь ответ, который работает с Python 3. 6+, который может быть интересен людям, использующим более современные версии языка.

Он использует встроенный класс typing.NamedTuple который был добавлен в Python 3.5. Что может быть неочевидно из документации, так это то, что "тип" каждого поля может быть функцией.

В примере использования кода также используются так называемые литералы f-строки, которые не добавлялись до Python 3.6, но их использование не требуется для базовых преобразований типов данных.

#!/usr/bin/env python3.6
import ast
import csv
from typing import NamedTuple


class Record(NamedTuple):
    """ Define the fields and their types in a record. """
    IsActive : bool
    Type: str
    Price: float
    States: ast.literal_eval  # Handles string represenation of literals.

    @classmethod
    def _transform(cls: 'Record', dct: dict) -> dict:
        """ Convert string values in given dictionary to corresponding Record
            field type.
        """
        return {field: cls._field_types[field](value)
                    for field, value in dct.items()}


filename = 'test_transform.csv'

with open(filename, newline='') as file:
    for i, row in enumerate(csv.DictReader(file)):
        row = Record._transform(row)
        print(f'row {i}: {row}')

Выход:

row 0: {'IsActive': True, 'Type': 'Cellphone', 'Price': 34.0, 'States': [1, 2]}
row 1: {'IsActive': False, 'Type': 'FlatTv', 'Price': 3.5, 'States': [2]}
row 2: {'IsActive': True, 'Type': 'Screen', 'Price': 100.23, 'States': [5, 1]}
row 3: {'IsActive': True, 'Type': 'Notebook', 'Price': 50.0, 'States': [1]}

Обобщение этого путем создания базового класса, содержащего только общий метод class, не является простым из-за способа реализации typing.NamedTuple.

Чтобы избежать этой проблемы, в Python 3. 7+ dataclasses.dataclass можно было бы использовать dataclasses.dataclass потому что у них нет проблемы наследования - поэтому создание универсального базового класса, который можно использовать повторно, очень просто:

#!/usr/bin/env python3.7
import ast
import csv
from dataclasses import dataclass, fields
from typing import Type, TypeVar

T = TypeVar('T', bound='GenericRecord')

class GenericRecord:
    """ Generic base class for transforming dataclasses. """
    @classmethod
    def _transform(cls: Type[T], dict_: dict) -> dict:
        """ Convert string values in given dictionary to corresponding type. """
        return {field.name: field.type(dict_[field.name])
                    for field in fields(cls)}


@dataclass
class CSV_Record(GenericRecord):
    """ Define the fields and their types in a record.
        Field names must match column names in CSV file header.
    """
    IsActive : bool
    Type: str
    Price: float
    States: ast.literal_eval  # Handles string represenation of literals.


filename = 'test_transform.csv'

with open(filename, newline='') as file:
    for i, row in enumerate(csv.DictReader(file)):
        row = CSV_Record._transform(row)
        print(f'row {i}: {row}')

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

Ответ 4

Подкрепляет Джон Клементс и кортопию за то, что он учил меня о ast.literal_eval! Здесь то, с чем я закончил (Python 2; изменения для 3 должны быть тривиальными):

from ast import literal_eval
from csv import DictReader
import csv


def csv_data(filepath, **col_conversions):
    """Yield rows from the CSV file as dicts, with column headers as the keys.

    Values in the CSV rows are converted to Python values when possible,
    and are kept as strings otherwise.

    Specific conversion functions for columns may be specified via
    `col_conversions`: if a column header is a key in this dict, its
    value will be applied as a function to the CSV data. Specify
    `ColumnHeader=str` if all values in the column should be interpreted
    as unquoted strings, but might be valid Python literals (`True`,
    `None`, `1`, etc.).

    Example usage:

    >>> csv_data(filepath,
    ...          VariousWordsIncludingTrueAndFalse=str,
    ...          NumbersOfVaryingPrecision=float,
    ...          FloatsThatShouldBeRounded=round,
    ...          **{'Column Header With Spaces': arbitrary_function})
    """

    def parse_value(key, value):
        if key in col_conversions:
            return col_conversions[key](value)
        try:
            # Interpret the string as a Python literal
            return literal_eval(value)
        except Exception:
            # If that doesn't work, assume it an unquoted string
            return value

    with open(filepath) as f:
        # QUOTE_NONE: don't process quote characters, to avoid the value
        # `"2"` becoming the int `2`, rather than the string `'2'`.
        for row in DictReader(f, quoting=csv.QUOTE_NONE):
            yield {k: parse_value(k, v) for k, v in row.iteritems()}

(Я немного насторожен, что, возможно, я пропустил некоторые угловые случаи с цитированием. Прошу прокомментировать, если вы видите какие-либо проблемы!)

Ответ 5

Альтернатива (хотя кажется немного экстремальной) вместо использования ast.literal_eval - это модуль pyparsing, доступный на PyPi, - и посмотрите, < http://pyparsing.wikispaces.com/file/view/parsePythonValue.py является подходящим для вас или может быть легко адаптирован.

Ответ 6

Я люблю @martineau ответ. Это очень чисто.

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

Для этого просто замените эту строку:

row = CSV_Record._transform(row)

этим:

row.update(CSV_Record._transform(row))

Функция "update" обновляет строку переменной напрямую, объединяя необработанные данные из экстракта csv со значениями, преобразованными в правильный тип с помощью метода _transform.

Обратите внимание, что в обновленной версии нет строки "row =".

Надеюсь, это поможет, если у кого-то есть подобное требование.

(PS: я довольно новичок в публикации на stackoverflow, поэтому, пожалуйста, дайте мне знать, если выше не ясно)