从CSV文件中读取数据,并从字符串转换为正确的数据类型

问题描述 投票:17回答:6

当我从CSV文件读回数据时,每个单元格都被解释为字符串。

  • 如何自动将读取的数据转换为正确的类型?
  • 或者更好:我如何告诉csv阅读器每列的正确数据类型?

(我写了一个二维列表,其中每一列都是不同类型的(布尔,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]
python csv python-2.5
6个回答
12
投票

作为docs explain,CSV阅读器不执行自动数据转换。您具有QUOTE_NONNUMERIC格式选项,但这只会将所有未引用的字段转换为浮点数。这与其他csv阅读器非常相似。

我不认为Python的csv模块对于这种情况完全没有帮助。正如其他人已经指出的那样,literal_eval()是更好的选择。

以下内容确实可以工作并进行转换:

  • 字符串
  • INT
  • 浮子
  • 列表
  • 字典

您也可以将其用于布尔值和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

7
投票

您必须映射行:

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+的答案,这可能是使用最新语言版本的人们所感兴趣的。

[它利用了Python 3.5中添加的内置typing.NamedTuple类。从文档中可能看不到的是每个字段的“类型”可以是一个函数。

示例用法代码还使用了所谓的typing.NamedTuple文字,这些文字直到Python 3.6才添加,但是不需要使用它们来进行核心数据类型转换。

f-string

输出:

#!/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]} 的方式。

[为了避免该问题,在Python 3.7+中,可以使用typing.NamedTuple来代替,因为它们没有继承问题-因此创建可以重用的通用基类很简单:

dataclasses.dataclass

从某种意义上说,使用哪个并不是很重要,因为从未创建过该类的实例-使用一个只是一种在记录数据结构中指定并保存字段名称及其类型的定义的干净方法。


2
投票

Jon Clements和Cortopy的道具,教我有关dataclasses.dataclass!这就是我最终要使用的内容(Python 2;对3的更改应该是微不足道的):

#!/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}')

((我有点担心,我可能会错过一些涉及报价的特殊情况。如果发现任何问题,请发表评论!)


0
投票

替代使用ast.literal_eval的替代方法(尽管看起来有些极端)是PyPi上可用的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's 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's 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()} 模块-并查看ast.literal_eval代码示例是否适合您的要求,或者可以轻松实现适配。


0
投票

我爱pyparsing的答案。非常干净。

我需要做的是只转换几个值,而将所有其他字段保留为字符串,例如将字符串作为默认值,只是更新特定键的类型。

为此,只需替换此行:

http://pyparsing.wikispaces.com/file/view/parsePythonValue.py

此人:

@martineau

'update'函数直接更新变量row,将来自csv提取的原始数据与通过'_ transform'方法转换为正确类型的值合并。

注意,在更新的版本中没有'row ='。

希望这对任何有类似要求的人都有帮助。

(PS:我对在stackoverflow上发布内容还很陌生,所以如果以上内容不清楚,请让我知道)

© www.soinside.com 2019 - 2024. All rights reserved.