| | この章では、Python言語を使用した実践的なプログラミングを取り上げます。 Pythonはスクリプト言語であり、コンパイル言語のCと違いPCで作成したプログラムを
Armadillo用にクロスコンパイルする必要がないので、効率よく開発ができる可能性があります。 さらに、非常に多くのパッケージ(ライブラリ)が提供されており、
かつそれらを簡単に導入できます。 現在では、ディープラーニングなどにも利用されており、一般的なアプリケーションから
研究開発用アプリケーションまで幅広く利用されている言語です。 クラウドサービスを利用したアプリケーションを開発する場合などでも、
各クラウドサービスともにPython向けのSDKも提供していることが多いです。 実行速度が求められるアプリケーションやデバイスドライバの開発をするのでなければ、
開発言語としてPythonは選択肢の一つになります。 ここではPythonの文法に関する詳細な解説はしません。
詳細な解説は公式サイト[]
[]を参照してください。 Pythonの実行環境はaptで簡単にインストールできます。
Pythonにはバージョン2系と3系があり、現在ではバージョン3系が主流と
なっているので、ここでもバージョン3系をインストールします。 インストールはaptでできます。 インストール後に動作を確認できます。 単にpython3として実行すると対話モードと呼ばれるモードでpythonが起動します。
このモードでは入力されたコードが随時実行されていきます。
対話モードを終了する場合はquit()関数を呼び出します。 次に、Pythonのパッケージ管理ツールであるpipをインストールします。
pipを使うことで必要なパッケージ(ライブラリ)を手軽にインストールできるようになります。 まずは、「ファイルの取り扱い」と同様に
ファイルの取扱について取り上げます。 ここでは、「テキストファイルを扱う」と同じ仕様のサンプルプログラムを紹介します。
PythonではCSVを扱うパッケージが標準で用意されているのでこれを使います。 一見してC言語のプログラムより短くなっていることがわかります。
特にCSVファイルを読み込んでカンマ区切りでトークンに分割する処理については、
Pythonでは2行で収まっています。 import csv
(中略)
# CSVファイルから1行読み込む
csvreader = csv.reader(csvfile)
for row in csvreader: 変数rowにはすでに分割されたトークンが配列として代入されているので、
後の処理は内容を整形して表示するだけです。 このようにPythonでは適切なパッケージをimportすることによって、
煩雑な処理を簡潔に書くことができ、バグを作り込む可能性も低減できます。 実際に動作させるとC言語版と同じ結果が表示されます。 リターンを入力すると次に進みます。途中で終了したいときは、Ctrl+Cを入力してください。 ここでも、「設定ファイルに対応する」と同じ仕様のサンプルプログラムを紹介します。
Pythonには設定ファイルを取り扱うconfigparserパッケージも標準で用意されているのでこれを使います。 設定ファイルの作成・読み込みを行うreadconf関数を追加しています。この関数の中で
configparserパッケージを使って処理を行っています。 設定ファイルの新規作成時のデフォルト値が連想配列のように書くことができたり、
読み込みは1行で済んだりと、こちらもC言語版と比べて簡潔に書くことができています。 注意点として、読み込んだ設定値はすべて文字列として保持されるため、数値として扱う場合や
配列として扱う場合は、適宜変換する必要があります。 初回実行した後に、dispcsv2.confファイルができています。 [display]
width = 60
height = 17
[control]
pause = True
count = True
[data]
code = -1
zipcode =
street =
city =
pref =
flag = [-1, -1, -1, -1, -1, -1] C言語版と同様に、以下のように動作を変更させてみます。 -
画面サイズは80x24
-
一時停止しない
-
町域名にKOKUBUNJIを含んだもののみ出力する
[display]
width = 80
height = 24
[control]
pause = False
count = True
[data]
code = -1
zipcode =
street = kokubunji
city =
pref =
flag = [-1, -1, -1, -1, -1, -1] テキストエディタでこのようにdispcsv2.confを
変更して実行すると、以下のように動作します。 PythonでのJSONファイルを扱いについて説明します。現在ほとんどの
クラウドサービスでは、通信のレスポンスとしてJSON形式を採用しており、
クラウドサービスと連携したアプリケーションを開発するには、JSONファイルを
扱うことがほぼ必須となっています。 当然、C言語でもJSONファイルを扱うプログラムを書くことはできますが、C言語では
書きづらい文字列処理を多く実装しなくてはならないため、簡潔に書くのは難しくなります。 ここでは、「設定ファイルに対応する」で取り上げた設定ファイルを、JSON形式として
保存・読み込みを行うサンプルプログラムを示します。 内容としては、図7.6「CSVファイルの内容を表示するプログラムのconfファイル対応版 (dispcsv2.py)」のreadconf関数でconfigparserパッケージを使っている箇所を
JSONパッケージを使うように置き換えただけです。加えて、configparserとは違い、設定ファイルを読み込んだ際に、
数値は数値として読み込まれるので、文字列を数値に変換する必要はありません。 初回実行した後に、dispcsv3.jsonファイルができています。 {
"display": {
"Width": 60,
"Height": 17
},
"control": {
"Pause": true,
"Count": true
},
"data": {
"Code": -1,
"Zipcode": "",
"Street": "",
"City": "",
"Pref": "",
"Flag": [
-1,
-1,
-1,
-1,
-1,
-1
]
}
} ここでも、以下のように動作を変更させてみます。 -
画面サイズは80x24
-
一時停止しない
-
町域名にKOKUBUNJIを含んだもののみ出力する
{
"display": {
"Width": 80,
"Height": 24
},
"control": {
"Pause": false,
"Count": true
},
"data": {
"Code": -1,
"Zipcode": "",
"Street": "kokubunji",
"City": "",
"Pref": "",
"Flag": [
-1,
-1,
-1,
-1,
-1,
-1
]
}
} テキストエディタでこのようにdispcsv3.jsonを
変更して実行すると、図7.7「dispcsv2.confを編集したdispcsv2.pyの実行結果」と同じ結果となります。 XMLファイルはJSONと同様に広く使われているファイルフォーマットです。 PythonではXMLを扱うパッケージも標準で用意されているので、
C言語で実装する場合と比較すると簡単に扱うことができます。 ここでも、「設定ファイルに対応する」で取り上げた設定ファイルを、XML形式として
保存・読み込みを行うサンプルプログラムを示します。 実行方法や実行結果に関してはこれまでのサンプルプログラムと同じなのでここでは省略し、
初回実行時に生成されるXMLファイルのみ示します。 <?xml version="1.0" ?>
<conf>
<display>
<width>60</width>
<height>17</height>
</display>
<control>
<pause>True</pause>
<count>True</count>
</control>
<data>
<code>-1</code>
<zipcode/>
<street/>
<city/>
<pref/>
<flag>-1,-1,-1,-1,-1,-1</flag>
</data>
</conf> XMLファイル内の設定値を変更してプログラムを実行すると、JSONファイル版の
プログラムと同じように動作することを確認できます。 ここで取り上げたものだけではなく、Pythonには様々なファイル形式に対応した
パッケージがあるので、何かファイルを処理したい場合はまずパッケージがないか
探してみると見つかる可能性があります。 Pythonでもソケットを使ったネットワークプログラムを書くことができます。 TCP/IPプログラムの流れは「TCP/IP」を参照してください。
Pythonでもこの流れと同じです。 7.3.1. Python版TCP/IPでHello!「TCP/IPでHello!」のPython版を作ってみます。 ソケット作成から、メッセージの送信までの流れはC言語版と同じです。 実際に動作させてみます。 PCからtelnetで接続してみます。 このようにHello!と表示されることが確認できました。 Pythonで通信系アプリケーションを開発しようとする場合は、Socketのような
低位層のインターフェースを使ったものより、HTTPのような
上位層の通信を要求されることのほうが多いかもしれません。 ここではPythonにおけるHTTP通信の例を紹介します。 ATDE7では、標準状態でlighttpdというHTTPサーバが可動していますので、
これを利用します。 ATDEを起動している状態で、他のPCなどからウェブブラウザでATDEのIPアドレスに
アクセスするとlighttpdの初期画面が表示されます。 この画面を簡単なhtmlに置き換えます。 作成したhtmlファイルを、wwwディレクトリに配置してパーミッションを変更します。 [ATDE ~]$ mv index.html /var/www/html
[ATDE ~]$ sudo chown www-data:www-data /var/www/html/index.html ここまでで、再度ブラウザからATDEのIPアドレスにアクセスすると、「hello, Python.」
と表示されることが確認できます。 HTTPサーバーにアクセスするPythonプログラムは、urllibパッケージを使うと
簡単に書けます。 実行すると、index.htmlの内容が表示されます。 URLにパラメータをつけてリクエストすることもできます。 ここまではGETリクエストでしたが、POSTでリクエストすることもできます。 POSTでJSONデータを送信するプログラム例です。 センサーなどから取得したデータやサーバーから受信したデータを保存しておく場合、
ファイルとして保存しておくのも手ですが、データ同士に関連があったりまとまったデータに対して、
なにか条件をつけて目的のデータを検索したいとなった場合、データベースを構築して
そこに保存しておく方法もあります。 ここではPythonでデータベースを扱う例について取り上げます。 Python向けにpeeweeというデータベースを扱うパッケージがあるので、
それを使います。 peeweeはSQLite、MySQL、PostgreSQL、CockroachDBといったデータベースシステムを扱えますが、
ここでは組み込み向けに利用されることが多いSQLiteを選択します。 まずは、pipでpeeweeをインストールします。 [armadillo ~]# pip3 install peewee 例として「テキストファイルを扱う」で使用した、住所のCSVファイルのデータを
データベースに保存するプログラムを示します。 仕様は以下のとおりです。 -
データベースファイルの名前はaddress.dbとする
-
CSVファイルの先頭から10件分の住所をデータベースに保存する
-
保存するデータは、郵便番号、都道府県、市区町村、住所とする
-
サブコマンドとしてadd、show、update、deleteの4つを用意する
-
add -F <CSVファイル名> で読み込むCSVファイルを指定する
-
show で保存しているデータを表示する
-
update -I <id> -C <column名> -V <値> で指定したidのデータの<column名>を<値>に変更する
-
delete -I <id> で指定したidのデータを削除する
実際に動作させてみます。まず、CSVファイルからデータベースに保存します。 サブコマンドaddにオプション-FでCSVファイルを指定して実行します。 サブコマンドshowでデータが保存されていることが確認できます。 次に、サブコマンドupdateで5番目のデータを変更してみます。 5番目のデータが変更されていることが確認できます。 最後に、サブコマンドdeleteで6番目のデータを削除してみます。 6番目のデータが削除されていることが確認できます。 このように、PythonからSQLデータベースを複雑なSQL文を意識することなく、
操作できます。 peeweeには他にも様々な機能がありますので詳細は公式サイト[]を
参照してください。 ここで作成したデータベースファイルaddress.dbはWindowsなどにあるSQLデータベースブラウザ
のようなアプリケーションで中身を直接確認することもできます。 Pythonでアプリケーションを開発しても、C言語に比べてコード量が少ないとはいえ
バグは存在します。
Pythonはスクリプト言語なので、怪しいと思った箇所にprint文を
埋め込んでコンパイル不要ですぐに動作を確認することができますが、
それでも数が多くなるとprint文を埋め込むのもデバッグ後に削除するのも大変です。 ここではPythonプログラムのデバッグ手法について紹介します。 Pythonでは、プログラムがクラッシュした場合、その理由やプログラムのどこで
発生したのかなどの情報を含んだトレースバックを出力します。
ほとんどの場合は、このトレースバックを読んで原因箇所を修正すれば解決します。 以下は0除算を行う可能性のあるプログラムです。 実行すると以下のようになります。 トレースバックが出力されています。
下から上に向かって読むと、まず ZeroDivisionError: division by zero このメッセージで0除算が発生していることが分かります。
次に File "dividezero.py", line 7, in divide
c = a / b ここで、divide関数の中、プログラム7行目で発生していることが分かります。 別な例として、配列の範囲外へのアクセスが発生した場合です。 実行すると以下のようになります。 トレースバックの内容から、プログラム8行目で配列の範囲外へのアクセスが
発生していることが分かります。 このように、クラッシュが発生するようなバグの場合はトレースバックに表示される
情報ですぐに発生原因と発生箇所を特定できるので、まずはトレースバックに目を通すのが
よいです。 クラッシュはしないが期待していた動作と違うといった場合は、デバッガを使うのが有効です。 Pythonでは標準でpdbというデバッガが用意されていますので、これを使います。 デバッグ対象の簡単なプログラムです。 このプログラムを実行すると以下のように表示されます。 結果として5050と表示されるはずですが期待と違っていました。
pdbを使ってデバッグしてみます。 pdbを使うためにプログラム実行時にオプション引数を渡します。 [armadillo ~]# python3 -m pdb sum.py
> /home/atmark/workspace/source/sum.py(1)<module>()
-> import os
(Pdb) 1行目で止まり、(Pdb)プロンプトが表示されました。今後はこのプロンプトに
コマンドを入力してデバッグを進めます。
nextと入力すると次の行へ進んで実行が止まります。 [armadillo ~]# python3 -m pdb sum.py
> /root/work/developers_guide/python/debug/sum.py(1)<module>()
-> import os
(Pdb) next
> /root/work/developers_guide/python/debug/sum.py(2)<module>()
-> import sys
(Pdb) next
> /root/work/developers_guide/python/debug/sum.py(4)<module>()
-> def sum(a):
(Pdb) next
> /root/work/developers_guide/python/debug/sum.py(11)<module>()
-> if __name__ == "__main__":
(Pdb) next
> /root/work/developers_guide/python/debug/sum.py(12)<module>()
-> s = sum(100)
(Pdb) next
> /root/work/developers_guide/python/debug/sum.py(13)<module>()
-> print(s) ここでpコマンドを使うと、変数sに入っている値を表示することができます。 (Pdb) p s
4950 continueコマンドを使うとプログラムを最後まで実行します。最後まで実行された後は、
再び最初から実行が開始されます。 (Pdb) continue
4950
The program finished and will be restarted
> /root/work/developers_guide/python/debug/sum.py(1)<module>()
-> import os pdbを終了するにはquitと入力します。 (Pdb) quit
[armadillo ~]# 再び、pdbを開始しsum関数の呼び出し箇所まで処理を進めます。 # python3 -m pdb sum.py
> /root/work/developers_guide/python/debug/sum.py(1)<module>()
-> import os
(Pdb) next
> /root/work/developers_guide/python/debug/sum.py(2)<module>()
-> import sys
(Pdb) next
> /root/work/developers_guide/python/debug/sum.py(4)<module>()
-> def sum(a):
(Pdb) next
> /root/work/developers_guide/python/debug/sum.py(11)<module>()
-> if __name__ == "__main__":
(Pdb) next
> /root/work/developers_guide/python/debug/sum.py(12)<module>()
-> s = sum(100) ここで、nextではなくstepと入力するとsum関数の中へ入ることができます。nextも
stepも1行ずつ実行するコマンドですが、nextは関数の中には入らず、stepは関数の中に入ります。 (Pdb) step
--Call--
> /root/work/developers_guide/python/debug/sum.py(4)sum()
-> def sum(a): sum関数の中へ入り、処理が止まりました。 ここまで、nextやstepで一行ずつ実行してきましたが、目的の行に
たどり着くまで何度も入力するのは大変です。 そこで、ブレークポイントを設定しそこまで一気に処理を実行してみます。 まず、ブレークポイントを設定する行を確認するために、listコマンドで
ソースコードを表示します。 [armadillo ~]# python3 -m pdb sum.py
> /home/atmark/work/developer_guide/python/sum.py(1)<module>()
-> import os
(Pdb) list
1 -> import os
2 import sys
3
4 def sum(a):
5 s = 0
6 for i in range(1, a):
7 s += i
8
9 return s
10
11 if __name__ == "__main__":
(Pdb) | |
---|
ソースコード内にコメントがある場合はコメントも表示されます。
この時、日本語のコメントがあった場合、環境によっては
文字エンコードに関する例外が発生することがありますが、
デバッグは可能です。 |
breakコマンドで7行目にブレークポイントを設定し、continueコマンドで
設定した行まで一気に実行します。 (Pdb) break 7
Breakpoint 1 at /root/work/developers_guide/python/debug/sum.py:7
(Pdb) continue
> /root/work/developers_guide/python/debug/sum.py(7)sum()
-> s += i
(Pdb) p s
0
(Pdb) p i
1 ブレークポイントで実行が停止しました。そこでの変数sとiの値も表示しています。 displayコマンドを使うと値が変化した変数が自動的に表示されるようになります。 (Pdb) display s
display s: 0
(Pdb) display i
display i: 1
(Pdb) continue
> /root/work/developers_guide/python/debug/sum.py(7)sum()
-> s += i
display s: 1 [old: 0]
display i: 2 [old: 1]
(Pdb) continue
> /root/work/developers_guide/python/debug/sum.py(7)sum()
-> s += i
display s: 3 [old: 1]
display i: 3 [old: 2]
(Pdb) continue
> /root/work/developers_guide/python/debug/sum.py(7)sum()
-> s += i
display s: 6 [old: 3]
display i: 4 [old: 3] for文を抜けるまでcontinueし続けるのは大変なので、clearコマンドでブレークポイントを
削除し、returnコマンドで関数の最後まで処理を実行します。 clearコマンドでブレークポイントを削除する時は、ソースコードの行数ではなく
ブレークポイント番号を指定する必要があります。ブレークポイント番号は、
breakと入力することで確認できます。 (Pdb) break
Num Type Disp Enb Where
1 breakpoint keep yes at /root/work/developers_guide/python/debug/sum.py:7
breakpoint already hit 6 times
(Pdb) clear 1
Deleted breakpoint 1 at /root/work/developers_guide/python/debug/sum.py:7
(Pdb) return
--Return--
> /root/work/developers_guide/python/debug/sum.py(9)sum()->4950
-> return s
display s: 4950 [old: 15]
display i: 99 [old: 6] ブレークポイントを削除し、関数の最後まで処理を進めたところ
変数iの値が99となっており、forループが99回しか実行されておらず、
forループの終了条件に問題があることがわかりました。 このように、デバッガを使うとデバッグのためのprint文を埋め込むことなく
処理の流れや変数の内容を確認できるので、効率的にデバッグができるようになります。 最後に、ここでの説明で使用したpdbのコマンド一覧を示します。
またほとんどのコマンドは、省略形でも使うことができます。 表7.1 使用したコマンド コマンド | 省略形 | 動作 |
---|
step | s | 1行ずつ実行し、関数の場合は中に入る | next | n | 1行ずつ実行し、関数には入らない | p | - | 変数の値を表示する | continue | c | 実行を再開する | quit | q | pdbを終了する | list | l | ソースコードを表示する | break n | b n | n行目にブレークポイントを設定する | clear n | d n | n番目のブレークポイントを削除する | return | r | 関数を最後まで実行する | display | - | 変化のあった変数の値を表示する |
他にもコマンドがありますので詳細は公式サイト[]を参照してください。 | |
| | | |
| |