Pythonに挑戦(2)で、手始めに簡単な住所録を作ってみようと書きました。前回では住所録とはとても呼べないものなので、少々、それなりに動作するように改良してみます。
まず、住所録ファイルから既存のデータを読み込んだのちに、新規のデータをキーボードから追加入力し、新たに住所録ファイルに書き出す、というようにしてみたいと思います。
まず、ファイルの入出力ですが、Pythonでは、他の言語と同様に、まず、open()という関数でファイルを開き、ファイル・オブジェクトのread()メソッドで読み込み、write()メソッドで書き込み、読み込み書き込み終了時にファイル・オブジェクトのclose()メソッドでファイルを閉じる、という手順になります。EXCEL VBAでは、open()もファイル・オブジェクトのメソッドなのに、Pythonがopen()だけ関数にしているのは不思議です。ともかく、Pythonではopen()関数でファイル・オブジェクトを取得するという発想をするようです。
python関係のウェブサイトを見ると、ファイルの入出力やメモリ確保には、with文を使え、と書いてあります。with文を使えばclose()しなくて良い、というのがその理由に挙げられていますが、他の言語を使う可能性も考えれば、close()しなくて良い、メモリ解放しなくて良い、という発想は間違っていると私は思います。なので、ここでは、with文は使いません。
ファイルの読み込みについては、C言語と同様の読み込み方も可能なようですが、Pythonでは、forループを用いて1行ずつ読み込んでいく便利な手法があるようです。以下に示すプログラムでは、for str1 in fd1というにして、ファイル・オブジェクトfd1の中の各行を1行ずつstr1に読み込んでいます。Pythonのforループでは、inという演算子を用いて、ファイル・オブジェクトfd1の中に行がある限り繰り返す、という風にするらしいです。
住所録ファイルは、CSVファイルとして表計算ソフトEXCELでも読み込めるように、各データを、名前、郵便番号、住所、電話番号を‘,’で区切って格納するようにします。4つの項目があるので、‘,’は1つのデータに3個あるはずです。以下のプログラムでは、文字列オブジェクトstr1のcount()メソッドを使って‘,’の個数を数え、3個でなければエラーにしてそれ以後の処理を行わないようにしました。それに続き、文字列オブジェクトstr1のsplit()メソッドを用いて、文字列str1を‘,’で分離して住所録ファイルの1行から名前、郵便番号、住所、電話番号をリストdに取り出します。リストdには、[名前,郵便番号,住所,電話番号]というようにデータが入ります。
その後、リストdから、1つずつデータを取り出して、クラスpersonのインスタンスeを作成し、eは、リストpersonのリストである住所録オブジェクトbookのappend()メソッドを用いて、bookに付加されます。append()メソッドは、リストに要素を付加するリストのメソッドです。
ファイル書き込みにおいては、for c in bookとして、bookオブジェクトの中に、住所録の個別データcが存在する限りループします。cはクラスpersonのインスタンスであるリスト・オブジェクトで、名前、郵便番号、住所、電話番号が入っています。これをCSVファイルの形で出力するために、adclass.pyに、csvline()というクラスpersonのメソッドを準備しました。このメソッドは、名前、郵便番号、住所、電話番号を‘,’でつなげた文字列を作ってreturnします。文字列は、str1としますが、
str1 = “%s,%s,%s,%s” % (self.name, self.postcode, self.address, self.telno)
このメソッドをc.csvline()というように呼び出すと、cというリスト・オブジェクトをselfとして、selfのname, postcode, address, telnoという各要素を、%s(文字列出力)という形式で、str1という文字列に出力します。あとは、c.csvline()の戻り値としてできている文字列を、ファイル・オブジェクトfd1のwrite()オブジェクトでファイルに書き込むということになります。
住所録オブジェクトbook全体について、住所録データをファイルに書き込んだら、最後に、fd1.write(‘\n’)として、1つ[LF]を書き込みます。write()メソッドの最初に[LF]を書き込んでくれるようですが、最後の行だけ[LF]がない状態で終わってしまうからです。[LF]の文字コード(16進で0DH)は、Pythonでは‘\n’と表します。‘\’はエスケープ・シーケンスと言い、特殊な文字を表記するのに使います。
さて、先の
str1 = “%s,%s,%s,%s” % (self.name, self.postcode, self.address, self.telno)
ですが、書式を示す文字列 “%s,%s,%s,%s”と(self.name, self.postcode, self.address, self.telno)を‘%’という二項演算子(右側を左側の指定に基づいて文字列化する)で結んでいます。(self.name, self.postcode, self.address, self.telno)という左カッコ‘(‘と右カッコ‘)’で挟まれたデータの並びをtupleと言うのだそうです。tupleというのは、doubleとかtripleを一般化したもので、複数個のデータ並びを示します。それに対して、住所録オブジェクトbookはリストです。リストのデータは、[person1, person2, ・・・]というように、左カッコ‘[‘と右カッコ‘]’で挟んでデータの並びを表します。ともに、3番目のデータというときには、tuple[2],list[2]というようにします(先頭が0番目)。tupleとリストの違いは、データを追加したり、削除したりできるか、というところにあります。tupleでは追加、削除はできません。リストでは、追加メソッドappend()、削除メソッドpop()、remove()、全削除メソッドclear()などが用意されています。なお、Pythonには他にも‘{‘と‘}’で挟む置換フィールド(前回、format()関数を用いる表示制御のところに出てきました)、とか、辞書と呼ばれるものもあります。
また、エスケープ・シーケンスですが、以下のプログラムの中に、カレントディレクトリを、住所録が存在するディレクトリ(C:\python\addressbook)に設定するところがあります。os.chdir()というメソッドに、単に、“C:\python\addressbook”という文字列を渡すと、‘\’はエスケープ・シーケンスとして動作するので、‘\p’や‘\a’で特殊文字と解釈されてしまいます(‘\t’は[TAB],‘\r’は[CR]です。文字列“・・・“中で“を表示させたい場合は、\”とします)そこで、‘\’を2つ並べて‘\\’と書くことによって文字‘\’を表します。従って、os/chdir()に渡す文字列は、“C:\\python\\addressbook”ということになります。
上記で、os.chdir()というメソッドが出てきましたが、osオブジェクトのような既存の別の成果物を使う場合には、importという命令を使います。import osは、os.pyというpythonプログラムを使う、という意味です。osオブジェクトは、カレント・ディレクトリを取得するos.getcwd()というメソッドや、カレント・ディレクトリの位置を変更するos.chdir()というメソッドを持っています。os.pyというファイルの記述は、Pythonをインストールしたディレクトリの中の\Libというディレクトリに収録されています。import osと書いてあれば、Pythonが自動的に探してくれます。
前回、住所録の1つのデータを表すpersonというクラスを記述したadclass.pyというファイルを作りましたが、adbook.pyからadclass.pyを使いたい場合には、import adclassと宣言します。
以下のプログラムでは、cont_flagで処理の流れを制御します。住所録の読み込みに失敗すると、cont_flagをFalseとし、以後の処理を行わないようにします。この辺は、C言語のgoto文があればラストにいきなり飛んでしまえばよいのですが、Pythonにはgotoがないようなので、面倒ですがこうするしかありません。
住所録ファイルを読み込むと、キーボードからの住所録入力に移ります。キーボードからの入力には、input()関数を使います。input()関数では、引数に文字列を渡すことで、プロンプトを表示できます。プロンプトと言うのは、キー入力を要求する前に表示する文字列のことです。単に画面上でキー入力待ちになっても何をすればよいのかわかりません。そこで、画面に、
名前:
と表示してからキー入力待ちになるようにします。このためには、name = input(“名前:“)とします。引き続いてキー入力して[Enter]キーを押せば、入力された文字列がnameという変数に入ります。postcode = input(“郵便番号(012-3456):“)とすれば、画面に、プロンプト
郵便番号(012-3456):
と表示してキー入力待ちになります。キー入力して[Enter]キーを押せば、入力された文字列がpostcodeという変数に入ります。(012-3456)と書いているのは、このような形式で入力するように促すためです。同様にして、住所、電話番号を入力します。各入力の後で、len()関数を用いて、文字列の長さを求めています。nameに“青木三郎“と入っているとき、len(name)は4になります(文字コードをUTF-8にしているからですが、Shift-JISにすると8になるのかも知れません)。len(name)が0になるときは、nameとして何もキー入力されなかったという意味なので、cont_flag = Falseとして処理打ち切りにしています。
以下のプログラムでは住所データ入力の前に、
if cont_flag:
while cont_flag2:
の2行を入れています。これは、住所録ファイルの読み込みに成功した(cont_flag = Trueになっている)ときだけ住所データの入力とファイル出力を行い、住所データが入力されている限り(cont_flag2 = Trueになっている)入力処理を繰り返す、という意味です。
if文では、ifの右側がTrueであれば引き続く処理に入ります。if の右側がFalseである場合には、文字下げされて書かれている引き続く処理には入らずに、if文と同じ文字下げの次の行に処理が移ります。while文では、whileの右側がTrueである限り、引き続く処理を繰り返します。whileの右側がFalseになると、whileの文字下げと同じ文字下げになっている次の行に処理が移ります。for a in bで、bの中にaが入っている限り繰り返すのとやや異なります。whileループやforループの処理を途中で打ち切りたい場合には、break文を置きます。break文の後while(あるいはfor)の文字下げと同じ文字下げになっている次の行に処理が移ります。Pythonでは、処理手順の制御を文字下げによって行うので、文字下げはどうでも良いC言語とは違い、文字下げを厳格に扱う必要があります。
かくして、前回のプログラムを修正したものが以下のようになります。文字下げ(インデント)は空白文字4文字で表しています。プログラム中の‘#’は、その行の‘#’以下行末までが、注釈であって、実行するプログラムの記述ではないことを表します。以下では、各行の動きを注釈(コメント)で示しました。コメントが長いので、IDLEのウィンドウを思い切り横長にしてください。
まずは、クラス定義のadclass.pyは以下のようになっています。
class person:
def __init__(self, name, postcode, address, telno): # class personのコンストラクタ
self.name = name
self.postcode = postcode
self.address = address
self.telno = telno
def show(self): # class personの表示用メソッド
str1 = "氏名:{0} 住所:〒{1} {2} 電話:{3}".format(self.name, self.postcode, self.address, self.telno) # 文字列str1に住所録1行分の表示内容を設定
print(str1)
def csvline(self): # class personのファイル書き込み用メソッド
str1 = "%s,%s,%s,%s" % (self.name, self.postcode, self.address, self.telno) # 文字列str1に住所録1行分(','で区切って要素を並べる)の記述を設定
return str1 # 文字列str1を返します。
次に、プログラム本体adbook.pyの内容です。
import adclass # adclass.pyを使うことを宣言します。
import os # os.pyを使うことを宣言します。
cont_flag = True # 途中で処理を中断する場合にFalseにします。
book = [] # 住所録オブジェクトです。単一住所録データが並ぶリストです。最初は空です。
org_path = os.getcwd() # カレントディレクトリは、org_pathに保持します。
print(org_path) # カレント・ディレクトリを表示させて確認します。
ad_path = "C:\\python\\addressbook" # カレント・ディレクトリを住所録ディレクトリC:\python\addressbookに設定します。
print("カレント・ディレクトリを%sに変更します。" % ad_path)
print("作業開始") # プログラムが始まったことを表示します。
os.chdir(ad_path) # カレント・ディレクトリをad_pathに設定します。
ad_book = ad_path + "\\" + "adbook.csv" # ad_bookは住所録のパス名です。
if os.path.exists(ad_book): # os.path.exists(a)は、aというパス名のファイルが存在するときにTrueを返します(住所録ファイルが存在しなければ読み込めないので、あらかじめ存在を確認する必要があります)。
print("住所録:%sの存在を確認したので読み込みます" % ad_book)
fd1 = open(ad_book, "r") # 住所録が存在すればopenし、ファイル・オブジェクトをfd1に返します。
for str1 in fd1: # ファイル・オブジェクトfd1の中に行が存在する限り、str1に読み込みます。
ncomma = str1.count(',') # ','の数を数えて、ncommaに入れます。
if ncomma == 3: # ','の数が3のときに以下を実行します。
d = str1.split(',') # str1をcommaで分離してリストdを作ります。
e = adclass.person(d[0], d[1], d[2], d[3]) # リストdの要素からpersonオブジェクトeを作ります。
book.append(e) # personオブジェクトeを住所録オブジェクトbookに付加します。
else: # commaが3個でなければエラーです。
print("{0} is broken".format(ad_book)) # ファイルが壊れていると表示
cont_flag = False # 以後処理を継続しないようにします。
fd1.close() # 住所録ファイルを閉じます。
if cont_flag: # cont_flagがFalseなら住所録表示は行いません。
for c in book: # 住所録リストにpersonデータcがある限り繰り返します。
c.show() # オブジェクトcのshow()メソッドでデータを表示させます。
count1 = 0 # count1:キーボードから新規に入力された数,これを初期化します。
cont_flag2 = True # Trueならキーボードからの入力を継続します。
if cont_flag: # cont_flag=Trueのときのみ処理を継続します。
while cont_flag2: # cont_flag2=Trueのときのみ住所録データ入力を継続します。
name = input("名前:") # 名前をキーボードから入力します。
if len(name) == 0: # name文字列の長さは0か?
cont_flag2 = False # name文字列の長さが0なら入力終了。
break # whileループから抜けます。
postcode = input("郵便番号(012-3456):") # 郵便番号をキーボードから入力します。
if len(postcode) == 0: # 文字列postcodeの長さは0か?
cont_flag2 = False
break
address = input("住所:") # 住所をキーボードから入力します。
if len(address) == 0: # 文字列addressの長さは0か?
cont_flag2 = False
break
telno = input("電話番号:") # 電話番号をキーボードから入力します。
if len(telno) == 0: # 文字列telnoの長さは0か?
cont_flag2 = False
break
a = adclass.person(name, postcode, address, telno) # 入力された名前、住所などでperson classのインスタンスaを作ります。
book.append(a) # リストaを住所録オブジェクトbookに付加します。
count1 += 1 # count1を1つ増やします。
if count1 > 0: # count1は正か?count1=0なら入力がないのでファイル書き出しは行いません。
print("新規入力があったので住所録:%sに出力します。" % ad_book)
fd1 = open(ad_book, mode='w') # 書き込みモードで住所録:ad_bookを開きます。
for c in book: # 住所録リストにpersonデータcがある限り繰り返します。
fd1.write(c.csvline()) # personオブジェクトcのcsvline()メソッドで住所録ファイルの1行分を作りファイルに書き込みます。
fd1.write('\n') # 住所録ファイルの最後の行の[LF]を書き込みます。
fd1.close() # 住所録ファイルを閉じます。
os.chdir(org_path) # カレント・ディレクトリを復旧します
print("作業完了") # 画面に「作業完了」を表示します。
ここで、adbook.pyを編集している画面のRunメニューの中の[Run Module]をクリックさせると、プログラムを動作させることができます。
プログラムの中にミスがあると、
例えば、if cont_flag:の‘:’が抜けていて、if cont_flagになっていたりすると、この行が赤色で表示されて、expected ‘:’と言われてしまいます。
count1 += 1をcont += 1とタイプミスして動作させると、赤字で、
Traceback (most recent call last):
File “C:\python\addressbook\adbook.py”, line 55, in
cont1 += 1
NameError: name ‘cont1’ is not defined. Did you mean: ‘count1’?
と表示されて、55行目にcont1 += 1を書かれているのは、count1の間違いじゃないのか、と言ってきます。
cont_flag = TRUEと書いたりすると、
Traceback (most recent call last):
File “C:\python\addressbook\adbook.py”, line 3, in
cont_flag = TRUE #
NameError: name ‘TRUE’ is not defined. Did you mean: ‘True’?
と言われて、Trueだろ、と言ってきます。
こんな感じで、プログラム中に問題があると、正常に動作できないので、モジュール中の行番号いくつのところの問題点を指摘してきます。これがインタプリタとして動かせるPythonの利点です。
初期状態では、編集画面に行番号は出ていませんが、Optionsメニューの中の[Show Line Numbers]をクリックすると行番号が表示されます。[Hide Line Numbers]をクリックすると行番号が消えます。行番号が表示されていなくても、ウィンドウの右下に、カーソルのいる位置の行番号と桁位置が表示されています。
こうして、文句を言われないようにプログラムを修正していき、問題がなくなると、正常動作を始めます。最初は、住所録がないので、
C:\python\addressbook
カレント・ディレクトリをC:\python\addressbookに変更します。
作業開始
名前:
となって入力待ちになります。「名前:」に続いて、
名前:青木三郎[Enter]
と入力すると、
郵便番号(012-3456):
となって入力待ちになります。続いて、
郵便番号(012-3456):104-0751[Enter]
と入力すると、
住所:
となって入力待ちになります。続いて、
住所:東京都中央区銀座4-10-35[Enter]
と入力すると、
電話番号:
となって入力待ちになります。続いて、
電話番号:03-1234-5678[Enter]
と入力すると、再び、
名前:
と出てきて入力待ちになります。もういいや、と、いきなり何も入れずに[Enter]キーを押し、
名前:[Enter]
とすると、
新規入力があったので住所録:C:\python\addressbook\adbook.csvに出力します。
作業完了
と表示してプログラムが終了します。住所録には、青木三郎さんのデータが入っているはずです。再度、Runメニューの[Run Module]をクリックして、プログラムを動かすと、
C:\python\addressbook
カレント・ディレクトリをC:\python\addressbookに変更します。
作業開始
住所録:C:\python\addressbook\adbook.csvの存在を確認したので読み込みます
氏名:青木三郎 住所:〒104-0751 東京都中央区銀座4-10-35 電話:03-1234-5678
名前:
となり、青木三郎さんのデータを表示して入力待ちになります。ここで、「名前:」に鈴木七郎、「郵便番号(012-3456):」に244-0255、「住所:」に東京都大田区西六郷7-8-92、「電話番号:」に、03-8765-4321、と入力し、再度出てきた「名前:」で即[Enter]キーを押すと、「作業終了」が表示されてプログラムが終了します。もう一度、Runメニューの[Run Module]をクリックして、プログラムを動かすと、鈴木七郎さんのデータも入っているので、
C:\python\addressbook
カレント・ディレクトリをC:\python\addressbookに変更します。
作業開始
住所録:C:\python\addressbook\adbook.csvの存在を確認したので読み込みます
氏名:青木三郎 住所:〒104-0751 東京都中央区銀座4-10-35 電話:03-1234-5678
氏名:鈴木七郎 住所:〒244-0255 東京都大田区西六郷7-8-92 電話:03-8765-4321
名前:
となり、住所録ファイルの内容を表示して、入力待ちになります。
これを繰り返せば、住所録を、Pythonのプログラムで構築できます。ディレクトリC:\python\addressbookに生成される住所録ファイル:addressbook.csvはCSV形式のファイルなので、表計算ソフトEXCELで読み込むことができます。
きょうは、ここまでにします。Pythonの機能を調べながらコーディングし、説明を書いてきたので大変でした。ですが、これを高校の先生が高校生相手に授業でやるのかと思うと頭痛がしてきます。実際に偏頭痛がひどくて。
Pythonが高機能な処理系であることはわかってきましたが、高機能であるがゆえに複雑でとっつきにくく、Pythonは教育現場には向かないなあ。C言語のようなprimitiveな言語の方がよくないですかね?C言語は、もともとアセンブラの負担を軽くするために作られているので、ポインタなど、コンピュータの動作を理解しやすく、input(“名前:“)なんてするよりも、fputs()関数で一々画面に“名前:“と表示させて、getc()とかgetchar()関数でキー入力待ちにさせ、入力されたキーを解析して名前データを構成する関数を自作させる、という方が、コンピュータの仕組みまで理解できて良いのではないか、と私は思います。
ブログTOP TOPページに戻る