ものづくりブログ

ゼロからスタートして何かを作って世に出すまでのことを書くブログです.

Python学習用課題に取り組んでみた(2次回答)

こんにちは。

前回に引き続きPython学習用の課題についての記事です。

添削してもらった結果、得たフィードバックは下記のようなものでした。


  • 不正な入力が来た時にプログラムが止まらないよう例外処理を使ったほうが良い
  • 日付のエラー判定はdateクラスを使うと良い

ふむ。



というわけで書いたプログラムが以下のとおりです。

# -*- coding: utf-8 -*-

from datetime import date

#社員情報クラス
class employee:

    def __init__(self,id,name,bd,salary):
        self.id = id
        self.name = name
        self.bd = bd
        self.salary = salary


#社員登録
def register(employeeid):

    print "名前を入力してください"
    tempname = raw_input()
    while(1):
        print "生年月日を入力してください(yyyy/mm/ddまたはyyyymmddの形で)"
        tempbd = raw_input()
        bdlist = tempbd.split("/")
        bd = []
        if len(bdlist) != 3:
            if tempbd.isdigit() and len(tempbd) == 8:
                try:
                    bd = date(int(tempbd[:-4]),int(tempbd[-4:-2]),int(tempbd[-2:]))
                except:
                    print"不正な値です。"
                    continue
                break
            else:
                print "不正な値です。再度入力してください。"
        else:
            try:
                bd = date(int(bdlist[0]),int(bdlist[1]),int(bdlist[2]))
            except:
                print "不正な値です。"
                continue
            break

    while(1):

        print "月額の給与を入力してください(8桁まで)"
        tempstrsalary = raw_input()
        try:
            tempintsalary = int(tempstrsalary)
        except:
            print "不正な値です。"
            continue
        if tempintsalary > 0 and tempintsalary < 100000000:
            break
        else:
            print "不正な値です。"

    EmployeeList.append(employee(employeeid,tempname,bd,tempintsalary))

    employeeidtemp = employeeid + 1

    return(employeeidtemp)

#社員情報照会
def intro():

    print "*****************************"
    print "ID   名前       生年月日     給与"
    print "*****************************"
    for i in EmployeeList:
        string = str(i.id) + "   " + i.name + "    " + str(i.bd.year) + "年" + str(i.bd.month) +"月" + str(i.bd.day) + "日" + "      " + '{:,d}'.format(i.salary) + "円"
        print string
    print "*****************************"


#削除
def delete(requiredid):

    j = 0
    if requiredid == 0:
        print "削除する社員のIDを入力してください"
        deleteid = input()

    else:
        deleteid = requiredid

    for deletenum in EmployeeList:
        if deletenum.id == deleteid:
            deletedList = EmployeeList.pop(j)
            if requiredid == 0:
                print "%sを削除しました" % deletedList.name
            break
        j = j + 1
    print "該当するIDの社員情報がありませんでした"

#編集
def edit():

    print "更新したい社員のIDを入力してください"
    editid = input()


#削除関数と登録関数により編集を実施
    delete(editid)
    register(editid)




if __name__ == "__main__":

    employeeid = 1  #社員IDを連番にするための変数の初期値
    EmployeeList = []   #社員リスト

    while(1):
        print "<MENU>\n"
        print "========================"
        print "1. 登録"
        print "2. 紹介"
        print "3. 削除"
        print "4. 更新"
        print "========================"

        select = raw_input("実行する処理を半角数字で入力")

        if select == "1":
            employeeid = register(employeeid)
        elif select == "2":
            intro()
        elif select == "3":
            delete(0)
        elif select == "4":
            edit()
        else:
            print "無効なコマンドです。"

1箇所ずつ順に見ていきます。
まずはdateクラスを使うためにimportします。

from datetime import date

次に実際にdateクラスのインスタンスを作成し、代入するところを見てみます。

        tempbd = raw_input()
        bdlist = tempbd.split("/")
        bd = []
        if len(bdlist) != 3:
            if tempbd.isdigit() and len(tempbd) == 8:
                try:
                    bd = date(int(tempbd[:-4]),int(tempbd[-4:-2]),int(tempbd[-2:]))
                except:
                    print"不正な値です。"
                    continue
                break
            else:
                print "不正な値です。再度入力してください。"

ここは添削されたところではないのですが、日付の入力はyyyy/mm/ddだけでなく、yyyymmddでも良いようにすると仕様書にあったため実装した箇所です。
if文により、ユーザが入力した文字列を/で区切った時に3個の要素にならないかを見た後(if len(bdlist) != 3:)、それがTrueだった時(3個でない時)にその文字列が全て数字に変更可能(isdigit())で、かつ8桁であることを判定しています(if tempbd.isdigit() and len(tempbd) == 8:)。
もしこれがTrueなら下4桁を削った値を年、その次の2桁を月、そして最後の2桁を日としてbdという変数に代入しています[2]。
ここで登場するのがdateクラスです。
bdにはdateクラス形式で代入しているので、無効な日付であればエラーを返してくれます。
単純にエラーを返すだけではそこで処理が止まってしまうため、例外処理(try,exception)を使用しました[3]。



次はユーザが入力した文字列を/で区切った時に3個の要素になる場合の処理を見てみます。

        else:
            try:
                bd = date(int(bdlist[0]),int(bdlist[1]),int(bdlist[2]))
            except:
                print "不正な値です。"
                continue
            break

こちらも先ほどと同様にdateクラスを使用するように改造しました。
また、例外処理を用いることで予期せぬ入力にも対応するようにしました。



次はもう1箇所例外処理を導入した箇所です。

        tempstrsalary = raw_input()
        try:
            tempintsalary = int(tempstrsalary)
        except:
            print "不正な値です。"
            continue
        if tempintsalary > 0 and tempintsalary < 100000000:
            break
        else:
            print "不正な値です。"

給与を入力する箇所です。ここでは例外処理を使うこと自体は生年月日の箇所と同じですが、今回はdateのようなクラスが存在しないため、値が正しいかどうかを判断する必要があります。
そこで、try以下で処理を行った後、エラーが無かった場合もif文を使用して0以下でないか、また8桁を超えていないかを判定しています。



以上が添削を受けて私が修正した部分です。



参考文献
[1]Python式日本語版「8.1. datetime — 基本的な日付型および時間型」(http://docs.python.jp/2/library/datetime.html)
[2]PythonWEB「スライスを使った部分文字列の取得」(http://www.pythonweb.jp/tutorial/string/index11.html)
[3]python-izm「例外処理」(http://www.python-izm.com/contents/basis/exception.shtml)

部屋の使用状況をTweetするデバイス(Raspberry Pi)の再検証に失敗した

 

こんにちは.

 

以前行った検証の結果を受けて,改良を加えた会議室モニタリングシステム(仮名)の再検証を行おうと思いました.

 

 

 

1. 改良点

 

何らかの原因でWifi接続が切断してしまった時に再接続しないという問題があったので[1],wicd-cursesを使用して自動的に再接続するよう設定しました.

また,人が少ない場合に退室したと判断するケースがあったので[1],プログラムを少し改修しました.

 

Daisukekmr / raspberrypiShellscript / Downloads — Bitbucket

 

具体的には,PIRセンサが30秒反応しない場合に退室と判定していたのを,少し伸ばして45秒にしました.ここはチューニングが必要と感じたので,変数を使用してより簡単に変化させられるようにしました.またこれにより将来的には外部から秒数を調整するなんてことも視野に入れています.

 

その他設置環境は初回の検証と全く同じで,設置期間は18:00から翌日の18:30までです.

 

 

 

2. 検証失敗

 

ただここで1点大きな問題が発生しました.

 

前回接続していたネットワークが業務に使用していないネットワークで,WEP接続だったのですが,wicd-cursesを使用するとWEPでは接続できないことがわかりました.

 

そこで業務に使用しているwpa2接続のネットワークへ試験的に接続してみたのですが,これではttytterのoAuth認証が通らないことが判明しました.ちなみにこのネットワークではapt-getのupdateも失敗するため,何らかの特殊な理由があるのだと想像しているのですが,現在までのところ解明できていません.teratailで質問したり,Googleで国内外のサイトを探しているのですが同様の事例が出てこないため,かなり希少な例なのだと思います.

 

 

 

3. 対応

 

結論として,WEP接続では再接続ができないということと,wpa2ではttytterが使えないということになってしまったので,twitterへのつぶやきで部屋の使用状況を取得することを諦めました.

その代わり,ラズパイ内にデータベースを構築し,PHPまたはPythonを使用してそこに書き込むことでログを取ることにしました.

これにより,外部のネットワークと接続することなくログを溜めることができるので,効果の検証ができます.

とりあえず次の1手が決まったので,Pythonの学習と,それをラズパイで使用する方法について学んでいきたいと思います.

参考文献

[1]ものづくりブログ「部屋の使用状況をTweetするデバイスを会社の会議室で使ってみた」(http://daisukekmr.hatenablog.com/entry/2015/02/10/193000)

Python学習用課題に取り組んでみた(1次回答)

こんにちは.

前回の記事で,Python勉強用の課題を掲載しました.今回はそれに対する1回目の私の回答を掲載します.1回目というのは,このあとこの回答を元にエンジニアに添削してもらい,色々と気づきがあったので,あえて最終形をいきなり掲載するのではなく,最初に私が自分で書いたコードを掲載することにしました.

私が1回目に書いたコードは以下のとおりです.

# -*- coding: utf-8 -*-

#社員情報クラス
class employee:

    def __init__(self,id,name,bd,salary):
        self.id = id
        self.name = name
        self.bd = bd
        self.salary = salary

#生年月日クラス
class p_bd:

    def __init__(self,year,month,date):
        self.year = year
        self.month = month
        self.date = date

#社員登録
def register(employeeid):

    print "名前を入力してください"
    tempname = raw_input()
    print "生年月日を入力してください(yyyy/mm/ddの形で)"
    while(1):
        tempbd = raw_input()
        bdlist = tempbd.split("/")
        if len(bdlist) != 3:
            print "不正な値です.再度入力してください"
        elif int(bdlist[0]) > 2015:
            print "不正な値です.再度入力してください"
        elif int(bdlist[1]) > 12 or int(bdlist[1]) < 1:
            print "不正な値です.再度入力してください"
        elif int(bdlist[2]) > 31 or int(bdlist[2]) < 1:
            print "不正な値です.再度入力してください"
        else:
            break
    bdclass = p_bd(bdlist[0],bdlist[1],bdlist[2])

    print "月額の給与を入力してください(8桁まで)"
    while(1):
        tempsalary = raw_input()
        if tempsalary.isdigit():
            tempintsalary = int(tempsalary)
            if tempintsalary <= 99999999:
                break
            else:
                print "桁数が多すぎます.再度入力してください"
        else:
            print "数値ではありません.再度入力してください"

    EmployeeList.append(employee(employeeid,tempname,bdclass,tempintsalary))

    employeeidtemp = employeeid + 1

    return(employeeidtemp)

#社員情報照会
def intro():

    print "*****************************"
    print "ID   名前       生年月日     給与"
    print "*****************************"
    for i in EmployeeList:
        string = str(i.id) + "   " + i.name + "    " + i.bd.year + "年" + i.bd.month +"月" + i.bd.date + "日" + "      " + '{:,d}'.format(i.salary) + "円"
        print string
    print "*****************************"


#削除
def delete(requiredid):

    j = 0
    if requiredid == 0:
        print "削除する社員のIDを入力してください"
        deleteid = input()

    else:
        deleteid = requiredid

    for deletenum in EmployeeList:
        if deletenum.id == deleteid:
            deletedList = EmployeeList.pop(j)
            if requiredid == 0:
                print "%sを削除しました" % deletedList.name
            break
        j = j + 1
    print "該当するIDの社員情報がありませんでした"

#編集
def edit():

    print "更新したい社員のIDを入力してください"
    editid = input()


#削除関数と登録関数により編集を実施
    delete(editid)
    register(editid)




if __name__ == "__main__":

    employeeid = 1  #社員IDを連番にするための変数の初期値
    EmployeeList = []   #社員リスト

    while(1):
        print "<MENU>\n"
        print "========================"
        print "1. 登録"
        print "2. 紹介"
        print "3. 削除"
        print "4. 更新"
        print "========================"

        select = raw_input("実行する処理を半角数字で入力")

        if select == "1":
            employeeid = register(employeeid)
        elif select == "2":
            intro()
        elif select == "3":
            delete(0)
        elif select == "4":
            edit()
        else:
            print "無効なコマンドです。"

順に解説していきます.

以下はC言語でいうところのmain関数に当たる部分です.プログラムはまずここから実行されます.

if __name__ == "__main__":

    employeeid = 1  #社員IDを連番にするための変数の初期値
    EmployeeList = []   #社員リスト

    while(1):
        print "<MENU>\n"
        print "========================"
        print "1. 登録"
        print "2. 紹介"
        print "3. 削除"
        print "4. 更新"
        print "========================"

        select = raw_input("実行する処理を半角数字で入力")

        if select == "1":
            employeeid = register(employeeid)
        elif select == "2":
            intro()
        elif select == "3":
            delete(0)
        elif select == "4":
            edit()
        else:
            print "無効なコマンドです。"

最初に社員IDを表す変数(employeeid)を作成し,そこに初期値1を代入しています.今後社員情報が追加される度にこの変数に1を足していくことで,IDを連番にします.
次にこの社員情報を集めたリストを作成します.最初は空のリストとして作成しています[1].

次にwhileでくくっていますが,条件が(1)になっていますのでこの中身を無限にループします.
whileの中身を見てみましょう.
printを使用してどのようなことができるのかを表示しています.それぞれの処理に1〜4までの番号を振って表示しています.

次に出てくる,raw_inputは,コマンドラインからの入力を受け付ける関数です[2].引数に文字列を渡すと入力欄の前にメッセージを表示することができます(下記).

f:id:daisukekmr:20150222202652p:plain

ここでユーザに1〜4までの数字を入力してもらい,この後のif文で実行する動作を指定しています.1〜4の数字以外のデータが入力した場合は無効なコマンドであることを通知し,再度入力させるようにしています.



次にそれぞれの動作を実行する関数を見ていきます.まずは登録です.

#社員登録
def register(employeeid):

    print "名前を入力してください"
    tempname = raw_input()

関数を定義し,ユーザに社員の名前を入力させています.main関数からこの関数が呼ばれた時に引数として社員IDが渡されています.



次は生年月日の入力です.

    print "生年月日を入力してください(yyyy/mm/ddの形で)"
    while(1):
        tempbd = raw_input()
        bdlist = tempbd.split("/")
        if len(bdlist) != 3:
            print "不正な値です.再度入力してください"
        elif int(bdlist[0]) > 2015:
            print "不正な値です.再度入力してください"
        elif int(bdlist[1]) > 12 or int(bdlist[1]) < 1:
            print "不正な値です.再度入力してください"
        elif int(bdlist[2]) > 31 or int(bdlist[2]) < 1:
            print "不正な値です.再度入力してください"
        else:
            break
    bdclass = p_bd(bdlist[0],bdlist[1],bdlist[2])

生年月日は書式が決まっているので,きちんとそれに沿っているかを判定する必要があります.
入力はyyyy/mm/ddで入力させることにし,まずはきちんとそれに則っているかを判定します.具体的には,"/"で文字列を区切って[3],きちんと3つの文字列に区切られているかを判定します.
もしこれが問題なければ次に区切られた文字列の1番目,すなわち年が未来になっていないかを判定します.ここは1900年以前など過去でも現実的でない数値の場合もエラーと判定してしまっても良いかもしれません.
次に月と日についても同様の判定を行います.実際は2/31などありえないのですが,その辺りを気にするとうるう年を気にする必要が出てきて,かなり煩雑になるので一旦無視しています.
全て問題なければbreakによって繰り返しから脱し,次の行で年月日を変数に代入します.
後ほど述べますが,生年月日は年と月と日をそれぞれわけてクラスとして定義していますので,その形式で代入しています.



次は給与の入力です.

    print "月額の給与を入力してください(8桁まで)"
    while(1):
        tempsalary = raw_input()
        if tempsalary.isdigit():
            tempintsalary = int(tempsalary)
            if tempintsalary <= 99999999:
                break
            else:
                print "桁数が多すぎます.再度入力してください"
        else:
            print "数値ではありません.再度入力してください"

給与は数値だけで入力してもあり,8桁以内と指定しています.
文字列を整数型に変更するにはint()関数を使用すればよいのですが[4],整数に変更できない文字列だった場合エラーになってしまうため,事前に整数に変換可能な文字列かどうかを判定するため,isdigit()関数を使用しています[5].
こうして整数型に変更したら,あとは桁数の判定を行い,問題があれば再入力を促します.



次はこれまでユーザに入力させた情報を社員情報としてリストに追加します.

    EmployeeList.append(employee(employeeid,tempname,bdclass,tempintsalary))

    employeeidtemp = employeeid + 1

    return(employeeidtemp)

今回のプログラムでは,社員情報をクラス化しています.リストは複数の社員情報をそれぞれクラスとして保持します.
上記では,EmployeeListというリストに,employeeクラスの形式で社員情報を保持しています.このクラスの定義と中身については次に見ていきます.
関数の最後にemployeeidに1追加して,返り値として返して終了です.



それでは次に社員情報を入れるクラスの中身を見てみます.

#社員情報クラス
class employee:

    def __init__(self,id,name,bd,salary):
        self.id = id
        self.name = name
        self.bd = bd
        self.salary = salary

employeeという名前のクラスです[1].持っている値は,id,name,bd,salaryの4個です.それぞれ,社員ID,名前,生年月日,給与を表しています.この中で生年月日だけは年,月,日という3項目を分けて保持したほうが都合が良いため,別途クラスを定義しています.次はそれを見ていきます.

#生年月日クラス
class p_bd:

    def __init__(self,year,month,date):
        self.year = year
        self.month = month
        self.date = date

生年月日クラスは,年,月,日という変数を持っています.こうしておくことで表示する際にyyyy/mm/dd形式で表示するかyyyy年mm月dd日形式で表示するか選ぶのが容易になったり,あとから検索する際に月だけで検索するといったことが容易になります.



クラスの中身を見たところで,動作を表す関数に戻りましょう.次は社員情報の照会です.

#社員情報照会
def intro():

    print "*****************************"
    print "ID   名前       生年月日     給与"
    print "*****************************"
    for i in EmployeeList:
        string = str(i.id) + "   " + i.name + "    " + i.bd.year + "年" + i.bd.month +"月" + i.bd.date + "日" + "      " + '{:,d}'.format(i.salary) + "円"
        print string
    print "*****************************"

ここは単純にprintでIDや名前といった列名を表示し,その下にfor文を使用してリストの最初から最後までを順に表示しています.
まずstringという変数に社員1人分のID,名前,生年月日,給与を間に空白を挟みながら代入しています.
次にprintでそのstringの中身を表示して次のループに進むという繰り返しです.
idは整数型なので,printで表示するために文字列に変換する必要があります.これはstr()関数を使用することで実行できます.
また,クラスの中の要素を取り出すときは,インスタンス名.要素名を使用するため,ここではi.idのように表記しています.
誕生日に関してはクラスの中にクラスが入っているため,i.bd.monthのように連ねて書くことで取り出しています.
給与は3桁区切りで表記する必要があるので,format関数を使用しました[6]



次は削除の関数の中身を見ていきます.

#削除
def delete(requiredid):

    j = 0
    if requiredid == 0:
        print "削除する社員のIDを入力してください"
        deleteid = input()

後述しますが,削除関数は編集関数の中でも呼ばれるので,mainから呼ばれたのか編集の関数から呼ばれたのかを判断する必要があります.そこで,引数としてrequiredidというものを与えることにしました.これが0であればmainから呼ばれた処理,すなわち単純に社員情報を削除する処理で,それ以外の数字であれば指定したIDの社員情報を編集するための削除であるという判断をします.
上記はrequiredidが0の場合なので,単純な削除の処理の場合の処理です.
削除したい社員のIDを入力させて,それはdeleteidに代入しています.



次にrequiredidが0以外の時の処理を見てみます.

    else:
        deleteid = requiredid

編集したい社員のIDをdeleteidに代入しています.後でも述べますが,編集は指定した社員の情報を削除して,上書きするという方法を取っているため,ここで指定した番号を1回削除する必要があります.



次は実際に削除する処理を見てみます.

    for deletenum in EmployeeList:
        if deletenum.id == deleteid:
            deletedList = EmployeeList.pop(j)
            if requiredid == 0:
                print "%sを削除しました" % deletedList.name
            break
        j = j + 1
    print "該当するIDの社員情報がありませんでした"

リストの中身を1個ずつ見ていき,その社員IDがdeleteidと一致するかどうかを判断していきます.
もし一致しなければ何もせずに次のループに進み,一致すればその社員情報を削除します.
requiredidが0であればmainから呼ばれた純粋な削除なので,ユーザに削除したことをprintで伝えます.
requiredidが0以外であればこれは編集処理の一部ですので,削除したことを表示するとユーザを驚かせてしまうため,何も表示しません.
削除が完了したらbreakによりループを抜けます.



最後に編集の処理を見てみます.

#編集
def edit():

    print "更新したい社員のIDを入力してください"
    editid = input()


#削除関数と登録関数により編集を実施
    delete(editid)
    register(editid)

ここはこれまでに記述した処理の組み合わせで実現しています.
最初にユーザから編集したい社員のIDを入力してもらい,それをdelete関数とregister関数に渡して処理を行っています.



細かいところでユーザの入力によって予期せぬことが起こったりするのですが,おおまかな流れはできました.次は私の知人のエンジニアにアドバイスしてもらって修正したものについて解説していきます.



参考文献
[1]Python-izm(http://d.hatena.ne.jp/alicehimmel/20101122/1290398381http://www.python-izm.com/)
[2]Python プログラミング 講座「ファイル入出力: 標準入出力、ファイルオブジェクト」(http://bacspot.dip.jp/virtual_link/www/si.musashi-tech.ac.jp/www/Python_IntroProgramming/02/index-2a.html)
[3]GeSource「文字列を分割する」(http://www.gesource.jp/programming/python/code/0016.html)
[4]Python式日本語版「2.組み込み関数」(http://python.g.hatena.ne.jp/muscovyduck/20080816/p1http://docs.python.jp/3.3/library/functions.html#int)
[5]主にプログラムを勉強するブログ「isdigit()について」(http://d.hatena.ne.jp/artgear/20120217/1329493335)
[6]こしごぇ(B)「Python の書式指定など」(http://koshigoeb.hateblo.jp/entry/2013/08/24/160215)