Pythonに挑戦(3)で、それなりに動作する住所録に改良してみました。ここでは、Pythonの特徴を活かしながら、住所録を実用的なものにするべくさらなる改良を試みます。本当なら、何を考えてプログラムを作ったか、ということを書くべきですが、想定をはるかに越えるトラブル続きで、悪戦苦闘記です。
C言語でコードを書くとき、私は市販のエディタを使うので、Pythonのように開発ツールIDLEを使って開発しようとすると、
TabError: inconsistent use of tabs and spaces in indentation
というエラーに悩まされます。一々空白を4個打ち込み、消すときは[BS]キーを4回押さなければいけない、というのは煩わしくて忍耐を超えるので、指が覚えてしまっている市販エディタを使うと、[TAB](私は、8タブが大嫌いなので、普段は4タブです)と空白が入り乱れて、インデントでプログラムの流れを制御するPythonでは大変なことになります。この点は、MicrosoftのVisual Studioも同じで、VBAでも同じ苦労を味あわされます。
インデントで流れの制御を行うPythonでは、print文や関数set_trace()を入れてデバグをした後、ifの後、forループ内に埋もれているprint,set_trace()を除去するのも大変です。C言語なら、printfを行頭から書けば、すぐに見つけられます。削除に困ったことはありません。
さて、改良に当たって、プログラムを複数のモジュールに分け、メイン部分adbook.py、クラス部分adclass.py、サブルーチン部分adsub.py、グローバル変数adglobal.pyの4つに分けることにします。ですが、これが、C言語でextern宣言を入れたファイルをincludeする、というような簡単な話ではありません。
まず、Pythonでは、グローバル変数が簡単ではありません。Python関係のウェブサイトを見ていると、まるでグローバル変数は悪だとでも言いたげな書き方をしています。グローバル変数が勝手に書き換えられてしまうから、と言うのですが、そんなことは、C言語でのprintfデバグでもすぐに調べられます。システム全体で使われる変数を否定してもコーディングしにくいだけです。試行錯誤の結果、C言語のグローバル変数のようにするには、以下のように。グローバル変数をまとめたモジュールadglobal.pyを作り、from文を使えばよいことが分かりました。
adglobal.py
N = “name”
P = “postcode”
A = “address”
T = “telno”
M = “mail”
ad_path = “C:\\python\\addressbook2”
org_path = “”
j = [N, P, A, T, M]
z = [‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘, ‘-‘]
h = [‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’, ‘-‘]
P = “postcode”
A = “address”
T = “telno”
M = “mail”
ad_path = “C:\\python\\addressbook2”
org_path = “”
j = [N, P, A, T, M]
z = [‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘, ‘-‘]
h = [‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’, ‘-‘]
adglobal.pyを参照する側では、
from adglobal import N, P, A, T, M, j
という記述を最初の方に置けば、これで、変数N,P,A,T,M,jを外部から参照することができます。但し、参照のみで書き換えることはできません。
書き換えたい場合、例えば、プログラム起動時に、adsub.py内のサブルーチンでカレント・ディレクトリを読み取って、adglobal.py内のorg_pathに保持させるためには、adsub.pyの先頭で、
import adgobal
と宣言し、global.org_path = os.getcwd()
のようにします(勿論、osモジュールの機能を使うので、import osも必要です)。“global.”と書くのが面倒なら、as文を用いて、import adglobal as g
として、g.org_path = os.getcwd()
のように短縮化することもできます。要するに、書き換えも行うのであれば、その変数がどこにあるかを一々指定する必要があります。“global.”とか“g.”を忘れて、org_pathを使うと、変数が定義されていない、というエラーが出てしまいます。サブルーチンも同様で、どこのモジュールの関数なのか、adsub.zhconv()という具合に、zhconv()というサブルーチンが、adsub.pyというモジュールに入っていることを明示する必要があります。1つのモジュール内で、
from adglobal import N, P, A, T, M, j
import adglobal as g
と、from文とas文を併用しても大丈夫なようです。import adglobal as g
ただ、関数zhconv()がクラスadsubのメソッドという訳でもないのに、adsub.zhconv()と書くのは、オブジェクト指向として少々違和感がありますね。
また、Pythonのコーディングでは、使わないとしても、
import sys
import pdb
としておき、プログラムが動作不良を起こしたら、すぐに、プログラムの強制終了関数sys.exit(),デバグ機能開始関数pdb.set_trace()をプログラム内に入れられるようにしておくと良いと思います。
Pythonでは、リストの初期化も注意が必要です。a=b=c=0という具合に変数a,b,cを初期化するような感覚で、リストの初期化をうっかりx=y=z=[]とやってしまうと、x[1]=”a”とした時に、y[1]もz[1]も“a”になってしまいます。これが原因で動作不良を起こしていることに気づかないと、y[1],z[1]がいつの間にか壊れるので、デバグに苦しむことになります。
Python処理系が、名前が異なれば別のリストとして扱うべきだ、と私は思いますが、3つのリストの初期化は、x, y, z = [], [], []というように、3つのリストが別々になるようにやらないといけません。
前回のPythonに挑戦(3)で、誤りがありました。pathにファイルのパスを指定して、
fd1 = open(path, mode=’w’)
により、書き込みオープンして、ファイル・オブジェクトfd1を作り、文字列レコードstr1を、fd1.write(str1)
としてファイルに書き込むときにPythonが、str1に‘\n’を付加して書いていると書きましたがこれは誤りです。fd1.write(str1)では単に文字列str1を書き込むだけなので、csvファイルにしたい場合など、1レコードごとに‘\n’を付加させる場合には、fd1.write(str1 + ‘\n’)
として、改行コード‘\n’を付加させる必要があります。なのに、前回プログラムがどうして動作していたか、と言うと、fd1 = open(path, mode=’r’)
により読み込みオープンして、for str1 in fd1:
という具合に、ファイル・オブジェクトfd1から1レコードずつ読み込むと、str1の末尾に‘\n’が付いているために、前回のプログラムは、たまたま動作してい(るように見え)ただけです。今回、これを修正しようとしたのですが、行末の‘\n’の除去が簡単ではなく、諸所のウェブサイトに、
str1.replace(‘\n’, ”) (str1の‘\n’をヌル文字”で置き換える)
とかstr1.rstrip(‘\n’) (str1の右側から‘\n’を除去する)
とやれば除去できる、と書いてあるのですが大ウソで、これでは文字列末尾の‘\n’は残ってしまいます。簡単には除去できない、と書いているウェブサイトもありますが、fd1から1レコードずつ読み込むときの‘\n’は末尾につくので、末尾文字を取り除いた新たな文字列str2を作るようにして、str2 = str1[:-1]
としてやることでやっと文字列末尾の‘\n’を除去できました。これを色々と試行錯誤する際に、書式演算子%とord()という関数を使って、
a1 = list(str1)
for c1 in a1
print(“%x” % ord(c1))
のように調べたのですが、関数ord()は、ord(c1)とすると、文字c1のUTF-8コードを返してくれます。これで分かってきたのですが、PythonはUTF-8を前提にしているがゆえに困ることがあります。Shift-JIS前提のC言語では全く気にもせず、%を使って書式制御を行い、printfで出力して何の問題もありませんでしたが、UTF-8前提ということになると、そうは行きません。for c1 in a1
print(“%x” % ord(c1))
str1 = “前3後6″ # 3と6は半角
print(“{:>10}”.format(str1))
str1 = “前3後6” # 3と6は全角
print(“{:>10}”.format(str1))
などとするとき、表示桁に何桁使うのか制御できないのです。どちらも文字列の長さlen(str1)は4です(Pythonでは全角も半角も1文字と数えます)。どちらも、書式“>10”を使って右揃え10桁としているのに、実行させてみると、
前3後6 (表示は6桁)
前3後6 (表示は8桁)
となり右揃えになりません。縦をきれいに揃えて表示させようとしても、Pythonの標準の方法では不可能だということが分かりました。
前述のadglobal.pyに全角半角変換用のリストz,hが出てきますが、桁の制御を行うためには、特別な処理を行う必要があり、そのためのリストです。今回のプログラムでは、全角半角変換サブルーチンzhconv()を用意し、全角の数字(Pythonでは数字として認識します)を半角の数字に直してしまうことにしました。また、全角半角を考慮した桁数制御を行う文字列生成サブルーチンmakestr()を用意しました。でも、こんなことまでやらないとけないのならC言語の方がよっぽどいいなあ。
Shift-JIS前提のC言語なら、全角・半角の区別は文字コードの最上位ビット(1なら全角、0なら半角)を見るだけなので簡単です。C言語では、str1 = “前3後6″ならstrlen(str1) = 6,str1 = “前3後6“ならstrlen(str1) = 8です。Pythonでは、全角文字も半角文字も1文字と数えますが、C言語では全角文字は2バイト、半角文字は1バイトで単純です。
また、Shift-JISでは、JIS第一水準の漢字(当用漢字と人名用漢字、よく使われる漢字)は、2バイトで、889FH(亜)~9872H(腕)というコードに音読みあいうえお順に割り当てられていて文字コードを使ってソートし易いように考えられていますが、UTF-8コードでは漢字のコード割り当てがめちゃめちゃで、Shift-JISとUTF-8の間の変換規則も明確ではありません。紀伊国屋さんで、このことの対策を書いてある本を探しましたが、見つけられませんでした。
UTF-8→UTF-16の変換は規則的に行えるので、UTF-16に直して全角半角の表示制御をするという高等技巧を使うのでしょうか?
文科省は、これでも高校の情報の授業で、Pythonを使え、と言うんですかねえ?私の感覚では信じられません。まあ、国際標準のUTF-8に対して、Shift-JISは日本語専用の規格なので、Shift-JISにこだわると技術がガラパゴス化するのかも知れませんが、でも、高校教育では、ソフトウェア技術の基本を教えるべきであって、Pythonでは、高校生に情報技術を嫌いになれと言っているようなものです。ウェブサイトを徘徊していて、Pythonの学校が多いのに驚かされますが、納得です。Pythonは、複雑な高等技巧の集積で、学校で色々な技巧を叩き込まれている習熟したプロならともかく、素人にはとても食えません。C言語なら学校に行く必要はなく、独学で充分に修得可能です。私もン十年前くらいに、独学でOKでした。
住所録の改良に数日でともくろんでいましたが、10日以上もかかってしまいました。Pythonの専門家が、苦しむのは、あなたの技術力が劣るからだ、と言うなら、まさにPythonは高校教育には不適切な言語だということです。Pythonが初学者にも分かりやすいと多くのPythonのウェブサイトに書かれているもウソだということです。苦学楽学塾の制作システムは、C言語によって作られていますが、開発途中で今回Pythonで味わったような言語仕様上の困難には対面したことはありません。
Pythonの文法や、関数についての説明は次回に回して、最後に、Pythonで書かれた住所録プログラム:adbook.pyの使い方を説明しておきます。
必要なソースは、adbook.py,adclass.py,adsub.py,adglobal.pyです。これをまとめて一つのディレクトリに入れておきます。
さらに、住所録ファイルを格納するディレクトリをadglobal.py内のad_pathという変数に設定します。Pythonでは、ディレクトリを指定する文字‘\’は、エスケープ文字なので、ディレクトリ指定には‘\\’と2つ重ねることに注意して下さい。
IDLE画面(Windowsのスタート・ボタンをクリックしてPのところにあるPython右の‘v‘を押下するとIDLEを起動するボタンが出てきます)で、adbook.pyを読み込んでRunメニュー内のRun Moduleをクリックすると住所録プログラムが起動します。コマンドラインプロンプトからでも、explorer画面でadbook.pyをダブルクリックしても起動できます。
「作業開始」と表示された後、最初は、住所録がないので、
c:correct, d:dup-check, e:end, l:list, r:remove,
s:sequential input, n,p,a,t,m:search
command:
を表示して、コマンド入力待ちになります。ここで使えるコマンドは、半角英文字1文字で、c ,d,e,l,r,s,n,p,a,t,mです。
データを入力する場合には、sコマンドを使います。コマンド入力待ちの時に、
s[Enter]
とすると、逐次入力モードになり、
name:
と表示して入力待ちになります。名前(20文字以内)を入力して[Enter]すると、
postcode:
と表示して入力待ちになります。郵便番号(数字3桁、ハイフン‘-‘、数字4桁、半角でも全角でもOK)を入力して[Enter]すると、
address:
と表示して入力待ちになります。住所を入力して[Enter]すると、
telno:
と表示して入力待ちになります。電話番号(045-123-4567,090-987-6543のように、市外局番、市内局番、電話番号をハイフン‘-で結びます。市外局番を省略してもハイフンなしでもOK,半角でも全角でもOK)を入力して[Enter]すると、
mail:
と表示して入力待ちになります。30字以内の半角文字でメール・アドレス(xxxx@yyyy.comのように)を入力して[Enter]すると(これで一件の住所が登録されます)、再び、
name:
と表示して名前の入力待ちになります。引き続き、住所録のデータ入力を続けることができますが、ここで、何も入力せずに[Enter]すると、逐次入力モードを終了します。name:に対してe[Enter]としても“e”という名前だと判断されるだけでpostcode:と表示されて逐次入力モードは終わりません。逐次入力モードを終了してコマンド入力に戻るのは、name:に対してそのまま[Enter]したときだけです。
pastcode:,address:,telno:,mail:に対して何も入力しないで[Enter]すると、名前は有効で、他の項目が無効になるので、誤ったデータとして修正モードに移行します。
郵便番号、電話番号は、全角数字で入力できますが、半角数字に直して登録されます。
コマンド入力待ちの時に、
c[Enter]
と入力すると、
specify data no. following c
と表示されて再度コマンド入力待ちになります(但し、重複チェックコマンドdで重複が検出された後は、“c”だけで重複データに対する修正モードになります)。
あらかじめ、
l[Enter] (lは、数字の1ではありません。アルファベットのLの小文字です)
として全データを表示させてから、修正するデータの番号を選び、仮に12番だったとすると、
c12[Enter]
とすると、12番のデータが表示され、12番のデータに対して修正モードになります。修正モードに入ると、
modify mode command(n,p,a,t,m,e):
と表示されて入力待ちになります。名前を青木三郎に直すのであれば、
n青木三郎[Enter]
とします。郵便番号を104-0751にするのであれば、
p104-0751[Enter]
とします。住所を東京都中央区銀座4-10-35にするのであれば、
a東京都中央区銀座4-10-35
とします。電話番号を03-1234-5678にするのであれば、
t03-1234-5678[Enter]
とします。メール・アドレスをaoki1973425@gmail.comにするのであれば、
maoki1973425@gmail.com[Enter]
とします。データの前に、nかpかaかtかmを付けてどのデータを直すのかを指定します。修正モードを抜けてコマンド入力モードに戻るには、e[Enter]とするか、何も入れないで[Enter]します。
コマンド入力待ちの時に、
d[Enter]
と入力すると、データの重複確認を行います。但し、この住所録プログラムでは、名前の重複チェックしか行いません。他の項目も重複チェックしたいのであれば、プログラムを直してください。重複があるときは、重複検知されたデータの番号が重複データのリストに入るので、続けて、コマンド入力待ちに対して、c[Enter]とするだけで修正モードに移行します。
コマンド入力待ちの時に、
r12[Enter]
と入力すると、12番のデータを削除します(このプログラムでは一度削除すると元に戻りません。一旦中間バッファに残し、後で復活できるようにしたいなら、プログラムを直してください)。通常は、dコマンドで重複チェックを行い、重複しているデータを削除するのにrコマンドを使います(但し、r12としても12番が重複データかどうかのチェックを行っていません)。
コマンド入力待ちの時に、
n三郎[Enter]
と入力すると、名前に「三郎」を含むデータを列挙して修正モードに移行します。「井上徳三郎」さんも「飯島三郎太」さんも「三郎寺敦夫」さんも選択されます。列挙されたデータが複数個ある場合には、
which? enter no.:
と表示されるので、修正したいデータのデータ番号を入力すると修正モードに移行します。
p011[Enter]
なら、011を含む郵便番号のデータが選択されます。
a東京[Enter]
なら、住所に東京を含むデータが選択されます。
t045[Enter]
なら、電話番号に045を含むデータが選択されます。
mminato[Enter]
なら、メール・アドレスに“minato”を含むデータが選択されます。
コマンド入力待ちの時に、
e[Enter]
あるいは、何も入れずに単に、
[Enter]
とすると、住所録プログラムを終了し、データ(があれば)を住所録ファイルに書き出します。住所録は、adglobal.py内のad_pathという変数に指定したディレクトリに、メインのadbook.py内でbookクラスのインスタンスを作るときに指定した名前のファイルで作成されます。作成された住所録ファイルを表計算ソフトEXCELでも読み込めるように、csvファイルとして出力されます。
一度住所録ファイルを作成すると、次回以降、住所録プログラムを起動すると、住所録を読み込んで一覧表示してから、コマンド入力待ちになります。
下記にソースコードが書かれたページへのリンクを貼っておきます。リンク先をコピーして、Windowsのメモ帳、あるいは、ご利用のエディタにペーストし、文字コード:UTF-8で、指定されたファイル名(拡張子は“py”)で保存し、ご利用ください。
adbook.py
adclass.py
adsub.py
adglobal.py
ブログTOP TOPページに戻る