我有以下问题。我写了一个二维列表,其中每列具有不同的类型(bool,str,int,list),到csv文件。现在我想再次从csv文件中读出数据。但我读到的每个单元都被解释为一个字符串。
如何自动将读入数据转换为正确的类型?或者更好:是否有可能告诉csv-reader每列的正确数据类型?
示例数据(如csv文件中):
IsActive,Type,Price,States
True,Cellphone,34,"[1, 2]"
,FlatTv,3.5,[2]
False,Screen,100.23,"[5, 1]"
True,Notebook, 50,[1]
作为docs explain,CSV阅读器不执行自动数据转换。您有QUOTE_NONNUMERIC格式选项,但这只会将所有非引用字段转换为浮点数。这与其他csv读者非常相似。
我不相信Python的csv模块对这种情况有任何帮助。正如其他人已经指出的那样,literal_eval()
是一个更好的选择。
以下工作和转换:
您也可以将它用于布尔值和NoneType,尽管这些必须相应地格式化以便literal_eval()
通过。 LibreOffice Calc以大写字母显示布尔值,而在Python布尔值为大写时。此外,你必须用None
(没有引号)替换空字符串
我正在为mongodb写一个进口商来做这一切。以下是我到目前为止编写的代码的一部分。
[注意:我的csv使用tab作为字段分隔符。您可能还想添加一些异常处理]
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
你必须映射你的行:
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')
我知道这是一个相当古老的问题,标记为python-2.5,但这里的答案适用于Python 3.6+,这可能是使用该语言的更新版本的人们感兴趣的。
它利用了Python 3.5中添加的内置typing.NamedTuple
类。文档中可能不明显的是每个字段的“类型”可以是一个函数。
示例用法代码也使用了所谓的f-string文字,这些文字在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]}
通过创建一个只包含泛型类方法的基类来推广它并不简单,因为实现了typing.NamedTuple
的方式。
为了避免这个问题,在Python 3.7+中,可以使用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}')
从某种意义上说,你使用哪一个并不是非常重要,因为从未创建过类的实例 - 使用一个实例只是一种在记录数据结构中指定和保存字段名称及其类型定义的简洁方法。
转发Jon Clements和cortopy教我关于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'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
的替代方案(虽然看起来有点极端)是PyPi上可用的pyparsing
模块 - 并查看http://pyparsing.wikispaces.com/file/view/parsePythonValue.py代码示例是否适合您的需求,或者可以轻松调整。
我喜欢@martineau的回答。它非常干净。
我需要的一件事是只转换几个值并将所有其他字段保留为字符串,例如将字符串作为默认值,只更新特定键的类型。
要做到这一点,只需替换此行:
row = CSV_Record._transform(row)
通过这个:
row.update(CSV_Record._transform(row))
'update'函数直接更新变量行,将来自csv数据提取的原始数据与通过'_transform'方法转换为正确类型的值合并。
请注意,更新版本中没有'row ='。
希望这有助于万一有人有类似的要求。
(PS:我在stackoverflow上发帖很新,所以如果上面的内容不清楚,请告诉我)