Python3 – 正規表現 – モジュール関数

概要

reモジュールの関数は、パターンと文字列を直接指定してマッチングなどの操作を行う。

引数の中のflagsについては、reモジュールで定義された定数を指定する。複数のflagsを指定する場合は、ビットごとのOR('|'演算子)を使って組み合わせる。

パターンは実行に先立ってコンパイルされるので、同じパターンを複数回用いる場合には、re.compile()関数でパターンをコンパイルし、コンパイルされたRegexObjectオブジェクトのメソッドを用いる方がよい。

正規表現のコンパイル

compile()

re.compile(pattern, flags=0)

正規表現パターンを正規表現オブジェクトにコンパイルする。正規表現オブジェクトのメソッド群で、以下のモジュール関数と同等の操作を行うことができる。

以下のモジュール関数を使う場合はflagsを関数ごとに指定するが、正規表現オブジェクトを使う場合は、compile()関数の引数でflagsを指定する。

パターンの検索

re.search(pattern, string, flags=0)

stringの任意の位置で、最初にpatternにマッチした時にMatchObjectのオブジェクトを返す。マッチしなければNoneを返す。

match()

re.match(pattern, string, flags=0)

stringの先頭でpatternにマッチすればMatchObjectのオブジェクトを返す。マッチしなければNoneを返す。stringの途中ではマッチしない。

fullmatch()

re.fullmatch(pattern, string, flags=0)

patternstring全体にマッチしたときだけMatcObjectのオブジェクトを返し、それ以外はNoneを返す。

findall()

re.findall(pattern, string, flags=0)

string中でpatternにマッチする全ての部分文字列を要素とするリストを返す。マッチする部分がなければ空のリスト([])を返す。先頭からマッチした部分を取り除きながらサーチしていく。

finditer()

re.finditer(pattern, string, flags=0)

string中でpatternにマッチした結果のMatchObjectオブジェクトのイテレータを返す。マッチする部分がなければ空のイテレータを返す。先頭からマッチした部分を取り除きながらサーチしていく。

分割

split()

re.split(pattern, string, maxsplit=0, flags=0)

stringをすべてのpatternにマッチする部分で分割し、それらを要素とするリストを返す。patternにマッチする部分は除かれる。マッチする部分がなければ、stringを1つの要素とするリストが返される。

maxsplitに1以上の数nを指定すると、先頭から最大n個の分割が発生し、残りはリストの最後の要素となる。

patternが先頭の部分に一致する場合は、リストの最初は空文字列から始まり、最後の部分に一致する場合はリストの最終要素が空文字列になる。

空文字列('')では分割されない。patternとして空文字1文字を指定するとエラー。

置換

sub()

re.sub(pattern, repl, string, count=0, flags=0)

string中でpatternにマッチする部分文字列をreplの文字列で置換する。countで正の整数値を指定すると、先頭から最大その回数だけ置換を行う。

空文字列とのマッチは、前のマッチの直後以外に置換される。

replには文字列を返す関数を指定できる。

subn()

re.subn(pattern, repl, string, count=0, fkags=0)

sub()と同じ操作を行うが、タプルで(置換後の文字列, 置換数)を返す。

 

Python3 – エスケープシーケンス

Pythonのエスケープシーケンス一覧

\\ バックスラッシュ('\')
\' シングルクォーテーション("'")
\" ダブルクォーテーション('"')
\a ベル
\b バックスペース
\f 改ページ
\r キャリッジリターン
\n 改行
\t 水平タブ
\v 垂直タブ
¥N{name} Unicodeデータベース中で名前nameを持つ文字
\uxxxx 16ビットの16進数xxxxを持つUnicode文字
\Uxxxxxxxx 32ビットの16進数xxxxxxxxを持つUnicode文字
\ooo 8進数oooを持つASCII文字
\xhh 16進数hhを持つASCII文字
\0 NULL
\+改行 改行の直前(文末)が\の場合は次の行が継続

 

Python3 – 正規表現

Pythonでの正規表現の扱い

Pythonでは、パターン・マッチングを以下のいずれかの方法で行い、その結果をMatchObjectオブジェクトとして返す。

  • パターンと文字列を指定して、reモジュールで定義された関数を実行する
  • パターン文字列を正規表現オブジェクトとしてコンパイルしておき、そのメソッドで文字列を指定して実行する

reモジュール関数を使う方法

モジュール関数で実行する例は次の通り。実行結果はMatchObjectのオブジェクトとして返されるが、その文字列表現のspanのところに4文字目~8-1文字目でヒットしたことが記録されている。

パターン文字列を正規表現オブジェクトにコンパイルする方法

正規表現オブジェクトにコンパイルして検索する方法は次の通り。検索結果は先と同じMatchObjectオブジェクト。

Python3 – 文字列とコレクション

リストと文字列

list()関数で文字列をリストに分解し、join()メソッドで区切り文字を''(空文字)で指定してリストを文字列に結合。

数値要素のリストを文字列要素のリストにするには、map()関数でstr()関数を適用する。

ただしmap()関数はいろいろと注意が必要

setと文字列

set()で文字列の重複した文字を集約したセットが得られる。文字列の順番は固定されず、実行ごとにも異なる。

 

 

Python3 – 文字列

内容の取得

文字列長

文字列長はlen()関数で得られる。

一文字取得

配列のように文字列中の位置を指定して、一文字取得する。開始位置は0。-1で最後の文字を指定でき、そこから順に-2、-3と先頭に向けて遡る。

イテレータ、リストによる連続取得

文字列はイテレータ。

list()関数で一文字ずつのリストが得られる。

文字の出現回数

count(sub[, start[, end]])は元の文字列の中の部分文字列subの出現回数を返す。startendはオプションで指定可能。

部分文字列の取得(スライス)

以下の記法で部分文字列を取り出せる。ただし取り出される最後の文字列は終了位置-1番目の文字。

具体例は以下の通り。

正のステップ値の場合は検索方向が左→右なので以下の関係でなければならない。

負のステップ値を使うと、最後尾から先頭へ向かって文字を取り出す。特にステップ値を-1とすると、1文字ずつ前へ向かって取り出すので、文字列の反転に便利。ただし開始位置と終了位置に注意が必要。

負のステップ値の場合には検索方向が右→左になるので、以下の関係でなければならない。

n文字目からm文字取得したい時。

スライスとfinde/rfindでは開始位置・終了位置の指定の考え方が違ってくる

特定文字での分割

split()メソッドは、指定した文字列で元の文字列を分解し、リストで返す。

partition()メソッドは指定文字の最初の出現位置で文字列を分割し、タプルで結果を返す。

内容の判定

stringオブジェクトの以下のメソッドは、それぞれの条件に合致した場合にTrueを返す。空文字列に対しては全てFalseを返す。

isalpha()
全ての文字がアルファベットの場合にTrue。
isdigit()
全ての文字が数字の場合にTrue。
isalnum()
全ての文字がアルファベットか数字の場合にTrue。
islower()
文字列中のアルファベットが全て小文字の場合にTrue。アルファベット以外の数字や記号が含まれていても判定対象外で無視されるが、アルファベットがまったく含まれていないとFalse。
isupper()
文字列中のアルファベットが全て大文字の場合にTrue。アルファベット以外の数字や記号が含まれていても判定対象外。アルファベット以外の数字や記号が含まれていても判定対象外で無視されるが、アルファベットがまったく含まれていないとFalse。
isspace()
全ての文字がスペースの場合にTrue。
istitle()
文字列中の区切られた部分文字列がタイトルケースの場合にTrue。

検索

find/rfind

find()は指定した文字列を検索し、そのインデックスを返す。存在しない場合は-1が返される。検索範囲の指定はスライスと同じで[開始位置, 終了位置)。

index()も同じ使い方ができるが、存在しない場合にValueError: substring not foundが返される。

rfind()は文字列の後方から検索する。開始位置と終了位置の意味はスライスと同じだが、開始位置だけ指定すると、そこから文字列の後方が検索範囲となってしまう点に注意。

find/rfindとスライスでの範囲指定が違ってくる点に注意

内容の変更

連結

+演算子で文字列を連結できる。

join()メソッドで、元の文字列オブジェクトを区切り文字にして、引数リストの各文字列要素を連結できる。

数値と文字列を連結する場合、Pythonでは自動変換されない。str()関数で明示的に文字列に変換する必要がある。

join()の方が推奨されているらしい。

繰り返し

*演算子で同じ文字列を複数回繰り返した文字列を得られる。

置き換え

replace(old, new[, count)メソッドは、部分文字列oldnewで置き換える。countが指定されると、先頭からその個数分だけ置き換える。

置き換え前後の部分文字列の長さが違ってもよい。置き換え後に空文字列を指定すると、文字列の削除に使える。

replace()のほか、sub()関数(reパッケージ)、str.translate()関数も使える。

なお、部分文字列をスライスで取得した書式を使って文字列を代入することはできず、エラーとなる。

書式・整形

センタリング・左寄せ・右寄せ

center()ljust()rjust()の各メソッドで、元の文字列を指定した幅の中でセンタリング・左寄せ・右寄せできる。デフォルトでは空いた場所がスペース(‘ ‘)で埋められるが、その文字を指定することが可能。

余白などの切り落とし

strip()lstrip()rstrip()各メソッドは、文字列の両端の指定文字を削除する。デフォルトでは空白が削除されるが、複数の文字を切り落とす対象として指定できる。

ただし、切り落とされるのは最も外側の文字列群だけであることに注意。

 

Python3 – map関数

基本

コレクションやイテレーターの戻り値に何らかの処理を施したい場合、たとえば内包表記を使うことが課が得られる。

同様の効果があるmap()関数は、コレクションやイテレーターの戻り値に関数を作用させた結果を返す。第1引数には適用したい関数、第2引数には適用対象となるコレクション等を指定する。

以下の例は、冒頭に示した内包表記と同じくリストの要素を2倍する。使い方としては、与えられた引数を2倍する関数を準備し、関数を第1引数に、対象となるリストを第2引数に与える。

なおmapオブジェクトはイテレーターとして振舞うので、結果をリスト表示するためにはlist()関数を通す必要がある。

再利用不可

map()関数で得られたオブジェクトを変数にセットして使うこともできる。ただしmapオブジェクトはイテレーターなので1度利用したものをそのまま再度利用することはできない

イテレーター等の指定

また第2引数にはコレクションではなく、rangeオブジェクトやイテレーターを渡すこともできる。

ラムダ式が可能

map()関数の第1引数には、関数のほかにラムダ式を指定することも可能。

辞書によるマッピング

コードが格納されているコレクションから、辞書とmap()関数でコードに対応する値を得ることができる。

逆に値が格納されているコレクションから、辞書を使ってコードのコレクションを得ることもできる。この場合、辞書のキーとコードを入れ替えて適用する。

 

Python3 – スライスとfindでの位置指定の違い

文字列のスライスとfind/rfindで検索範囲の指定が違っていて混乱したので整理する。

文字列のスライスでは、次のように指定する。

このとき、stepが正なら前方から後方へ、負なら後方から前方へ向かって文字列が取り出されるが、そのときのstartとendの指定は、stepが正か負かで次のように違ってくる。

つまりstepが正の時は、前方から後方へ文字列が取り出されるのでstart<stop、stepが負の時は逆にstart>stopでなければならない。

一方、find/rfindについてはスライスと違ってくる。

(string, start, stop)の指定で、検索範囲の指定は常にstart < stopでなければならない。

 

 

 

 

Python3 – モジュールとパッケージ

概要

モジュールは共通して利用したい実行ファイルをインポートできるように配置したもの。パッケージはパッケージやモジュールをひとまとめにするもの。

  • ローカルの実行環境では、ディレクトリがパッケージになる
    • パッケージ内の__init__.pyファイルは特別なモジュールで、パッケージをインポートするだけでモジュール内の関数やクラスが使える
    • パッケージ下にモジュールファイルを置くと、[パッケージ名].[モジュール名]でモジュール内の関数やクラスが使える
  • パッケージのセットアップとインストール→今後

カレントディレクトリ下での使用

モジュールは普通のPython実行ファイルで作成。

たとえばカレントディレクトリに以下のようなファイルをtestmodule.pyとして置く。このモジュールには関数が1つとクラスが1つ定義されている。

そして同じカレントディレクトリにあるtest.pyを以下のように書く。

この時点でファイル構成は以下のようになっている。

ここでtest.pyを実行すると次のように表示される。無事インポートできて、モジュールで定義した関数、クラスとも使える。

実行後には新しいディレクトリが追加されて、以下のようになった。

カレントディレクトリ下でのモジュール管理のまとめ

  • 関数・クラスなどを含むモジュールをPythonファイルとして作成し、カレントファイルに置く
  • 利用側で、モジュール名(モジュールのファイル名から拡張子”.py”を除いた名前)を指定してインポート
  • [モジュール名].[関数名/クラス名]として利用

サブディレクトリ管理でのエラー

モジュールをカレントディレクトリではなくサブディレクトリに置いて管理したいので、以下のように置いてみる。

そして以下のように呼び出そうとしたが残念ながらエラー。

ただしこの実行後、サブディレクトリの下に新しいディレクトリがつくられていた。

これはimport文を読んだPythonがsubdirというパッケージを探しに行ったが見つからなかったためで、モジュールを置いたディレクトリをパッケージとしてPythonに認識させる必要がある。

パッケージ

パッケージ直下のモジュール

ディレクトリをパッケージとしてPythonに認識させるためには__init__.pyというファイルが必要になる。

たとえば以下のようなディレクトリ・ファイル構造として

__init__.pyファイル内容を前のtestmodule.pyと同じとする。

カレントディレクトリ下のtest.pyでは前は[モジュール名].[関数/クラス]としていたが、今回は以下のようにパッケージから呼び出すように変更。__init__.pyファイルに書いたモジュールは、パッケージ直下から呼び出される。

出力は以下の通りとなって成功。

またここでもmypackageディレクトリの下に新しいディレクトリが作成された。

パッケージ内のモジュール

testpackageディレクトリ下にothermodule.pyファイルを置く。

test.pyを以下のようにして実行。

以下のように実行される。

一度実行されると、pycacheディレクトリに新たなファイルが追加される。

 

 

待ち行列(M/M/1)の再現 – 時刻制御

考え方

時刻制御(time driven)による待ち行列の計算の考え方は次の通り。

  1. 時刻t0から始めて、以下時間間隔Δtずつ増やしながら計算を進めていく
  2. ある時刻で一様乱数の値がλΔtより小さければ到着が発生
    • 新たにトランザクションを生成し、到着時刻を記録し、システムに投入する
  3. ある時刻で到着が発生せず、システムにトランザクションがある場合、一様乱数の値がμΔtより小さければサービスが終了
    • サービス中のトランザクションのサービスを終了させ、終了時刻を記録する
    • このとき待ち行列にトランザクションがあれば、先頭のトランザクションのサービスを開始し、サービス開始時刻を記録する
  4. 予め設定していた時刻(またはトランザクション数)に達したら終了

queue-mm1-time-driven-fig1

たとえば上の図でトランザクション②に注目すると、

  1. 時刻t4でトランザクションが発生
  2. t7でトランザクション①のサービスが終了し、②のサービスが開始
  3. t8でサービス終了

イベント制御のケースではRを用いたが、今回はPythonを使う。なおM/M/1待ち行列の解析的アプローチはこちら

待ち行列システムのクラスをつくり、システムの外からはトランザクションの到着、サービス終了の操作を行うだけで、内部的に処理が進むようにする。

クラス構成

待ち行列システムを表すQueueSystemクラス、システムのサービス窓口を表すServiceクラス、システム中の1つのトランザクションを表すTransactionクラスから構成される。システム中の待ち行列はインスタンス変数のリストとして持つ。

M/M/1型の行列システムとして、QueueSystemクラスは1つのServiceオブジェクトと1つの待ち行列リストをメンバーに持つ。

トランザクションの到着の指示が出るとTransactionオブジェクトが生成され、システムに登録される。

その一方で実行中のサービス終了の指示が出るとサービスを受けているトランザクションが解放される。

各Transactionオブジェクトに対して、システムの到着、サービスの開始、サービスの終了の時刻が自動的に登録される。

Transactionクラス

  • 識別子としての整数値のindexのほか、到着・サービス開始・サービス終了の時刻、到着時のシステム内トランザクション数と待ち行列長をメンバーとして持つ
    • これらのメンバーに対しては直接アクセスして参照・代入する
  • このクラスのオブジェクトの文字列表現を__str__メソッドで実装している

Serviceクラス

  • privateなメンバ_transactionを持ち、これはサービス中のトランザクションを保持する
    • サービス中のトランザクションがない場合はNone
  • サービス窓口が空いているか(vacant)、サービス中か(occupied)を判定するメソッドを持つ
  • サービス中のトランサクションへの参照を返すtransaction_in_service()メソッドを持つ
  • サービス開始時の処理を行うstart()メソッドを持つ
    • サービスを開始するTransactionオブジェクトとサービス開始時刻を指定する
  • サービス終了時の処理を行うend()メソッドを持つ
    • サービス終了時刻を指定し、サービス中のTransactionオブジェクトにその時刻を設定し、オブジェクトへの参照を返す

QueueSystemクラス

メンバとなるインスタンス変数は4つ

  • 待ち行列リスト_queue
  • サービス窓口オブジェクト_service
  • 到着済みのトランザクションを登録する_arrived_transaction_list
  • サービス完了済みのトランザクションを登録する_departed_transaction_list

getter系のメソッドは

  • システム中にあるトランザクション数を返すtransaction_number_in_system()
  • 待ち行列長を返すqueue_length()
  • システム中にあるトランザクションへの参照を返すtransaction_in_service()
  • 待ち行列中にあるトランザクションへの参照を返すtransaction_in_queue()
  • 到着済みのトランザクション数を返すarrived_transaction_number()
  • 完了済みのトランザクション数を返すdeparted_transaction_number()
  • 到着済みのトランザクションのn番目を返すarrived_transaction()
  • 完了済みのトランザクションのn番目を返すdeparted_transaction()

システムを操作するメソッドは

  • システムに到着したトランザクションを登録するarrive()
  • サービスが終了したトランザクションをシステムから取り出すdepart()

システムの状態を表示するメソッドとして以下の2つを実装している。

  • システムの現在の状態を表示するdisplay_current_status()
  • サービスを完了したトランザクション群の各パラメータを表示するdisplay_transaction_summary()

動作テスト

上記のクラス群を使って以下を実行。規則正しく刻まれた時刻に4つのトランザクションが到着し、その後規則正しくサービスが終了していく様子と、その結果を表示させている。

実行結果は以下の通りで、意図したようにトランザクションの到着に従って登録され、終了指示によって待ち行列・サービス状況が変化している。

時刻制御の実装

考え方

概略の流れは以下の通り。

  • 計算に必要なパラメータを設定する
    • 到着率 λ
    • サービス率 μ
    • 計算時間範囲 t_max
    • 計算時間間隔 dt
  • 時刻tが0からt_maxになるまでdtずつ増やしながら以下を実行(以下、randは[0, 1)の一様乱数値)
    • λdt < randならトランザクションが一つ到着して次の時間ステップへ
    • サービス中のトランザクションがあって、μdt < randならトランザクションが一つ終了して次の時間ステップへ
  • トランザクションのサマリーを表示

テスト実行

実行結果は以下の通り。

実行結果

到着時間間隔の分布の確認

およそ1000個のデータを発生させるため、観測時間を10000秒にして計算し、到着したトランザクションの到着時間間隔を確認した。確認部分のコードの未以下に示す。

実行結果は以下の通りで、指数分布の理論値とよく合っている。

matplotlibのhist関数はnormed=Trueとした場合に期待した動作をしないことがあるが、このケースではうまくいっている。

queue-mm1-time-driven-arrival-interval

平均トランザクション数・平均待ち行列長など

10000秒の観測時間で、システム内の平均トランザクション数、平均待ち行列長と、それらに対する平均応答時間、平均待ち時間について計算し、λ、μから計算される理論値と比較した。

計算結果は以下の通りで、かなり整合している。

各実行結果の傾向

およそ1000個のデータに相当する10000時間単位まで、時間ステップ0.1で計算を行い、理論値との違いを確認した。

ρ = 0.5のケース

λ = 1/10、μ = 1/5、ρ = 0.5のケース。平均トランザクション数と平均待ち行列長の理論値は、L = 1、Lq = 0.5で、これらに対する待ち時間はT = 10、Tw = 5。

トランザクション数に関する計算結果は理論値とかなりよく合っている。

L T 1/λ
1 1.19667319 11.32504892 9.46377759
2 1.14047151 10.43113949 9.14633938
3 1.01360544 9.81409135 9.68235860
4 1.10038241 10.86032505 9.86959165
5 1.21322160 11.32597765 9.33545664
6 0.91197544 9.24390993 10.13613912
7 0.92921236 9.52532403 10.25096570
8 0.92452830 9.96117180 10.77432870
9 0.87450199 9.12868526 10.43872440
10 0.93159923 9.19017341 9.86494311
AVG 1.02361714 10.08058469 9.89626249
STD 0.12142639 0.81382679 0.48579815

待ち行列長は若干ばらつきが大きくなっている。

Lq Tq 1/λ
1 0.67906067 6.27358121 9.23861664
2 0.63555992 5.54204322 8.71993819
3 0.50728863 4.87269193 9.60536397
4 0.57456979 5.73049713 9.97354408
5 0.63314711 6.07476723 9.59455888
6 0.42169908 4.29559877 10.18640773
7 0.46660020 4.73389831 10.14551282
8 0.44985104 5.01837140 11.15562921
9 0.38844622 4.27151394 10.99641011
10 0.44797688 4.41705202 9.85999996
AVG 0.52041995 5.12300151 9.94759816
STD 0.09722322 0.70052360 0.70110321

ρ = 0.9のケース

λ = 1/10、μ = 1/9、ρ = 0.9のケース。平均トランザクション数と平均待ち行列長の理論値は、L = 9、Lq = 8.1で、これらに対する待ち時間はT = 90、Tw = 81。

トランザクション数に関する計算結果は、理論値の周りに大きくばらついている。

L T 1/λ
1 7.59815951 76.92402863 10.12403445
2 7.72051010 86.82890542 11.24652443
3 7.96595331 80.47908560 10.10288191
4 5.22616633 54.55070994 10.43799728
5 5.11700000 52.88450000 10.33505961
6 8.65760322 89.70845921 10.36181226
7 6.19872476 65.22922423 10.52300703
8 10.88111888 110.54815185 10.15963092
9 8.41194645 86.59763131 10.29460088
10 18.80710660 193.82274112 10.30582456
AVG 8.65842892 89.75734373 10.38913733
STD 3.75303129 38.38222105 0.31302798

待ち行列長の計算結果もかなりばらついている。

Lq Tq 1/λ
1 6.71165644 67.97975460 10.12861061
2 6.82678002 77.21158342 11.31010274
3 7.02334630 71.38190661 10.16351801
4 4.35902637 45.64036511 10.47031177
5 4.22800000 44.04650000 10.41780984
6 7.72205438 80.19728097 10.38548513
7 5.31880978 56.14229543 10.55542457
8 9.95704296 101.38201798 10.18194040
9 7.49742533 77.17693100 10.29379122
10 17.87005076 184.15197970 10.30506193
AVG 7.75141923 80.53106148 10.42120562
STD 3.73662161 38.18242499 0.32416633

ρが高くなる(混雑してくる)と計算結果と理論値のかい離が大きくなるのは、(言語が異なるとはいえ)同じ傾向。時刻制御では計算時間間隔を0.1でも試してみたが、あまり傾向は変わらない。

 

 

Python3 – ジェネレーターの実装

関数とyield文によるジェネレーター

イテレータ__next__メソッドでコレクションの要素を取り出すのに有用なのに対して、ジェネレーターはyield文で任意の結果を順次返していく。関数内にyield文を書くと、その関数はジェネレーターになる。

ジェネレーターは以下のように構成する。

  • ジェネレーターは関数として定義され、その関数への参照として取得する
  • forブロックのループのたびにジェネレーターの中のyieldが順次1つずつ呼び出される
  • 呼び出せるyield文がなくなるか、forブロックで強制的に抜け出したときループが終了

先の例ではjoin文が4回だけ呼び出されるので、それが呼べなくなったときにループが終了する。これに対して次の例では、ジェネレーターは無限に結果を生成し続け、forブロックで条件に合致したときにループを終了させている。

ジェネレーター式

内包表現と同じ表現を()内に書くと、要素を一つずつ生成するジェネレーターオブジェクトを返す。