『退屈なことはPythonにやらせよう』その5(7章:正規表現)

Python初心者です。『退屈なことはPythonにやらせよう』を走っています。

5回目の今回は、6章をやっていきます~。

文字列と格闘した前回の記事はこちら↓

今回の目次


正規表現の基本の使い方

reモジュールのimportからの一連の流れ

まずは正規表現のモジュールである、「re」をインポートします。

その後の流れも含めてコード内に記載します。

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

regex = re.compile(r"正規表現") #探したい文字列の形を指定
mo = regex.search(regex)       #moはMatchオブジェクト
mo.group()                     #group()メソッドでMatchオブジェクトを出力

という感じです。

文字列の前についている「r」は、「raw文字列」にしますという宣言になります。

これをすることで、バックスラッシュなどをいちいちエスケープしなくても使うことができます。

re.compile()のところで、マッチさせたい文字列のパターンを指定します。

またsearch()が入っているところには、findall()と置き換えることもできます。

searchでは一つのMatchオブジェクトを返しますが、findall()ではマッチするもの全てを返します。

他には見つけたパターンを置き換え(置換す)るメソッドもあります。

sub()メソッドです。

regex = re.compile(r"置換前のパターン")
regex.sub("置換後の文字列", "対象の文字列")

という形で使うことができます。


正規表現の記号まとめ

文字集合

文字の集合を表すものが以下の記号になります。

公式のドキュメンテーションにより詳しくあります。(リンクはこちら)

以下のコードに例を示します。

\d   #0-9
\D   #0-9以外
\w   #文字、数字、下線
\W   #文字、数字、下線以外
\s   #スペース、タブ、改行
\S   #スペース、タブ、改行以外

演習プロジェクトの中でもぼちぼち使っています。

また、「文字」というと記号も入ってしまうため、

[a-z] (小文字)

[A-Z](大文字)

のようにしてアルファベットの範囲を指定することも可能です。

文字列の集合を表す以外にも、様々な記号が意味を持っています。

一例を示すと

#出てくる回数シリーズ
? #直前のグループの0回か1回の出現にマッチ
* #直前のグループの0回以上の出現にマッチ
+ #直前のグループの1回以上の出現にマッチ
{n} #直前のグループのn回の出現にマッチ(nには整数が入る)
{n,} #直前のグループのn回以上の出現にマッチ
{,m} #直前のグループのm回以下の出現にマッチ(mも整数)
{n,m} #直前のグループのnからm回の出現にマッチ (={n|n+1|n+2|・・・|m})

#位置シリーズ
^ #「^文字列」とすると、指定の文字列から始まるものにマッチ
$ #「文字列$」とすると、指定の文字列で終わるものにマッチ

#集合シリーズ
. #改行以外のすべてを表すワイルドカード
[abc0] #[]内の文字を指定する
[^abc0] #[]内以外の文字を指定する

7章:演習プロジェクト

正規表現を利用したパスワードチェッカー

※当ブログの管理人の解答です。あくまでご参考までに

今回の演習プロジェクトの1問目は、

「強いパスワードの検出」

です!

今回の課題として与えられた「強いパスワード」の条件は

  • 8文字以上
  • 大文字を含む
  • 小文字を含む
  • 数字を含む

というものです。

そこで考えた流れは以下の物です。

  1. 8文字以下を if 文で切る
  2. 8文字以上のものに関して、正規表現を用いてチェック

また、正規表現の出現パターンとしては、

大文字、小文字、数字の組み合わせになるので 3C2 (3と2は下付き文字)で、6通りとなります。

すなわち、

  1. 大文字・小文字・数字
  2. 小文字・大文字・数字
  3. 大文字・数字・小文字
  4. 小文字・数字・大文字
  5. 数字・大文字・小文字
  6. 数字・小文字・大文字

です。

大文字は正規表現で表すと、[A-Z]+

小文字は正規表現で表すと、[a-z]+

数字は正規表現で表すと、\d+ (\のところは、コード中ではバックスラッシュになってます)

以上のような仮説をもとに、以下のようなコードを書きました!

#うまくいかなかった例
import re

check_regex = re.compile(r"""
    ([A-Z]+[a-z]+\d+)|
    ([a-z]+[A-Z]+\d+)|
    (\d+[A-Z]+[a-z]+)|
    (\d+[a-z]+[A-Z]+)|
    ([A-Z]+\d+[a-z]+)|
    ([a-z]+\d+[A-Z]+)|
""", re.VERBOSE)

def check_password(password):
    if len(password) >= 8:
        mo = check_regex.search(password)
        if mo != None:
            print(mo.groups())
            print("You got a great password!")
        else:
            print("I am afraid to say, but your password is not strong enough...")
    else:
        print("I am afraid to say, but your password is not strong enough...")

test_password1 = "AsdfsF11Ss"
test_password2 = "ASDd4fD"
test_password3 = "111111212As"
test_password4 = "NEKONEKON"

check_password(test_password1)
check_password(test_password2)
check_password(test_password3)
check_password(test_password4)

"""
出力は
(None, None, None, None, None, None)
You got a great password!
I am afraid to say, but your password is not strong enough...
(None, None, '111111212As', None, None, None)
You got a great password!
(None, None, None, None, None, None)
You got a great password!
"""

出力のところを見ていただくと分かるように、うまくいっていません…

返ってきている値をみても、何を直したらいいのかあんまりよくわからん…


ということで、作戦を変更しました。

全ての条件を if 文を使って回し、一つでも含まれていなければ没にするというものです。

そんな感じで書いたのがこちら

#うまくいった例
import re

def check(password):
    if len(password) < 8:
        return False
    if not re.search(r"[A-Z]", password):
        return False
    if not re.search(r"[a-z]", password):
        return False
    if not re.search(r"\d", password):
        return False
    else:
        return True 

def check_password(password):
    if check(password):
        print("You got a great password!")
    else:
        print("I am afraid to say, but your password is not strong enough...")

test_password1 = "AsdfsF11Ss"
test_password2 = "ASDd4fD"
test_password3 = "111111212As"
test_password4 = "NEKONEKON"

check_password(test_password1)
check_password(test_password2)
check_password(test_password3)
check_password(test_password4)

"""
出力は
You got a great password!
I am afraid to say, but your password is not strong enough...
You got a great password!
I am afraid to say, but your password is not strong enough...
"""

ということで、うまく動きました!

一応他のパターンもいくつか検証してみたのですが、とりあえず上手く分けられていそうです。


正規表現を使ってstrip()メソッド

文字列型の時に用いたstrip()メソッドを再現してみようという課題です。

要件としては

  • 文章の前後の空白文字を除去する
  • 任意の文字列を指定すると、その文字列を両端から除去できる

といったところです。

とりあえず学習中に書いたsub()メソッドなどの書き方を参考にしながら、いちおう動くコードが書けました。

import re

def regex_strip(text, char=r"\s*"):
    mo = re.compile('^[' + char + ']*(.*?)[' + char + ']*$')
    stripped_text = mo.sub(r"\1", str(text))
    print(stripped_text)

#Test
regex_strip("   cat on the bench.   ")
regex_strip("nekonekocat on the beach.nekoneko", char = "neko")

"""
出力は
cat on the bench.
cat on the beach.
"""

ということで、一応動いています。

チェックの仕方も、もう少し賢くやって、全部のものを綺麗に回せるようにしたいですね…


感想

正規表現はかなり奥が深いです…

練習して使いこなせるようにしていきたいですね。

今回から第II部の「処理の自動化」に入ったので、次回の記事からは課題プロジェクトを中心にした記事にしていきたいと思います。

次の記事はこちら↓

『退屈なことはPythonにやらせよう』その5(7章:正規表現)” に対して2件のコメントがあります。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です