七転八倒プログラミング!(退屈なことはPythonにやらせよう_第7章演習_7.18.1)


1.はじめに

こんにちは。yutomanです。情シスの仕事を自動化したいという下心からpythonの勉強を始めた初学者です。
『退屈なことはPythonにやらせよう ―ノンプログラマーにもできる自動化処理プログラミング』(著・Al Sweigart,訳・相川 愛三,オライリージャパン,2017)を買ったは良いのですが理解するまでに七転八倒しましたので、せっかくならばこの七転八倒泥まみれの様子をお見せして、同じ空の下にいるどこかの初学者の一助になりたく、この記事を書きます。

2.転びながら解いていく

1.問題文を読む。

引数として渡されたパスワード文字列が強いかどうかを正規表現を用いて確認する関数を書きなさい。

この演習でやることは端的に言うと「関数を書くこと」です。僕みたいな初学者は「あれ?オレ何やってたんだっけ?」という自体に陥りがちなのでやることをはっきりさせておきます。

どんな関数か、と言うと「引数として渡されたパスワードが強いかどうか判断する」関数ということがわかります。

ちなみに、何をもって「強い」と言っているのかもきちんと記載がありますね。

①8文字以上で、
②大文字と
③小文字を含み、
④1つ以上の数字を含むもの

つまり関数の引数として与えられた文字列が、この4つの条件を満たしているか判断する関数を作成すれば良いということがわかります。

そして最後に、

パスワードの強さを調べるためには、複数の正規表現パターンを使う必要があるでしょう。

との大ヒント。つまりここで「複数 = ①~④の条件なんじゃないか?」となんとなく予想がつきます。

2.やることをはっきりさせる。

実際にプログラムを書く前に、問題文を読んでやらなければならないとわかったことを整理します。

まずは正規表現を扱う問題なので、
1.reモジュールのインポートです。

次に問題の核心となる
2.パスワードの強度を確認する関数の作成です。
関数名は何でも良いと思いますが、僕は"chk_pass()"と名付けました。

後はユーザーが使いやすいようにできると良いでしょう。具体的には
3.パスワードの入力を促し、強度の結果を表示する部分の作成です。

3.書いて実行してみる。

1.reモジュールのインポート
2.パスワードの強度を確認する関数の作成
3.パスワードの入力を促し、強度の結果を表示する部分の作成

の順番でプログラムを書いてみました。

chk_pass_01
#! python3

#1.reモジュールのインポート
import re

#2.パスワードの強度を確認する関数の作成
def chk_pass(password):
    if len(password) < 8:              #8文字以下だったら条件に合わないので
        return False                   #Falseを返す。
    if re.search(r'[a-z]', password):  #search()メソッドでpasswordの中に小文字[a-z]があるか探し、マッチすれば
        return True                    #Trueを返す。
    if re.search(r'[A-Z]', password):  #search()メソッドでpasswordの中に大文字[A-Z]があるか探し、マッチすれば
        return True                    #Trueを返す。
    if re.search(r'[0-9]', password):  #search()メソッドでpasswordの中に数字[0-9]があるか探し、マッチすれば
        return True                    #Trueを返す。

#3.パスワードの入力を促し、強度の結果を表示する部分の作成
print("パスワードを入力してください。")
password = input()                     #変数passwordにユーザーが入力したパスワードを格納する。

if chk_pass(password) == False:        #変数passwordを引数に関数chk_passを実行しFalseなら「パスワードが弱い」
    print("パスワードが弱いです。もう一度実行してください。")
elif chk_pass(password) == True:       #変数passwordを引数に関数chk_passを実行しTrueなら「パスワードが強い」
    print("適切なパスワードです。")

考え方としては
与えられた4つの条件をそれぞれif文で正か偽か判断する!というシンプルなものです。
さあ実行してみましょう。

chk_pass_01r
パスワードを入力してください
yutomanDA             #数字が無いからダメなはず・・・!
適切なパスワードです      #あれれ?!

全然適切じゃないですね。。。

4.基本が大事。

chk_pass_01
    if re.search(r'[0-9]', password):  #search()メソッドでpasswordの中に数字[0-9]があるか探し、マッチすれば
        return True                    #Trueを返す。

ここがTureじゃないのに関数の実行時には結果がTrueとなり、「適切なパスワードです。」と表示されている模様です。

一旦if文の基本に立ち返りましょう。テキストp31「2.7.1 if文」から下記抜粋。

if文の節(すなわち、if文に続くブロック)は、条件式がTrueのときに実行されます。条件式がFalseならブロックの実行はスキップされます。

ここです…。
変数passwordの中に0~9のどの数字も無かったので、条件式の判定としてはFalse。すると return Tureのブロックはスキップされます。つまり"if文の判定がTrueの時に戻り値としてTrueを返す"以外の動きをしてくれないのです。if文の判定がFalse(条件を満たしていない)時は、そのまま節をスキップしてプログラムは無情にも進んでいきます。

ちなみに、このif文自体の判定としてFalseは確かに出ているはずなのですが、関数chk_passの戻り値は直前のif文でTrueが返っています。したがって関数chk_pass実行の結果、「適切なパスワードです。」が表示されたのです。

要は、Fasleだったことが無かったことにされてしまっています。

5.直して実行した。

Falseだったことが無かったことにされてしまっていたのであれば、Falseの時はしっかりFalseを返すようにすれば良いということです。具体的に言えば、if文の判定がTrueになった時に戻り値としてFalseを返すようにする必要があります。

そこで、if文ではなく、if not文を用います。

chk_pass_02
#! python3

#1.reモジュールのインポート
import re

#2.パスワードの強度を確認する関数の作成
def chk_pass(password):
    if len(password) < 8:                  #8文字以下だったら条件に合わないので
        return False                       #Falseを返す。
    if not re.search(r'[a-z]', password):  #search()メソッドでpasswordの中に小文字[a-z]が無ければ
        return False                       #Falseを返す。
    if not re.search(r'[A-Z]', password):  #search()メソッドでpasswordの中に小文字[A-Z]が無ければ
        return False                       #Falseを返す。
    if not re.search(r'[0-9]', password):  #search()メソッドでpasswordの中に数字[0-9]が無ければ
        return False                       #Falseを返す。
    return True                            #全ての条件をクリアしていればTrueを返す。

#3.パスワードの入力を促し、強度の結果を表示する部分の作成
print("パスワードを入力してください。")
password = input()

if chk_pass(password) == False:
    print("パスワードが弱いです。もう一度入力してください。")
elif chk_pass(password) == True:
    print("適切なパスワードです。")

これで
if not 文がTrue、すなわち「小文字がない/大文字がない/数字がない」時は関数としてFalseを返し
if not 文がFalse、すなわち「小文字がある/大文字がある/数字がある」時は関数としてTrueを返します。
(それぞれのif notがFalseの場合、直後のブロックはスキップされるので、最終的にはreturn Tureにたどり着く仕組みです。)

実行してみましょう。

chk_pass_02r
パスワードを入力してください
yutomanDa
パスワードが弱いですもう一度入力してください #数字が無いから

パスワードを入力してください
yutoman01
パスワードが弱いですもう一度入力してください #大文字が無いから

パスワードを入力してください
YUTOMAN01
パスワードが弱いですもう一度入力してください #小文字が無いから

パスワードを入力してください
yutoMan01
適切なパスワードです

うまくいきました!!

6.完成!

解答コード(chk_pass_02再掲)
#! python3

#1.reモジュールのインポート
import re

#2.パスワードの強度を確認する関数の作成
def chk_pass(password):
    if len(password) < 8:                  #8文字以下だったら条件に合わないので
        return False                       #Falseを返す。
    if not re.search(r'[a-z]', password):  #search()メソッドでpasswordの中に小文字[a-z]が無ければ
        return False                       #Falseを返す。
    if not re.search(r'[A-Z]', password):  #search()メソッドでpasswordの中に小文字[A-Z]が無ければ
        return False                       #Falseを返す。
    if not re.search(r'[0-9]', password):  #search()メソッドでpasswordの中に数字[0-9]が無ければ
        return False                       #Falseを返す。
    return True                            #全ての条件をクリアしていればTrueを返す。

#3.パスワードの入力を促し、強度の結果を表示する部分の作成
print("パスワードを入力してください。")
password = input()

if chk_pass(password) == False:
    print("パスワードが弱いです。もう一度入力してください。")
elif chk_pass(password) == True:
    print("適切なパスワードです。")

ユーザー目線の使いやすさとかで言うと
while文とcontinueの組み合わせで何度もリトライできるようにしたりとか、入力したパスワードを表示させたりとかしてあげると良いとは思うのですが、今回は最低限の機能だけ実装できればOKということで転がってみました。(才能が無い)

今後も他の演習プロジェクトについて七転八倒解説を書いていきたいと思います!!
yutoman

3.参考

「退屈なことはPythonにやらせよう」演習プロジェクト