csvのheaderを気にしてOrderedDictで読む方法

問題

例えば以下のようなcsvがあるときに、読み込んだ後にcsvのheaderの順序を保持してほしいという場合がある。

"age"    "name"
"20"    "foo"

カジュアルに以下の様なコードで読んだ場合に順序は不定。

import csv
import sys


r = csv.DictReader(sys.stdin, delimiter="\t")
print(list(r))

順序は不定。

[{'name': 'foo', 'age': '20'}]
[{'name': 'foo', 'age': '20'}]
[{'age': '20', 'name': 'foo'}]

対応方法

csv.DictReaderを継承するしか無いの?

例えば以下の様な感じにする。dict_factoryみたいな形で引数になっているとありがたいのだけれど。

import csv
import sys
from collections import OrderedDict


class OrderedDictReader(csv.DictReader):
    def __next__(self):
        if self.line_num == 0:
            # Used only for its side effect.
            self.fieldnames
        row = next(self.reader)
        self.line_num = self.reader.line_num

        # unlike the basic reader, we prefer not to return blanks,
        # because we will typically wind up with a dict full of None
        # values
        while row == []:
            row = next(self.reader)
        d = OrderedDict(zip(self.fieldnames, row))
        lf = len(self.fieldnames)
        lr = len(row)
        if lf < lr:
            d[self.restkey] = row[lf:]
        elif lf > lr:
            for key in self.fieldnames[lr:]:
                d[key] = self.restval
        return d


r = OrderedDictReader(sys.stdin, delimiter="\t")
print(list(r))

今度は大丈夫。

[OrderedDict([('age', '20'), ('name', 'foo')])]
[OrderedDict([('age', '20'), ('name', 'foo')])]
[OrderedDict([('age', '20'), ('name', 'foo')])]

logging入門

長すぎにならない程度に使い方をまとめてみる。

loggingの使い方

ライブラリの利用者

既に存在するアプリを実行するファイルの場合

if __name__ == "__main__":
    import logging
    logging.basicConfig(level=logging.DEBUG)  # or INFO or WARNING or ERROR
    run()

個人的には時間もみたいので以下の様にしている。

 logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")

設定可能な属性について詳しくはここ

ライブラリの作成者

ライブラリ内の話。基本的にはモジュール毎に __name__ でロガーを作る。アプリを作成する際にはご自由に。

import logging
logger = logging.getLogger(__name__)


# おもに問題を診断するときにのみ関心があるような、詳細な情報。
logger.debug("hmm")
# 想定された通りのことが起こったことの確認。
logger.info("hmm")
# 想定外のことが起こった、または問題が近く起こりそうである (例えば、'disk space low') ことの表示。
logger.warning("hmm")
# より重大な問題により、ソフトウェアがある機能を実行できないこと。
logger.error("hmm")
# プログラム自体が実行を続けられないことを表す、重大なエラー。
logger.critical("hmm")

最悪、debug,info,errorだけ使えば良い。

例外発生時のtracebackを出力したい場合

stack traceもログに出力したい場合には exc_info=True をつける

try:
    foo()
except:
    logger.warning("hmm", exc_info=True)

うるさいloggerを黙らせる

ロガーの名前が foo.bar.boo の場合

logging.getLogger("foo.bar.boo").setLevel(logging.CRITICAL)

(sentryで適切にaggregationしたい場合)

loggerに渡す文字列をaggregation用のidとして利用できるようにする。具体的にはformat文字列などを利用して文字列を生成しない。

# ok
logger.info("name: %s", name)
# ng
logger.info("name: {}".format(name))

後者は文字列フォーマットの適用結果がsentryに送られてしまうため。適切に発生したエラーをaggregateできない。

ses also