Pythonロボティクスコース レッスン 40

テーマ.10-3 ユーザー認証システムの制作

情報を管理するためのデータベースや文章の検索に使われる正規表現について学ぼう

このレッスンで学ぶこと

このレッスンでは、情報を整理して管理するための「データベース」の技術と、ある文章の中から特定の文字列を検索するときなどに使われる「正規表現とマッチング」の技術について新たに学習します。

データベースとは

皆さんの中には、「データべース」という言葉を初めて聞いた人もいるのではないでしょうか。実は言葉を知らないだけで、私たちは普段からインターネットを通して、データベースをよく利用してます。

例えば、GoogleやBingなどの検索エンジンを利用すると、世界中にある多種多様な情報へ簡単にアクセスできます。情報にアクセスできるのは、世界のどこかにそのデータが蓄積されているためです。そして、情報を蓄積・管理する仕組みのひとつとして広く使われているものが、これから説明するデータベースです。

2. 1 データベースの基本

データベースに保管されている情報は、あとから分析や検索ができるように、特定のルールに従って整理されています。この情報の整理の仕方によってデータべースはいくつかの種類に分類されています。

■ データベースの種類

ここでは、5種類のデータベースについて簡単に紹介します。

  • カード型データベース
    1つのまとまった情報を1枚のカードとして管理する古い型のデータベース。現在はほとんど使われていない。
  • key-value型データベース
    キー(key)と値(value)のセットで情報を管理するシンプルなデータベース。動作が軽いため、膨大な情報を管理するときに使われる。
  • リレーショナルデータベース
    テーブルと呼ばれる複数の表で情報を管理する仕組みで、現在最も使われているデータベース。あるテーブルの値が別のテーブルのキーとなっていて、テーブル間の情報に関連性を持たせている。
  • ネットワーク型データベース
    網目状に情報がつながって管理されているデータベース。情報どうしは親子関係で表現されている。
  • オブジェクト指向データベース
    情報を1つのオブジェクトとして管理するデータベース。Pythonのようなオブジェクト指向のプログラミング言語と相性が良く、プログラム内のオブジェクトの形を崩さずに情報を保管することができる。

一般的には、保管する情報の性質や量によって、これらの中から適したタイプのデータベースを選んで使用します。残念ながら、MicroPythonにはkey-value型のデータベースを構築するための機能しか用意されていません。しかし、これだけでもデータベースの良さを体感するには十分です。ここからは実際にデータベースを構築して、どういった機能が提供されているのかをもう少し詳しく見ていきましょう。

2. 2 MicroPythonでのデータベースの構築

Micropythonには、key-value型のデータベースを扱うための、btreeモジュールが用意されています。btreeモジュールで作成したデータベースは、辞書型と同じようにキーを使ってデータを保管したり、データにアクセスしたりすることができますが、辞書型とは次の2点で大きな違いがあります。

  • キーと値はどちらもバイト列である。
  • キーの範囲検索を行うことができる。

では、実際にデータベースの構築とデータベース上の情報を検索するプログラムを作成しながら、使い方を見ていきましょう。

■ データベースを作成する

データベースを作成するためには、情報を記録しておくためのファイルが必要です。このファイルは「バイナリモード」で開きます。

※ バイナリモードとは・・・例えば、テキストモードでは、\r\r\nのような特定の文字列を意味のある改行コードとして扱いますが、バイナリモードではそのまま他の文字列と同様に扱います。つまり、あるがままにデータを読み書きするモードです。

そして、開いたファイルをbtreeモジュールのopen()関数に渡し、データベースのオブジェクトを作成します。

参考:
"r+b""w+b" は、Pythonのopen()関数を使ってファイルを開くときに指定するモードです。それぞれの意味を解説します。

"r+b" モード

  • 読み書き可能なバイナリモードでファイルを開きます。
  • "r"読み取り専用を意味しますが、"+" を付けることで読み書き両方が可能になります。
  • "b"バイナリモードを意味します。バイナリモードでは、ファイルの内容がそのままバイト列として扱われます。
  • すでに存在しているファイルを開きます。ファイルが存在しない場合はエラーが発生します。

"w+b" モード

  • 読み書き可能なバイナリモードでファイルを開きます。
  • "w"書き込み専用を意味しますが、"+" を付けることで読み書き両方が可能になります。
  • "b"バイナリモードです。
  • ファイルが存在しない場合は新しいファイルが作成されます。
  • すでに存在するファイルを開くと、そのファイルの内容は最初に削除されて空っぽになります(既存の内容が消えてしまいます)。

まとめ

"w+b" は、新しいファイルを作成して読み書きするか、既存のファイルの内容を削除してから読み書きしたい場合に使います。

"r+b" は、既存のファイルをバイナリモードで読み書きしたい場合に使います。ファイルがないとエラーになります。

■ データベースに情報を保管する

作成したデータベースのオブジェクトにデータを保管するときは、辞書型と同じ書き方をします。

db[キー] = 値

ただし、キーも値もバイト列である必要があるため、文字列型のencode()メソッドや数値型のto_bytes()メソッドでバイト列型に変換しておきます。例として、以下の情報をデータベースに追加してみましょう。

キー:果物名値:価格(円)
りんご(apple)150
オレンジ(orange)250
ばなな(banana)250
※ 辞書と同様にキーはデータベース内で重ならないようにする必要があります。値は同じものがあっても問題ありません。
追加【11行目~14行目】

13行目まででは、データは一時的にデータベースに保管された状態になっています。データベースの元になっているファイルへ書き込み、永久的に保管する場合は、最後にflush()メソッドを実行します。


参考:「ビッグエンディアン」と「リトルエンディアン」の違い
https://wa3.i-3-i.info/diff112endiannes.html

■ データベースにある情報を検索する

情報を検索する場合も、キーがバイト列であること以外は辞書と同じ使い方ができます。上で追加した3つのデータをデータベースから読み込んでターミナルに表示してみましょう。

【 サンプルコード 2-2-1 】
追加【16・17行目、20行目~23行目、26行目~28行目、31行目~33行目】
(実行結果)

btreeのオブジェクトには、辞書型と同様に、keys()values()items()のように順番にデータを取り出すメソッドも用意されています。また、データベースを使い終えた後は、close()メソッドで閉じ、さらにデータベースで利用していたファイルも閉じるようにしましょう。閉じる順番は必ず「データベース ⇒ ファイル」とすることに注意してください。

■ 範囲検索を行う

keys()values()items()メソッドは、範囲を絞ってキーや値を検索することもできます。上の【 サンプルコード 2-2-1 】でkeys()メソッドを利用していたコードを以下のように書き換えて、実行してみましょう。

追加・変更【21行目~23行目】
(実行結果)

書き換えたコードでは、「"a""c"」と範囲を絞ってキーの検索を行っているため、「orange」はヒットしていません。

ここまでで、データベースの基本を確認してきました。次は、文章内の特定の文字列の検索に使われたり、入力された情報に誤りがないかをチェックしたりするときに使われる「正規表現」と正規表現を利用した「マッチング」について見ていきましょう。

正規表現とマッチング

皆さんもインターネット上で提供されているサービスを利用するときに、会員になるために個人情報の登録を求められた経験があるのではないでしょうか。個人情報には、名前やメールアドレス、電話番号、住所などサービスを利用するために必要な様々な情報が含まれています。もし、利用者が誤った情報を登録してしまうと、サービスを利用するときに不都合が生じてしまいます。

【 誤った情報を登録することで起きる問題の例 】
  • 誤ったメールアドレスを登録したことで、重要なお知らせが届かない。
  • 誤った住所を登録したことで、購入した荷物が届かない。
  • 誤った電話番号を登録したことで、サービス提供者が利用者と連絡を取ることができない。

このようなトラブルを防ぐために、個人情報の登録時には、利用者へフォームに記入した内容の再確認を促したり、メールアドレスなど特に重要なものは、2回同じものを入力してもらったりしています。

ここで紹介する正規表現とマッチングもこのトラブルを防ぐためによく使われる技術で、あらかじめ設定したパターンに入力された内容がマッチするかどうかをチェックするときに役立ちます。

例えば、メールアドレスや電話番号は、限られた種類の数字や文字の組合せだったり、文字数が制限されていたりするなど、ある程度パターン化されています。

  • メールアドレス:XXX@XX.XX.XX
  • 携帯の電話番号:XXX-XXXX-XXXX

このパターンを定義した表現が「正規表現」で、正規表現で定義されたパターンに検索対象の文字列が当てはまるかどうかをチェックする処理が「マッチング」です。

まずは、正規表現から詳しく見ていきましょう。

3. 1 正規表現

正規表現では、通常の文字と「メタ文字」や「特殊シーケンス」と呼ばれる特殊な文字や記号を組み合わせて、特定の文字列のパターンを表します。

例えば、上で確認した携帯の電話番号を正規表現で表すと次のようになります。

"^\d\d\d-\d\d\d\d-\d\d\d\d$"

Pythonの正規表現では、パターンを文字列型のオブジェクトとして定義します。上のパターンにある最初の「^」と最後の「$」は、それぞれ文字列の先頭と末尾を表すメタ文字です。そして、複数個並んでいる「\d」は「0~9」の範囲にある数字を表す特殊シーケンスです。つまりこれで、「012-3456-7890」のように、(ハイフン)で区切られた数字が並ぶパターンを表しています。

MicroPythonでは、紹介した3つ以外にも次のメタ文字や特殊シーケンスを使って、パターンを表すことができます。数が多いため、いきなりすべてを覚えるのは大変ですので、練習としていくつかのパターンを定義しながら覚えていきましょう。

【 MicroPythonで扱えるメタ文字の一覧 】
メタ文字説明指定例マッチする文字列
.改行以外の任意の一文字にマッチします。a.cabc, acc, aac
^文字列の先頭にマッチします。改行がある場合は各行の先頭にマッチします。^abcabcdef
$行の末尾にマッチします。abc$defabc
*直前の正規表現を0回以上の繰り返したものにマッチします。ab*a, ab, abb, abbb
+直前の正規表現を1回以上の繰り返したものにマッチします。ab+ab, abb, abbb
?直前の正規表現を0回または1回繰り返したものにマッチします。ab?a, ab
[◎〇△]文字の集合を表します。[]内ののどれか1文字にマッチします。[abc]a, b, c
[◎-〇]-を2つの文字で挟んで指定すると、連続した文字の範囲の集合を表します。[a-e]a, b, c, d, e
[^◎〇△][の次に^を付けると、集合にない文字にマッチします。[^a-z]0, 1, 2, A, B, C
||を挟んでいずれかの正規表現にマッチする正規表現を作成します。ab|cdabd, acd
(...)正規表現をグループ化します。(a[bc]?)+a, ab, aba, abac
\エスケープ文字。バックスラッシュの後に続く文字は通常の文字として扱われます。\.\?\+.?+
【 MicroPythonで扱える特殊シーケンスの一覧 】
特殊シーケンス説明同じ意味の正規表現
\d数字にマッチします。[0-9]
\D数字以外にマッチします。[^0-9]
\s空白にマッチします。[\t-\r]
\S空白以外にマッチします。[^\t\n\r\f\v]
\w任意の英数字にマッチします。[a-zA-Z0-9_]
\W任意の英数字以外にマッチします。[^a-zA-Z0-9_]

■ 正規表現の練習

では、練習として次の例にマッチするパターンを正規表現で表してみましょう。

  • Python(決まった言葉)
"^Python$"
  • 「a」で始まり「e」で終わる5文字の英単語
"^a[a-z][a-z][a-z]e$"
  • 頭文字が「a」または「b」の3文字以上の英単語
"^[ab][a-z][a-z]+$"
  • 7桁の郵便番号(XXX-XXXXまたはXXXXXXXの形式)
"^\d\d\d-\d\d\d\d|\d\d\d\d\d\d\d$"
  • メールアドレス(XXXXXX@XXXX.XX.XX)※ この正規表現は、英数字と_(アンダースコア)-(ハイフン)のみを使用したメールアドレスを想定しています。
"^(\w|-)+@(\w|-)+(\.(\w|-)+)+$"

メールアドレスは少し難しいので解説すると、正規表現が3つのパートに分かれていて、それぞれ次のパターンにマッチするようになっています。

それでは、このメールアドレスの正規表現を使って、実際にマッチングを行うプログラムを作成してみましょう。

3. 2 マッチング

マッチングを行うときは、MicroPythonのureモジュールを使います。ureモジュールには以下の関数が用意されています。

【 ureモジュールの関数 】
関数名内容
compile(正規表現)正規表現をコンパイルして、regexオブジェクトを返します。
match(正規表現, 検索対象の文字列)正規表現をコンパイルして、対象の文字列とマッチするかどうかを照合します。マッチする場合はmacthオブジェクトを返し、マッチしない場合はNoneオブジェクトを返します。対象の文字列の先頭から照合を行うため、文字列の途中に正規表現とマッチする部分あった場合でもNoneオブジェクトが返される。
search(正規表現, 検索対象の文字列)正規表現をコンパイルして、対象の文字列とマッチするかどうかを照合します。マッチする場合はmacthオブジェクトを返し、マッチしない場合はNoneオブジェクトを返します。match関数とは違い、文字列の途中からでも正規表現にマッチする部分があれば、macthオブジェクトを返します。
※ コンパイル・・・プログラミング言語で書かれた文字列を、コンピュータが実行可能な形式に変換すること。
※ 英語で正規表現のことを、「Regular expression」といいます。

compile()関数で返されるregexオブジェクトもureモジュールのmatch()関数やsearch()関数と同じ名前と機能のメソッドを持っています。どちらを使用しても結果は変わりませんが、何度も繰り返しマッチングを行う場合は、正規表現をあらかじめコンパイルしてregexオブジェクトを作成しておく方が、無駄な処理が減るためプログラムの実行効率が良くなります。

また、match()関数とsearch()関数は、どちらも正規表現でマッチングを行う点は同じですが、以下の点に違いがあります。

【 match関数とsearch関数の実行結果の違い 】

■ 入力したメールアドレスが有効かどうかを確認するプログラム

以下のサンプルコードがureモジュールを使用して、ターミナルから入力された文字列がメールアドレスの正規表現とマッチングするかどうかをチェックするプログラムです。コードを書き写して実行し、ターミナルからmain()関数を呼び出して、正しくチェックできることを確認しましょう。

【 サンプルコード 3-2-1 】
(実行結果)

データベースを使った簡単な認証システムの制作

私たちが普段から利用しているSNSやネットショップなど、Web上で提供されているサービスの多くは、利用を始める前にアカウントの登録が求められます。そして、サービスを利用するときには、登録したアカウント情報によるユーザー認証を行った上で、情報の発信や取得、買い物などができるようになっています。

このように限定されたユーザー向けにサービスを提供するWebサイトのシステムでは、その多くが、ユーザーのアカウント情報の記録や管理にデータベースを利用しています。

Webサイト上でユーザーが入力したカウント情報は、そのWebサイトを運用しているサーバーに送信され、そのサーバーからデーターベースへ検索を要求します。そして、サーバーはデータベースから返された検索結果を受けて認証を行い、その結果をユーザーに知らせる仕組みになっています。

また、事前にユーザーが入力した情報の書式に誤りがないかどうかをチェックすることも大切です。特に新しくユーザーがアカウント情報を登録するときは、メールアドレスやパスワードなど大事な情報に誤りがあると、サービスを提供できなくなるため、正規表現とマッチングの機能を使用して書式に間違いがあれば、ユーザーに知らせます。

実際にサービスとして使える認証システムを構築するためには、他にもHTTPメソッドや暗号化通信、本格的なデータベース操作など数多くの専門知識が必要です。しかしながら、学習用のシンプルな認証システムなら、このレッスンで学習したデータベースと正規表現・マッチングを応用して制作することができます。

それでは、実際にプログラムを作成しながら認証システムの構築方法を確認していきましょう。

4. 1 シスステムに必要な機能の整理

これから作成する認証システムでは、「ユーザーのアカウント情報(メールアドレスとパスワード)の登録機能」と「システムへのログイン機能」の2つだけを提供します。まずは、これらの機能を実現するための処理の流れを見ていきましょう。

【 アカウント情報の登録に必要な処理 】
  1. ターミナルから新規のアカウント情報の入力を求める
  2. 入力されたアカウント情報の書式をチェックする
  3. データベースを開く
  4. データベースへアカウント情報を登録する
  5. データベースを閉じる
【 システムへのログインに必要な処理 】
  1. ターミナルから登録済みのアカウント情報の入力を求める
  2. 入力されたアカウント情報の書式をチェックする
  3. データベースを開く
  4. データベース上でアカウント情報を検索して照合する
  5. データベースを閉じる
  6. 認証結果を表示する

整理してみると、2つの機能には共通する処理がいくつかあります。そこで、共通する処理は別で関数にまとめることにして、全体で以下の関数を作成します。

【 作成する関数の一覧 】

|関数名|処理内容|
|:—|:—|
|open_db()関数|データベースを開く処理|
|close_db()関数|データベースを閉じる処理|
|get_account_info()関数|ターミナルからアカウント情報の入力を求める処理と入力された情報の書式をチェックする処理|
|register()関数|アカウント情報の登録を行う処理|
|login()関数|システムへのログインを行う処理|

それでは、この表の上から順番に関数を作成していきましょう。

4. 2 認証システムのプログラムの作成

2つの機能で共通して必要な処理から用意していきます。

■ open_db関数の作成

データベースの開き方は、前半のレッスンで説明した通りです。データベースやデータベースに使用するファイルのオブジェクトはグローバル変数として管理するようにしましょう。

■ close_db関数の作成

「データベース ⇒ ファイル」の順で閉じる必要があったことに注意して、下記のようにコードを書きましょう。

追加【15行目~19行目】

■ get_account_info関数の作成

次は、ターミナルからメールアドレスとパスワードの入力を求め、入力された文字列がそれぞれの書式に沿っているかどうかをチェックする関数を作成します。

メールアドレスの正規表現はレッスンの前半で確認したものをそのまま利用します。パスワードは、ここでは6文字~8文字の英数字としておきましょう。それぞれ正規表現を定義して、regexオブジェクトを作成しましょう。

追加【2行目、6行目~9行目】

「メールアドレス ⇒ パスワード」の順に、ターミナルからの入力を受け取り、用意した正規表現とマッチするかどうかを確認します。もしマッチしなかった場合は、再度入力を促します。問題がなければ、受け取ったメールアドレスとパスワードをタプルにまとめて、この関数の呼び出し元へ返します。

追加【27行目~42行目】

■ register関数の作成

入力されたアカウント情報をデータベースへ登録する関数を作成します。上で作成した3つの関数を利用して、次の手順で処理を進めましょう。

追加【45行目~53行目】

作成したregister()関数で、データベースへアカウント情報が登録できることを確認します。データベース上のアカウント情報が確認できるように、一覧を表示するshow_db()関数を追加して、テスト用のメールアドレス(test@sample.com)とパスワード(testtest)でプログラムを試しましょう。

追加【56行目~60行目】
(プログラムの実行例)

■ login関数の作成

最後に、入力されたアカウント情報をデータベース上で検索して、ログイン認証を行う関数を作成します。先に入力されたメールアドレスがデータベース上に存在していることを確認し、存在していた場合のみパスワードを照合するようにしましょう。

追加【63行目~78行目】

4. 3 プログラムの動作確認

これでプログラムの完成です。register()関数の動作確認時に登録しておいたアカウント情報がデータベース内に残っているので、これを使用してログインができるかどうかを確認しましょう。

もし、プログラムの実行時にエラーが出てしまい、うまく解決できない場合は、下のサンプルコードと見比べて、書いたコードに誤りがないかどうかを確かめましょう。

【 サンプルコード 4-3-1 】

確認方法:
1. show_bd() で登録情報を確認
2. login() でログインできるか確認

課題:データベースへ登録する情報の追加

【 サンプルコード 3-3-1 】では、データベースへ登録するアカウント情報として、キーにメールアドレスを、値にパスワードを設定していました。実際のサービスで使われているデータベースには、この他にもユーザーの名前や生年月日、電話番号など様々な情報が登録されています。

そこでこの課題では、ユーザーのニックネーム(nickname)をアカウント情報に追加して登録できるようにしてみましょう。

5. 1 複数の情報をひとつの値として記録する方法

一般的なデータベースシステムでは、キーに対して複数の値をひもづけすることができますが、bTreeモジュールのデータベースでは、キーにひとつの値しかひもづけすることができません。

そのため、次の工夫を行い、疑似的に複数の値を登録して利用できるようにします。

  1. 複数の値をタプルの形式で表記した1つの文字列「"('password', 'nickname')"」にまとめる。
  2. 1の文字列をバイト列にエンコードして、データベースに登録する。
  3. データベースから取得した値はバイト列からデコードして文字列に戻す。
  4. 3の文字列をeval()関数で評価して、タプル('password', 'nickname')を作成して利用する。

では、認証成功時に以下のメッセージが表示できるように、get_account_info()register()login()の3つの関数を変更していきましょう。

5. 2 プログラムの改造手順

3つの関数を、get_account_info()register()login()の順番に変更していきます。

■ get_account_info関数の変更

新たにニックネームを登録できるようにするため、プログラムの冒頭に、ニックネームの正規表現を定義します。ニックネームは特別な制限を設けずに、1文字以上の英数字としておきましょう。

追加【8行目、11行目】

ニックネームはアカウントの登録時には入力を求めますが、ログイン時には必要ないため、get_account_info()関数へ新たに引数_fromを追加して、呼び出し元の関数名("register"または"login")を渡してもらい、処理を分けるようにします。

login()関数から呼び出されたときは、メールアドレスとパスワードの入力を受け取った時点で情報を返し、register()関数から呼び出されたときは、続けてニックネームの入力を受け付けます。

変更・追加【29行目、44~53行目】

■ register関数の変更

パスワードとニックネームの2つの情報をタプル形式の文字列にまとめ、それをバイト列にエンコードして、データベースへ登録します。また、get_account_info()関数に呼び出し元の関数名を渡す点に注意しましょう。

変更・追加【57行目、60行目、62・63行目、66行目】

■ login関数の変更

データベースから取得した値をバイト列から文字列にデコーディングします。しして、その文字列をeval()関数で評価します。この文字列はタプル形式で書かれているため、評価されるとタプルが作成されます。

変更・追加【77行目、82行目、88行目~92行目】

5. 3 プログラムの動作確認

完成したプログラム例が以下になります。実行して、動作を確認してみましょう。

【 サンプルコード 5-3-1 】

確認方法:
1. register() でemail、PW、ニックネームを登録する(3つくらい登録してください)
2. show_db() で登録内容を確認
3.  login() でログインができ、ニックネームが表示されればOK!

おわりに

6. 1 このレッスンのまとめ

今回は、「データベース」と「正規表現とマッチング」について新たに学習しました。

データベースと正規表現はどちらもインターネットを利用したサービスを開発する上では欠かせない技術です。Pythonには様々な開発環境が用意されているため、ArtecRoboのようなロボットを制御することもできれば、ゲームやアプリケーションを開発したり、インターネット上のサーバーを開発したりすることだってできます。

もう既に私達の生活はインターネットなしでは成り立たなくなってきています。もし、将来的に自分で何か新しいサービスを開発したいという思うようになったとき、このレッスンで学んだことがきっと役に立つでしょう。

6. 2 次のレッスンについて

次のレッスンでは、ネットワーク上での情報のやり取りを安全に行うための技術について学習していきます。

TOP