34

CSVファイルからデータを読み戻すと、すべてのセルが文字列として解釈されます。

  • 読み込んだデータを正しいタイプに自動的に変換するにはどうすればよいですか?
  • またはそれ以上:csvリーダーに各列の正しいデータ型を伝えるにはどうすればよいですか?

(各列が異なるタイプ(bool、str、int、整数のリスト)である2次元リストを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]
4

8 に答える 8

16

ドキュメントで説明されているように、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
于 2015-09-04T11:54:37.670 に答える
8

行をマップする必要があります。

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')
于 2012-07-26T08:56:26.137 に答える
7

とタグ付けされたかなり古い質問ですが、Python 3.6以降で機能する回答があります。これは、言語のより最新のバージョンを使用している人々にとって興味深いかもしれません。

typing.NamedTuplePython3.5で追加された組み込みクラスを活用します。ドキュメントからは明らかではないかもしれませんが、各フィールドの「タイプ」は関数である可能性があります。

使用法の例では、Python 3.6まで追加されなかったいわゆるf文字列リテラルも使用していますが、コアデータ型変換を行うためにそれらを使用する必要はありません。

#!/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', dict_: dict) -> dict:
        """ Convert string values in given dictionary to corresponding Record
            field type.
        """
        return {name: cls.__annotations__[name](value)
                    for name, value in dict_.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は、継承の問題がないため、代わりにaを使用できます。したがって、再利用できるジェネリック基本クラスの作成は簡単です。

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

ある意味では、クラスのインスタンスは作成されないため、どちらを使用するかはそれほど重要ではありません。1つを使用することは、レコードデータ構造でフィールド名とそのタイプの定義を指定および保持するためのクリーンな方法にすぎません。

Python 3.8のモジュールにATypedDictが追加されました。typingこれは、入力情報の提供にも使用できますが、実際にはのような新しい型を定義していないため、少し異なる方法で使用する必要があります。NamedTupleしたがってdataclasses、スタンドアロンの変換が必要です。働き:

#!/usr/bin/env python3.8
import ast
import csv
from dataclasses import dataclass, fields
from typing import TypedDict


def transform(dict_, typed_dict) -> dict:
    """ Convert values in given dictionary to corresponding types in TypedDict . """
    fields = typed_dict.__annotations__
    return {name: fields[name](value) for name, value in dict_.items()}


class CSV_Record_Types(TypedDict):
    """ 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


filename = 'test_transform.csv'

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

于 2019-02-20T19:49:35.630 に答える
2

ジョン・クレメンツとコルトピーに教えてくれた小道具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()}

(引用を含むいくつかのコーナーケースを見逃した可能性があるので少し警戒しています。問題が発生した場合はコメントしてください!)

于 2015-09-08T17:14:35.360 に答える
1

私も@martineauのアプローチが本当に好きで、彼のコードの本質はフィールドとタイプの間のクリーンなマッピングであるという彼のコメントに特に興味をそそられました。それは私に辞書も機能することを示唆しました。したがって、彼のテーマのバリエーションを以下に示します。それは私にとってうまくいきました。

明らかに、ディクショナリの値フィールドは実際には呼び出し可能であるため、データのマッサージや型キャストのフックを提供するために使用できます。

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}')

出力

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]}
于 2019-10-26T02:55:43.380 に答える
0

使用する代わりに(少し極端に思えますが)ast.literal_evalpyparsingPyPiで利用可能なモジュールを使用して、http://pyparsing.wikispaces.com/file/view/parsePythonValue.pyコードサンプルが適切かどうかを確認してください。必要な、または簡単に適応させることができます。

于 2012-07-26T14:29:36.523 に答える
0

@martineauの答えが大好きです。とてもきれいです。

必要なことの1つは、いくつかの値のみを変換し、他のすべてのフィールドを文字列のままにすることでした。たとえば、文字列をデフォルトとして使用し、特定のキーのタイプを更新するだけです。

これを行うには、次の行を置き換えるだけです。

row = CSV_Record._transform(row)

これによって:

row.update(CSV_Record._transform(row))

' update '関数は、変数を直接更新し、csv抽出からの生データを' _transform 'メソッドによって正しいタイプに変換された値とマージします。

更新されたバージョンには「=」がないことに注意してください。

誰かが同様の要件を持っている場合にこれが役立つことを願っています。

(追記:stackoverflowに投稿するのは初めてなので、上記が明確でない場合はお知らせください)

于 2019-07-04T13:19:30.603 に答える
0

これは、複数のcsv形式、一部の列で実行する追加のカスタムデータラングリング、およびリストのリストまたはタプルのタプルとして出力する必要がある場合の質問に対する私の見解です。

列の型はPythonコードの外部のデータベースに格納されるため、型は文字列として表示されます。また、必要に応じていくつかのカスタムタイプを追加することもできます。

私はこれを超大型ファイルに対してテストしていませんが、私のプロダクションコードではパンダを使用しており、このコードはいくつかのテストセットアップ用にここにあります。しかし、csvからのすべてのデータが一度に読み込まれるため、これは他のいくつかの回答よりも多くのメモリを消費すると思います。

dict_headers_type = {
    "IsActive": "bool",
    "Type": "str",
    "Price": "float",
    "State": "list",
}

dict_converters = {
    "bool": x: bool(x),
    "float": x: float(x),
    "list": x: ast.literal_eval(x),
}

dict_header_converter = {
    header: dict_converters[my_type]
    for header, my_type in dict_headers_type.items()
    if my_type in dict_converters.keys()
}

その場で、変換を実行できます。

with open(csv_path) as f:
    data = [line for line in csv.reader(f)]

# list of the converters to apply
ls_f = [
    dict_header_converter[header]
    if header in dict_header_converter.keys() else None
    for header in data[0]
]


ls_records = [f(datapoint) if f else datapoint
     for f, datapoint in zip(ls_f, row)]
    for row in data[1:]]

# to add headers, if needed:
ls_records.insert(0, data[0])

出力:

[
  ['IsActive','Type','Price','State']
  [True, 'Cellphone', 34.0, [1, 2]],
  [False, 'FlatTv', 3.5, [2]],
  [True, 'Screen', 100.23, [5, 1]],
  [True, 'Notebook', 50.0, [1]],
]
于 2021-08-25T07:06:29.523 に答える