1
1

More than 3 years have passed since last update.

PythonでCSVから入力してjsonに出力してexeにする時の注意点

Last updated at Posted at 2020-06-06

 タイトルに通り、PythonでCSVから入力してjsonに出力してexeにする時の注意点を備忘録がてら記しておきます。実装例は完全に個人用のツールなので、概要だけ話します。重要なのはエラーメッセージと対処法。

デコードエラーを起こすCSV

 まずcsvを読み込みます。input.csvというcsvにデータを用意しておいて、それを読み取ります。

import csv

csv_file = open("./input.csv", "r")
c = csv.DictReader(csv_file)

 いきなりですが、ここでcsvの中身を見ようとすると(たぶん)以下のようなエラーになります。

Exception has occurred: UnicodeDecodeError
'cp932' codec can't decode byte 0xef in position 0: illegal multibyte sequence
  File "C:\Users\hoge\fuga.py", line 19, in <module>
    for row in c:

 デコードできないと叱られます。これはpythonをutf-8のファイルをshift-jisにしようとするからです。以下のように修正する必要があります。

csv_file = open("input.csv", "r",encoding="utf-8-sig")

全て同じデータになるjson

 今回いちばん面食らった部分。

#csvからパラメータだけ抜粋
params = []
for row in c:
    points.append(row)
n = len(params)

#jsonを読み込んでパラメータの枠を確保する
import json
json_open = open('sample.json', 'r')
j = json.load(json_open)
for i in range(n-1):
    j["Movements"].append(j["Movements"][0])

 下の段で少し不格好なことをしてまして、それがトラブルの元だったのですが、sample.jsonにはデータの雛形がありまして、それをパラメータの行数nまで拡張するために、雛形をn-1回コピーするという方法を取りました。この結果、無事にjというjsonデータのMovementsというキーにn個の枠ができたので、後はfor文をひたすら回してデータを入れていきます。

 ……が、データを入れ終わった後、なぜかすべての行が同じデータになるという謎の結果に悩まされました。

 少しデバッグした結果、あらゆる入力ですべての行が書き換えられてしまうということがわかり、これはすべての行が同じデータを参照しているということがわかりました。見かけのデータはn行でも、すべて1つのデータを参照しているだけであり、どれか1つに改変を加えたらすべてのデータが影響を受けるのです。

 なぜこんなことになってしまったのか。

 intstrのような型の場合、=で変数をコピーしても、なにか変更を加えた時点で「別の変数」と見なされてIDが割り振られますが、list=で変数をコピー(実はコピーできていない)した後に編集したらオリジナルデータが編集されるのです。……というのは知識としては知っていましたが、dictもそうでしたね。解決策としては、ハードコピーという手法で変数をコピーするしかないです。

#csvからパラメータだけ抜粋
params = []
for row in c:
    points.append(row)
n = len(params)

#jsonを読み込んでパラメータの枠を確保する
import json
import copy
json_open = open('sample.json', 'r')
j = json.load(json_open)
for i in range(n-1):
    new_j = copy.deepcopy(j["Movements"][0])
    j["Movements"].append(new_j)

 今回の事例では、ただのcopy(浅いコピー)ではなくdeepcopy(深いコピー)でないとダメでした。

動かないexe

 Pythonを持ってない人でも動かせるようにexe化してくれるモジュールで、pyinstallerというものがあります。これをpipしようと思ったらなぜかエラーでインストールできず、ぐぐったらpipのバージョンダウン(2018)すれば良いということなのでそれで成功しました。今それを再現しようとしたらなぜか最新のpipでも成功してしまったのでエラー再現不可能です。申し訳ない。

 それで無事pyinstallerを使ってexe化したのですが、実行してもすぐに終了してしまう。デバッグ用に入れた先頭のprintすらも吐かないので、どうやらimportの時点で問題が起こっているようです。なので、実際に入れた以下のモジュール群を順々に魔女狩りしていく。

import json
import pprint
import csv
import cmath
import math
import numpy as np
import copy
import os

 結果的にはnumpyが原因でした。幸いにも、numpyが必要な部分はプログラムで使わなかったので(np.piを使っていたが必要なくなった)このままモジュールを削除。もしnumpyを使ってゴリゴリに計算するプログラムをexe化しなければいけない場合は再度考えましょう……。

まとめ

 
 「なんとかなったけど、ようわからんかった」という内容が多くて恐縮ですが、こういうのを積み重ねていかないとどんどん忘れてしまうので、自分のために書いておきました。今までこういうのをサボっていてふと不安になったので、これからはこういう日記みたいなプログラム記録も積極的に書いていこうと思います。

一応コード

 完全に個人用のツールなので誰の役にも立たないと思いますが、一応。


import json
import pprint
import csv
import cmath
import math
import copy
import os

json_open = open('./sample.json', 'r')
j = json.load(json_open)

print("sample.jsonを読み込みました")

csv_file = open("./input.csv", "r",encoding="utf-8-sig")
c = csv.DictReader(csv_file)

points = []

for row in c:
    points.append(row)

n = len(points)

for i in range(n-1):
    new_j = copy.deepcopy(j["Movements"][0])
    j["Movements"].append(new_j)

for i,p in enumerate(points):
    x = float(p['X'])
    y = float(p['Y'])
    z = float(p['Z'])
    duration = float(p['Duration'])
    j["Movements"][i]["Duration"] = duration
    j["Movements"][i]["StartPos"]["x"] = x
    j["Movements"][i]["StartPos"]["y"] = y
    j["Movements"][i]["StartPos"]["z"] = z
    j["Movements"][i-1]["EndPos"]["x"] = x
    j["Movements"][i-1]["EndPos"]["y"] = y
    j["Movements"][i-1]["EndPos"]["z"] = z
    y -= 1.5
    c = complex(-x,-z)
    rad = cmath.phase(c)
    deg = -math.degrees(rad)+90
    #print(deg)
    j["Movements"][i]["StartRot"]["y"] = deg 
    j["Movements"][i-1]["EndRot"]["y"] = deg 
    c2 = complex(abs(c),y)
    rad2 = cmath.phase(c2)
    deg2 = math.degrees(rad2)
    #print(-deg2)
    j["Movements"][i]["StartRot"]["x"] = deg2 
    j["Movements"][i-1]["EndRot"]["x"] = deg2

pprint.pprint(j)

with open('output.json', 'w') as f:
    json.dump(j, f, indent=4)

print("作業を完了しました")
end = input()
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1