2017年10月11日水曜日

Python正規表現メモ

正規表現チートシートを作りたい。

よく迷うこと
- 後方参照
- 先読み/後読み・肯定/否定



後方参照

patternで () をつけたものは、その場で使える。
()の順番に\1, \2...として使える。
\1はエスケープされてしまうので、「 r"\1"」のようにrawにする。

re.sub("(\d{8})", r"\1-", "1234567890") 

8つ数字を見つけたら、末尾に-を入れて置き換える。
'12345678-90'


否定肯定先読み後読み

・マッチさせる場所に(?)
・否定か肯定化で!か=
・?< 、 ? は前後で決める

Look Behind
3の後ろに4があるならマッチ
re.sub("3(?=4)", "x", "123456123")
'12x456123'

3の後ろに4がないならマッチ
re.sub("3(?!4)", "x", "123456123")
'12345612x'

Look Ahead
3の前に2があるならマッチ
re.sub("(?<=2)3", "x", "12345613")
'12x45613'

3の前に2がないならマッチ
re.sub("(?<!2)3", "x", "12345613")
'1234561x'


間違いの例 -- 前に持ってきているので全部にマッチしている。
re.sub("(?!2)3", "x", "12345613")
'12x4561x'


---

Python正規表現で調べたメモ。

全てのスニペットでreモジュールをインポートして使います。
import re

urlのような文字列にマッチ

content = 'string' # チェックしたいもの
re.search('(https?|ftp)(:*\/*[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#]+)', content)
文字列の冒頭にhttp(s)またはftpがあり、:...でアドレスに使える文字が連続して含まれているものを探している

回数でマッチ

"テストです \n\n\n\n"のように末尾に\n改行が3つ以上あるとき、2つにしたい
"テストです \n \n \n \n"のように スペースがある場合もある。

やり方

 正規表現を使ってマッチさせてreplaceする。
import re
content1="テストです \n\n\n\n"
content2="テストです \n \n \n \n"
content = content1 # or content2
if re.search('(\n\s*){3,}',content):
content = content.replace(re.search('(\n\s*){3,}',content).group(), '\n\n')
content
 re.search('(\n\s*){3,}',content)は contentを対象として
 \nと0回以上(*)の\s(空白)とマッチする、ということ
{3,} は3回以上繰り返しているものとマッチする、ということ

\n\s* は \n(\s)* とかいても同じになる

.group() でマッチしたその部分が出力される。
'テストです \n\n'

その2

やりたいこと

ファイル名を作って保存するとき、禁止されている文字が入ることがあるので、
それを置き換える。

やり方

ファイル名として禁止されている文字をre.subで置き換える。
参考というか丸写し。
 http://iori084.blog.fc2.com/blog-entry-16.html

禁止文字は \ / : ? " > < | ということで。 ピリオドも加えてマッチさせます。
content = 'put:some"char?which\are>forbidden as>filename'
re.sub(r'[\\|/|:|?|.|"|<|>|\|]', '_', content)
'put_some_char_which\x07re_forbidden as_filename' 

その3

'【これを消したい】残すところ【これを消したい】'
という2chスレッドタイトル風の文字列があって、re.subでカッコの部分を消したいと思いました。
# 見えるように'X'に置換
# 消すときは'X'を''とする
import re
re.sub('【.*】','X','【これを消したい】残すところ【これを消したい】')
だと最初と最後の【】にマッチしてしまって'X'
が出力されてしまいます。

こういうときは、最短一致の?を使います。非貪欲のマッチとも言います。
re.sub('【.*?】','X','【これを消したい】残すところ【これを消したい】')
'X残すところX'
となってOK。

その4 正規表現の肯定的後読み  Aが直前にあるBにマッチ

 (?<=A)B
でAが直前にあるBにマッチする

やりたいこと: 掲示板のレスで">>103" などにマッチ(>>のあとに数字3桁まで)

問題発生

>> のあとに 1-3個の数字をマッチさせようとして \d{1,3} とすると
s ='>>103 somestring'
*error*
re.match('(?<=>>\d{1,3})', s)
error: look-behind requires fixed-width pattern

というエラーが。

英語ではlook-behindらしいですが、先読みは文字数が決まっていないといけないようです。

面倒ですが、d{1},{2},{3}でループして解決しました。


■文末のスペースを消す

\s は 空白文字( スペース、改行、タブ...\f\n\r\t\v) にマッチする

        # 文末のスペースを消す
        content = re.sub('\s{1,}$','', content)

■大文字小文字を区別しないでマッチする

(?i)  というフラグをつける

re.search('(?i).jpg|.png', content)

これでマッチする。content = 'test.Png' とか 'test.JPG'でもOK。jupyterでWarningが出ていたが1回目だけだった。よく理解できなかった・・・。

re.I または re.IGNORECASE

というコマンドでもIGNORECASEフラグを立てられるようだが戻し方が解らない。


https://docs.python.org/3/library/re.html#module-contents

■同じ文字が出てきたらマッチ

re.search('(.)\\1', 'apple')

とやると (.) と丸括弧でくくった部分がグループとなって、\\1 で後方参照でき、 back referenceになってマッチする。
<_sre.SRE_Match object; span=(1, 3), match='pp'>
 
\1 と \が1重だとエスケープできていなくて失敗していた。 


re.VERBOSE(冗長)と re.IGNORECASE(大文字小文字無視)を同時に設定
 
Pattern = re.compile(r'''
AAA
|BBB\d+
|CCC\d+
''', re.VERBOSE | re.IGNORECASE)
f = re.sub(Pattern, '', f)
こんな感じで、 |  で区切ればよい。

■後方参照

()内でマッチしたものがグループになるが、再利用する場合は\\1(後方参照)を使う。
(※\\1はエスケープのため\\になっている。)

rでrawにしてエスケープしておいてもよい

re.sub("(\d{8})", r"\1-", "1234567890") 

'12345678-90'


例: 何でもよいから同じ文字が2つあるときにマッチ

import re
re.search('(.+)\\1{1}', 'oraange')

<_sre.SRE_Match object; span=(2, 4), match='aa'>

■マッチする部分に名前をつける

(?P<name>regex)

とするとマッチしたところがnameという名前を持ちます。それを呼び出すときは、m.group("name")とします。

アルファベット、スペース、アルファベットという文字列にfirst_name、last_nameという名前をつけてマッチさせます。

>>>m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds")
>>> m.group("first_name")
'Malcolm'
>>> m.group("last_name")
'Reynolds'

0 件のコメント:

コメントを投稿