小さいツールを組み合わせる

これまで2回に渡ってPythonを使って実用的なツールを作成する方法を紹介してきた。1つはCSVデータをパースして処理する方法、もう1つはJSONデータのパースやダンプの方法だ。それ専用にモジュールが用意されているので、その機能を使って操作すればよい。どちらも簡単なサンプルだった。

例えば、次のサンプルコード(csv_read.py)は、CSVデータをファイルから読み込んでパースし、行ごとに(セルごとにとも言える)処理するといった内容になっている。

#!/usr/bin/env python3

import csv

with open('addresses.csv') as fp:
    reader = csv.reader(fp)
    for row in reader:
        for i in range(0,4):
            print('[' + row[i] + ']', end='')
        print()

また、次のサンプル(genjson2.py)はPythonのディクショナリとして用意したデータをJSON形式の文字列に変換して出力するというものだ。

#!/usr/bin/env python3

import json

# JSONに相当するデータをディクショナリで用意
jsondic = {"status":"success","data":{"ipv4":"93.184.216.34","continent_name":"North America","country_name":"United States","subdivision_1_name":"Massachusetts","subdivision_2_name":None,"city_name":"Norwell","latitude":"42.15080","longitude":"-70.82280"}}

# ディクショナリデータを形式指定なしでJSON文字列として出力
print('----')
print(json.dumps(jsondic))

# ディクショナリデータをインデント指定してJSON文字列として出力
print('----')
print(json.dumps(jsondic, indent=4))

どちらも簡単なサンプルだが、これらのサンプルを組み合わせることでさらに別のツールが作れることにお気づきだろうか。Pythonはかなり短いコードで機能的に動作するツールを作成することができるのだ。

CSVデータをJSONデータに変換して出力するコマンド

今回は上記2つのツールを組み合わせて、CSVデータをJSONデータに変換して出力するコマンド(csv2json.py)を作ってみよう。まず、CSVを操作するツールを作成した際に用いた住所データにヘッダを追加し、次のようなCSVデータ「addresses.csv」を用意する。

"全国地方公共団体コード","(旧)郵便番号","郵便番号","都道府県名","市区町村名","町域名","都道府県名","市区町村名","町域名","一町域が二以上の郵便番号で表される場合の表示","小字毎に番地が起番されている町域の表示","丁目を有する町域の場合の表示","一つの郵便番号で二以上の町域を表す場合の表示","更新の表示","変更理由"
13101,"100  ","1000000","トウキョウト","チヨダク","イカニケイサイガナイバアイ","東京都","千代田区","以下に掲載がない場合",0,0,0,0,0,0
13101,"102  ","1020072","トウキョウト","チヨダク","イイダバシ","東京都","千代田区","飯田橋",0,0,1,0,0,0
13101,"102  ","1020082","トウキョウト","チヨダク","イチバンチョウ","東京都","千代田区","一番町",0,0,0,0,0,0
13101,"101  ","1010032","トウキョウト","チヨダク","イワモトチョウ","東京都","千代田区","岩本町",0,0,1,0,0,0
13101,"101  ","1010047","トウキョウト","チヨダク","ウチカンダ","東京都","千代田区","内神田",0,0,1,0,0,0
13101,"100  ","1000011","トウキョウト","チヨダク","ウチサイワイチョウ","東京都","千代田区","内幸町",0,0,1,0,0,0
13101,"100  ","1000004","トウキョウト","チヨダク","オオテマチ(ツギノビルヲノゾク)","東京都","千代田区","大手町(次のビルを除く)",0,0,1,0,0,0
13101,"100  ","1006890","トウキョウト","チヨダク","オオテマチジェイエイビル(チカイ・カイソウフメイ)","東京都","千代田区","大手町JAビル(地階・階層不明)",0,0,0,0,0,0
13101,"100  ","1006801","トウキョウト","チヨダク","オオテマチジェイエイビル(1カイ)","東京都","千代田区","大手町JAビル(1階)",0,0,0,0,0,0
13101,"100  ","1006802","トウキョウト","チヨダク","オオテマチジェイエイビル(2カイ)","東京都","千代田区","大手町JAビル(2階)",0,0,0,0,0,0
13101,"100  ","1006803","トウキョウト","チヨダク","オオテマチジェイエイビル(3カイ)","東京都","千代田区","大手町JAビル(3階)",0,0,0,0,0,0
13101,"100  ","1006804","トウキョウト","チヨダク","オオテマチジェイエイビル(4カイ)","東京都","千代田区","大手町JAビル(4階)",0,0,0,0,0,0
13101,"100  ","1006805","トウキョウト","チヨダク","オオテマチジェイエイビル(5カイ)","東京都","千代田区","大手町JAビル(5階)",0,0,0,0,0,0
13101,"100  ","1006806","トウキョウト","チヨダク","オオテマチジェイエイビル(6カイ)","東京都","千代田区","大手町JAビル(6階)",0,0,0,0,0,0
13101,"100  ","1006807","トウキョウト","チヨダク","オオテマチジェイエイビル(7カイ)","東京都","千代田区","大手町JAビル(7階)",0,0,0,0,0,0
13101,"100  ","1006808","トウキョウト","チヨダク","オオテマチジェイエイビル(8カイ)","東京都","千代田区","大手町JAビル(8階)",0,0,0,0,0,0
13101,"100  ","1006809","トウキョウト","チヨダク","オオテマチジェイエイビル(9カイ)","東京都","千代田区","大手町JAビル(9階)",0,0,0,0,0,0
13101,"100  ","1006810","トウキョウト","チヨダク","オオテマチジェイエイビル(10カイ)","東京都","千代田区","大手町JAビル(10階)",0,0,0,0,0,0
13101,"100  ","1006811","トウキョウト","チヨダク","オオテマチジェイエイビル(11カイ)","東京都","千代田区","大手町JAビル(11階)",0,0,0,0,0,0
13101,"100  ","1006812","トウキョウト","チヨダク","オオテマチジェイエイビル(12カイ)","東京都","千代田区","大手町JAビル(12階)",0,0,0,0,0,0

日本郵便が提供している郵便番号データ(CSV)にはヘッダは含まれていない。ただし、説明文章にはヘッダに相当するものが説明されており、その説明文言を1行目に書き込んだものが上記データである。

この1行目をJSONデータの鍵とし、1行目以外を値としてJSONデータを作成するコードを考える。作成はそれほど難しくない。次に示すcsv2json.pyのように、CSVデータをパースしながらディクショナリおよびリストデータを作成して行き、最後にjson.dumps()で文字列に変換して出力すればOKだ。

#!/usr/bin/env python3

import sys
import csv
import json

# 住所データを保持するリスト
jsonlist = []

with open(sys.argv[1]) as fp_r:
    # 1行づつ読み込んで処理
    reader = csv.reader(fp_r)

    # 最初の1行目はヘッダデータとして処理する
    header = {}
    is_header = True

    for row in reader:
        if is_header:
            # 1行目をヘッダとして確保
            header = row
            is_header = False
        else:
            # 2行目以降はJSONディクショナリデータとして確保し
            # リストへ追加
            jsondic = {}
            for i in range(len(header)):
                jsondic[header[i]] = row[i]
            jsonlist.append(jsondic)

print(json.dumps(jsonlist, ensure_ascii=False, indent=4))

このコマンドを実行すると次のような結果を得ることができる。

% ./csv2json.py addresses.csv
[
    {
        "全国地方公共団体コード": "13101",
        "(旧)郵便番号": "100  ",
        "郵便番号": "1000000",
        "都道府県名": "東京都",
        "市区町村名": "千代田区",
        "町域名": "以下に掲載がない場合",
        "一町域が二以上の郵便番号で表される場合の表示": "0",
        "小字毎に番地が起番されている町域の表示": "0",
        "丁目を有する町域の場合の表示": "0",
        "一つの郵便番号で二以上の町域を表す場合の表示": "0",
        "更新の表示": "0",
        "変更理由": "0"
    },
    {
        "全国地方公共団体コード": "13101",
        "(旧)郵便番号": "102  ",
        "郵便番号": "1020072",
        "都道府県名": "東京都",
        "市区町村名": "千代田区",
        "町域名": "飯田橋",
        "一町域が二以上の郵便番号で表される場合の表示": "0",
        "小字毎に番地が起番されている町域の表示": "0",
        "丁目を有する町域の場合の表示": "1",
        "一つの郵便番号で二以上の町域を表す場合の表示": "0",
        "更新の表示": "0",
        "変更理由": "0"
    },
    {
        "全国地方公共団体コード": "13101",
        "(旧)郵便番号": "102  ",
        "郵便番号": "1020082",
        "都道府県名": "東京都",
        "市区町村名": "千代田区",
        "町域名": "一番町",
        "一町域が二以上の郵便番号で表される場合の表示": "0",
        "小字毎に番地が起番されている町域の表示": "0",
        "丁目を有する町域の場合の表示": "0",
        "一つの郵便番号で二以上の町域を表す場合の表示": "0",
        "更新の表示": "0",
        "変更理由": "0"
    },
    {
        "全国地方公共団体コード": "13101",
        "(旧)郵便番号": "101  ",
        "郵便番号": "1010032",
        "都道府県名": "東京都",
        "市区町村名": "千代田区",
        "町域名": "岩本町",
        "一町域が二以上の郵便番号で表される場合の表示": "0",
        "小字毎に番地が起番されている町域の表示": "0",
        "丁目を有する町域の場合の表示": "1",
        "一つの郵便番号で二以上の町域を表す場合の表示": "0",
        "更新の表示": "0",
        "変更理由": "0"
    },
    {
        "全国地方公共団体コード": "13101",
        "(旧)郵便番号": "101  ",
        "郵便番号": "1010047",
        "都道府県名": "東京都",
        "市区町村名": "千代田区",
        "町域名": "内神田",
        "一町域が二以上の郵便番号で表される場合の表示": "0",
        "小字毎に番地が起番されている町域の表示": "0",
        "丁目を有する町域の場合の表示": "1",
        "一つの郵便番号で二以上の町域を表す場合の表示": "0",
        "更新の表示": "0",
        "変更理由": "0"
    },
    {
        "全国地方公共団体コード": "13101",
        "(旧)郵便番号": "100  ",
        "郵便番号": "1000011",
        "都道府県名": "東京都",
        "市区町村名": "千代田区",
        "町域名": "内幸町",
        "一町域が二以上の郵便番号で表される場合の表示": "0",
        "小字毎に番地が起番されている町域の表示": "0",
        "丁目を有する町域の場合の表示": "1",
        "一つの郵便番号で二以上の町域を表す場合の表示": "0",
        "更新の表示": "0",
        "変更理由": "0"
    },
    {
        "全国地方公共団体コード": "13101",
        "(旧)郵便番号": "100  ",
        "郵便番号": "1000004",
        "都道府県名": "東京都",
        "市区町村名": "千代田区",
        "町域名": "大手町(次のビルを除く)",
        "一町域が二以上の郵便番号で表される場合の表示": "0",
        "小字毎に番地が起番されている町域の表示": "0",
        "丁目を有する町域の場合の表示": "1",
        "一つの郵便番号で二以上の町域を表す場合の表示": "0",
        "更新の表示": "0",
        "変更理由": "0"
    },
    {
        "全国地方公共団体コード": "13101",
        "(旧)郵便番号": "100  ",
        "郵便番号": "1006890",
        "都道府県名": "東京都",
        "市区町村名": "千代田区",
        "町域名": "大手町JAビル(地階・階層不明)",
        "一町域が二以上の郵便番号で表される場合の表示": "0",
        "小字毎に番地が起番されている町域の表示": "0",
        "丁目を有する町域の場合の表示": "0",
        "一つの郵便番号で二以上の町域を表す場合の表示": "0",
        "更新の表示": "0",
        "変更理由": "0"
    },
    {
        "全国地方公共団体コード": "13101",
        "(旧)郵便番号": "100  ",
        "郵便番号": "1006801",
        "都道府県名": "東京都",
        "市区町村名": "千代田区",
        "町域名": "大手町JAビル(1階)",
        "一町域が二以上の郵便番号で表される場合の表示": "0",
        "小字毎に番地が起番されている町域の表示": "0",
        "丁目を有する町域の場合の表示": "0",
        "一つの郵便番号で二以上の町域を表す場合の表示": "0",
        "更新の表示": "0",
        "変更理由": "0"
    },
    {
        "全国地方公共団体コード": "13101",
        "(旧)郵便番号": "100  ",
        "郵便番号": "1006802",
        "都道府県名": "東京都",
        "市区町村名": "千代田区",
        "町域名": "大手町JAビル(2階)",
        "一町域が二以上の郵便番号で表される場合の表示": "0",
        "小字毎に番地が起番されている町域の表示": "0",
        "丁目を有する町域の場合の表示": "0",
        "一つの郵便番号で二以上の町域を表す場合の表示": "0",
        "更新の表示": "0",
        "変更理由": "0"
    },
    {
        "全国地方公共団体コード": "13101",
        "(旧)郵便番号": "100  ",
        "郵便番号": "1006803",
        "都道府県名": "東京都",
        "市区町村名": "千代田区",
        "町域名": "大手町JAビル(3階)",
        "一町域が二以上の郵便番号で表される場合の表示": "0",
        "小字毎に番地が起番されている町域の表示": "0",
        "丁目を有する町域の場合の表示": "0",
        "一つの郵便番号で二以上の町域を表す場合の表示": "0",
        "更新の表示": "0",
        "変更理由": "0"
    },
    {
        "全国地方公共団体コード": "13101",
        "(旧)郵便番号": "100  ",
        "郵便番号": "1006804",
        "都道府県名": "東京都",
        "市区町村名": "千代田区",
        "町域名": "大手町JAビル(4階)",
        "一町域が二以上の郵便番号で表される場合の表示": "0",
        "小字毎に番地が起番されている町域の表示": "0",
        "丁目を有する町域の場合の表示": "0",
        "一つの郵便番号で二以上の町域を表す場合の表示": "0",
        "更新の表示": "0",
        "変更理由": "0"
    },
    {
        "全国地方公共団体コード": "13101",
        "(旧)郵便番号": "100  ",
        "郵便番号": "1006805",
        "都道府県名": "東京都",
        "市区町村名": "千代田区",
        "町域名": "大手町JAビル(5階)",
        "一町域が二以上の郵便番号で表される場合の表示": "0",
        "小字毎に番地が起番されている町域の表示": "0",
        "丁目を有する町域の場合の表示": "0",
        "一つの郵便番号で二以上の町域を表す場合の表示": "0",
        "更新の表示": "0",
        "変更理由": "0"
    },
    {
        "全国地方公共団体コード": "13101",
        "(旧)郵便番号": "100  ",
        "郵便番号": "1006806",
        "都道府県名": "東京都",
        "市区町村名": "千代田区",
        "町域名": "大手町JAビル(6階)",
        "一町域が二以上の郵便番号で表される場合の表示": "0",
        "小字毎に番地が起番されている町域の表示": "0",
        "丁目を有する町域の場合の表示": "0",
        "一つの郵便番号で二以上の町域を表す場合の表示": "0",
        "更新の表示": "0",
        "変更理由": "0"
    },
    {
        "全国地方公共団体コード": "13101",
        "(旧)郵便番号": "100  ",
        "郵便番号": "1006807",
        "都道府県名": "東京都",
        "市区町村名": "千代田区",
        "町域名": "大手町JAビル(7階)",
        "一町域が二以上の郵便番号で表される場合の表示": "0",
        "小字毎に番地が起番されている町域の表示": "0",
        "丁目を有する町域の場合の表示": "0",
        "一つの郵便番号で二以上の町域を表す場合の表示": "0",
        "更新の表示": "0",
        "変更理由": "0"
    },
    {
        "全国地方公共団体コード": "13101",
        "(旧)郵便番号": "100  ",
        "郵便番号": "1006808",
        "都道府県名": "東京都",
        "市区町村名": "千代田区",
        "町域名": "大手町JAビル(8階)",
        "一町域が二以上の郵便番号で表される場合の表示": "0",
        "小字毎に番地が起番されている町域の表示": "0",
        "丁目を有する町域の場合の表示": "0",
        "一つの郵便番号で二以上の町域を表す場合の表示": "0",
        "更新の表示": "0",
        "変更理由": "0"
    },
    {
        "全国地方公共団体コード": "13101",
        "(旧)郵便番号": "100  ",
        "郵便番号": "1006809",
        "都道府県名": "東京都",
        "市区町村名": "千代田区",
        "町域名": "大手町JAビル(9階)",
        "一町域が二以上の郵便番号で表される場合の表示": "0",
        "小字毎に番地が起番されている町域の表示": "0",
        "丁目を有する町域の場合の表示": "0",
        "一つの郵便番号で二以上の町域を表す場合の表示": "0",
        "更新の表示": "0",
        "変更理由": "0"
    },
    {
        "全国地方公共団体コード": "13101",
        "(旧)郵便番号": "100  ",
        "郵便番号": "1006810",
        "都道府県名": "東京都",
        "市区町村名": "千代田区",
        "町域名": "大手町JAビル(10階)",
        "一町域が二以上の郵便番号で表される場合の表示": "0",
        "小字毎に番地が起番されている町域の表示": "0",
        "丁目を有する町域の場合の表示": "0",
        "一つの郵便番号で二以上の町域を表す場合の表示": "0",
        "更新の表示": "0",
        "変更理由": "0"
    },
    {
        "全国地方公共団体コード": "13101",
        "(旧)郵便番号": "100  ",
        "郵便番号": "1006811",
        "都道府県名": "東京都",
        "市区町村名": "千代田区",
        "町域名": "大手町JAビル(11階)",
        "一町域が二以上の郵便番号で表される場合の表示": "0",
        "小字毎に番地が起番されている町域の表示": "0",
        "丁目を有する町域の場合の表示": "0",
        "一つの郵便番号で二以上の町域を表す場合の表示": "0",
        "更新の表示": "0",
        "変更理由": "0"
    },
    {
        "全国地方公共団体コード": "13101",
        "(旧)郵便番号": "100  ",
        "郵便番号": "1006812",
        "都道府県名": "東京都",
        "市区町村名": "千代田区",
        "町域名": "大手町JAビル(12階)",
        "一町域が二以上の郵便番号で表される場合の表示": "0",
        "小字毎に番地が起番されている町域の表示": "0",
        "丁目を有する町域の場合の表示": "0",
        "一つの郵便番号で二以上の町域を表す場合の表示": "0",
        "更新の表示": "0",
        "変更理由": "0"
    }
]
%

ご覧のように、CSVデータがJSONデータに変換されていることを確認できる。このコードは結構汎用性があり、CSVで同じ形式のデータを用意してあれば、それをここで説明したようなJSON形式に変換することが可能だ。

こんな感じでPythonでは簡単に実用的なツールを作成することができる。C言語で作ればもっと高速に動作するものを作成することもできるが、コードの量は増えるし、Pythonと比べると理解しづらい記述になってしまうところがある。何度も言ってきたが、Pythonは学習が容易で、費用対効果の高いプログラミング言語だ。ぜひともこれまで3回にわたって取り上げたツールなどを実際に試し、Pythonプログラミングを体験していただきたい。