Lorsque je relis des données à partir d'un fichier CSV, chaque cellule est interprétée comme une chaîne.
(J'ai écrit une liste bidimensionnelle, où chaque colonne est d'un type différent (bool, str, int, liste d'entiers), vers un fichier CSV.)
Exemples de données (dans un fichier CSV):
IsActive,Type,Price,States
True,Cellphone,34,"[1, 2]"
,FlatTv,3.5,[2]
False,Screen,100.23,"[5, 1]"
True,Notebook, 50,[1]
Comme le les documents expliquent , le lecteur CSV n'effectue pas la conversion automatique des données. Vous avez l'option de format QUOTE_NONNUMERIC, mais cela ne convertirait que tous les champs non entre guillemets en flottants. Il s'agit d'un comportement très similaire aux autres lecteurs csv.
Je ne pense pas que le module csv de Python serait d'une quelconque aide pour ce cas. Comme d'autres l'ont déjà souligné, literal_eval()
est un bien meilleur choix.
Ce qui suit fonctionne et se convertit:
Vous pouvez également l'utiliser pour les booléens et NoneType, bien que ceux-ci doivent être formatés en conséquence pour que literal_eval()
passe. LibreOffice Calc affiche les booléens en majuscules, quand en Python les booléens sont en majuscules. De plus, vous devrez remplacer les chaînes vides par None
(sans guillemets)
J'écris un importateur pour mongodb qui fait tout cela. Ce qui suit fait partie du code que j'ai écrit jusqu'à présent.
[REMARQUE: Mon csv utilise tab comme délimiteur de champ. Vous voudrez peut-être également ajouter une gestion des exceptions]
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
Vous devez mapper vos lignes:
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
résulte en
(True, 'foo', 1, 2.3, 'baz')
(False, 'bar', 7, 9.8, 'qux')
Je sais que c'est une question assez ancienne, étiquetée python-2.5 , mais voici la réponse qui fonctionne avec Python 3.6+ qui pourrait intéresser les gens qui utilisent plus d'up-) versions à jour de la langue.
Il s'appuie sur le intégré typing.NamedTuple
classe qui a été ajoutée dans Python 3.5. Ce qui peut ne pas être évident dans la documentation est que le "type" de chaque champ peut être une fonction.
L'exemple de code d'utilisation utilise également des soi-disant f-string littéraux qui n'ont pas été ajoutés avant Python 3.6, mais leur utilisation n'est pas requise pour faire les données de base transformations de type.
#!/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}')
Production:
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]}
Généraliser cela en créant une classe de base avec juste la méthode de classe générique n'est pas simple à cause de la façon dont typing.NamedTuple
est implémenté.
Pour éviter ce problème, dans Python 3.7+, un dataclasses.dataclass
pourrait être utilisé à la place car ils n'ont pas de problème d'héritage - donc créer une classe de base générique qui peut être réutilisée est simple:
#!/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}')
Dans un sens, il n'est pas vraiment très important de choisir celle que vous utilisez, car une instance de la classe n'a jamais été créée - en utiliser une est juste un moyen propre de spécifier et de conserver une définition des noms de champ et de leur type dans une structure de données d'enregistrement.
Accessoires à Jon Clements et cortopy pour m'avoir enseigné ast.literal_eval
! Voici ce que j'ai fini par faire (Python 2; les changements pour 3 devraient être triviaux):
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()}
(Je suis un peu méfiant à l'idée d'avoir peut-être manqué quelques cas de coin impliquant des citations. Veuillez commenter si vous voyez des problèmes!)
J'ai trop aimé l'approche de @ martineau et j'ai été particulièrement intrigué par son commentaire selon lequel l'essence de son code était une correspondance claire entre les champs et les types. Cela m'a suggéré qu'un dictionnaire fonctionnerait également. D'où la variation de son thème ci-dessous. Cela a bien fonctionné pour moi.
De toute évidence, le champ de valeur dans le dictionnaire est vraiment juste un appelable et pourrait donc être utilisé pour fournir un crochet pour le massage des données ainsi que la transtypage si vous le souhaitez.
import ast
import csv
fix_type = {'IsActive': bool, 'Type': str, 'Price': float, 'States': ast.literal_eval}
filename = 'test_transform.csv'
with open(filename, newline='') as file:
for i, row in enumerate(csv.DictReader(file)):
row = {k: fix_type[k](v) for k, v in row.items()}
print(f'row {i}: {row}')
Production
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]}
Une alternative (bien que cela semble un peu extrême) au lieu d'utiliser ast.literal_eval
est le module pyparsing
disponible sur PyPi - et voyez si le http://pyparsing.wikispaces.com/file/view/parsePythonValue.py exemple de code est approprié pour quoi vous avez besoin, ou peut être facilement adapté.
J'adore @ martinea réponse. C'est très propre.
Une chose dont j'avais besoin était de convertir seulement quelques valeurs et de laisser tous les autres champs sous forme de chaînes, comme avoir des chaînes par défaut et simplement mettre à jour le type de clés spécifiques.
Pour ce faire, remplacez simplement cette ligne:
row = CSV_Record._transform(row)
par celui-ci:
row.update(CSV_Record._transform(row))
La fonction ' update ' met à jour la ligne de variable directement, en fusionnant le brut les données de l'extrait csv avec les valeurs converties au type correct par la méthode ' _ transform '.
Notez qu'il n'y a pas de ligne ' =' dans la version mise à jour.
J'espère que cela vous aidera au cas où quelqu'un aurait une exigence similaire.
(PS: je suis assez nouveau pour publier sur stackoverflow, alors s'il vous plaît laissez-moi savoir si ce qui précède n'est pas clair)