【学問のすすめ】中世ヨーロッパ写字生のように黙って静かに手を動かすブログ

これまで習ったことや学んだことを記事にまとめています。ITネタとChromebookネタが多いです。

【Python】大文字小文字を無視して指定された文字列の含まれる行だけを出力するコードを書いてみた(ソースコードあり)

f:id:pesia_one:20180912203816j:plain:w400

Python3で他のファイルから読み込んだ行に特定の文字列が含まれていた場合だけ出力する、さらに特定の文字列を強調するコードを書いてみました。
開始時点で下記のコードが与えられており、2行目の「# ここになにがしかのコードを入力する」をどうするかを考えます。

f = open('small.txt', 'rU')
pat = "but"
# ここになにがしかのコードを入力する

f.close()

 

はじめに

コードの要件

今回作成するコードの要件は下記の通りです。

  • 要件1:ファイルを読み込み指定された文字列が存在する行だけを出力する
  • 要件2:大文字/小文字を無視する
  • 要件3:「要件1」とは別に指定された文字列を「*** 」で強調し出力する
  • 要件4:文字列は同じディレクトリにあるファイル「small.txt」から取得する

入力値と出力値

入力値はファイル「small.txt」によります。

We are not what we should be
We are not what we need to be
But at least we are not what we used to be
---Football Coach

出力を予定している値は下記の通りです。

But at least we are not what we used to be
***But*** at least we are not what we used to be

 

コード書いてみた

正解のコード

正解のコードは下記です。(Python3.2.5で動作を確認)

#!/usr/bin/env python3
# -*- coding; utf-8 -*-

f = open('small.txt','rU') 
pat = "but"  

import re

f1 = list(f)
f2 = re.compile(pat, re.IGNORECASE)

for f3 in f1:
    if re.search(f2, f3) != None:
        print(f3)        

        m1 = re.match(f2,f3)
        f4 = re.sub(f2, "***" + str(f3[m1.start():m1.end()]) + "***", f3)
        print(f4)    

f.close()

下記URLのGitHubリポジトリにも同じコードを掲載しています。

https://github.com/a1852rw/aiit_001_system_programing/blob/master/lesson_003/009_grep_001.py

 

解説

処理について説明

このコードに記述されている処理について説明します。

#!/usr/bin/env python3
# -*- coding; utf-8 -*-

いつも通りのshebang(シバン)と文字コードの指定です。
このコードがPython3で動作すること、文字の出力はUTF-8で行われることを示しています。(ここで文字コード指定しないと日本語が文字化けします)

f = open('small.txt','rU')    
pat = "but"  

変数「f」ファイル「small.txt」の文字列を代入します。 「rU」は関数「open」のオプションで指定すると「\n」「\r\n」「\r」の三種類すべてを改行文字と認識します。
次の行では、文字列「but」を変数「put」に代入しています。とても厄介なことにこの状態ですと「but」の大文字小文字が認識されます。
この処理で「要件4:文字列は同じディレクトリにあるファイル「small.txt」から取得する」がクリアされます

import re

正規表現を行うモジュール「re」をインポートします。これによりコード内で各種の正規表現を使った操作ができるようになります

f1 = list(f)
f2 = re.compile(pat, re.IGNORECASE)

変数「f1」に変数「f」の要素(ファイルの各行をリスト形式で読み込んだデータ)を代入します。
さらに、変数「f2」には「re.compile()」を使い変数「pat」を正規表現オブジェクトにコンパイルしています。この際「.IGNORERCASE」を使い大文字小文字を無視する設定にしています。(ここが一番苦労しました)
この処理で「要件2:大文字/小文字を無視する」がクリアされます

for f3 in f1:
    if re.search(f2, f3) != None:
        print(f3)        

for文を使って変数「f1」にリスト形式で格納されている要素を1つずつ変数「f3」に代入します。これにより対象ファイルの各行が順番に代入されます。
「re.search()」により「f3」の中に正規表現オブジェクト「f2」が存在するかどうかを判定します。
「!= None:」により存在する場合のみ次の処理「print(f3)」が行われます。
この処理で「要件1:ファイルを読み込み指定された文字列が存在する行だけを出力する」がクリアされます。

       m1 = re.match(f2,f3)
        f4 = re.sub(f2, "***" + str(f3[m1.start():m1.end()]) + "***", f3)
        print(f4)    

「re.match(f2,f3)」で「f3」の中のどこに正規表現オブジェクト「f2」が存在するかを検出します。
「f3[m1.start():m1.end()]」により、「f3」の中の正規表現オブジェクト「f2」に該当する文字列を出力しデータ型「str()」により文字列型に置き換えます。
(このようにしないと大文字小文字の区別なく処理することができません) さらに「re.sub()」により対象の文字列を「 文字列 」の形式に置き換えます。
この処理で「要件3:「要件1」とは別に指定された文字列を「*** 」で強調し出力する」がクリアされます。

今回は「print()」を二か所に記述しているため、要件1の文字列と「要件3」の文字列が一緒に出力されます。

 

余談

Python3で「大文字小文字を区別しないで」と指定があった場合「f1.upper()」「f1.lower()」などを使うのが定石のようです。
これを使うと「すべて大文字に変換」「すべて小文字に変換」をすることができるため、「f1.upper() == pat.upper()」などのように大文字小文字を無視して比較することができます。
しかし今回の設問では「特定の文字のあるところだけ協調表示せよ」という要件があり、このやり方をすると条件分岐その他でコードが長くそしてわかりにくくなります
なのでどうしても「f1.upper()」「f1.lower()」などを使いたくありませんでした

結果としてかなり読みやすいコードになったと思います

 

終わりに

定石を無視した書き方を探したためかなり時間がかかりました
python 大文字小文字を無視」「python grep.py」などで検索しましたがサンプルとなるようなコードはなく、ほぼ1から作ったためです。
13行目の「if re.search(f2, f3) != None:」ここを「if re.search(f2, f3) == True:」こうするとエラーになるのはいまだに謎です。

また、今回はPythonドキュメントの「re」モジュールのページが大いに参考になりました。
このページ日本語と英語が混じっているのが困りものです。

6.2. re — 正規表現操作 — Python 3.6.5 ドキュメント