正規表現の基礎
入門Python3 7.1.3
正規表現についてです。
pythonの正規表現はなにやらいろいろメソッドがあったりして把握が大変なのでとにかく御託はいいから一番シンプルな使い方はなんなのってことで実装してみます。
str1 = "abcdefg" result = re.search('cde',str1) if result: print("match!")
$ py main.py match!
abcdefgにcdeという文字列が含まれているかどうかを確認するだけの処理です。
またこの他にre.compileをつかって先に正規表現をコンパイルしてから利用するやり方もあります。
import re regex = re.compile('cde') # あらかじめコンパイル str1 = "abcdefg" result = regex.search(str1) if result: print("match!")
$ py main.py match!
検索の仕方
さて先ほどsearchというメソッドを使用しましたが他にも検索のためのメソッドが存在します。
ドキュメントにあるように良く使われる4つのメソッドについて確認してみます。
メソッド/属性 | 目的 |
---|---|
match() | 文字列の先頭で正規表現とマッチするか判定します。 |
search() | 文字列を操作して、正規表現がどこにマッチするか調べます。 |
findall() | 正規表現にマッチする部分文字列を全て探しだしリストとして返します。 |
finditer() | 正規表現にマッチする部分文字列を全て探しだし iterator として返します。 |
matchは単にsearchの時に「^」で先頭でマッチさせるならまったく同じ意味になるかんじなんですかね。
matchとsearchはヒットしなければNoneが返り、ヒットすればMatchオブジェクトを返します。
Matchオブジェクトでよく使われそうなものとして以下のメソッドの確認をしてみます。
メソッド/属性 | 目的 |
---|---|
group() | 正規表現にマッチした文字列を返す |
group(x) | 正規表現にマッチした()でグループ化した部分の文字列を返す |
groups() | 正規表現にマッチした()でグループ化した部分の文字列をタプルで全て返す |
start() | マッチの開始位置を返す |
end() | マッチの終了位置を返す |
span() | マッチの位置 (start, end) を含むタプルを返す |
import re str1 = "abcdefg" r = re.search('(c)(de)',str1) if r: print(r.group()) # cde print(r.group(1)) # c print(r.group(2)) # de print(r.groups()) # ('c', 'de') print(r.start()) # 2 print(r.end()) # 5 print(r.span()) # (2, 5)
$ py main.py cde c de ('c', 'de') 2 5 (2, 5)
次はfindallについて。これはマッチした部分の文字列が全てリストで返ってくるというもの。
import re str1 = "ab11cde22fg34" r = re.findall('\d+',str1) # 数値にマッチ if r: print(r)
$ py main.py ['11', '22', '34']
次はfinditerについて。findallと似ているがリストではなくイテレータを返すところが違う。
また各要素にはMatchオブジェクトが返る。
import re str1 = "aa1bb2cc3bb4" r = re.finditer('bb(\d+)',str1) # 「bb数値」にマッチ if r: for item in r: print(item.group()) print(item.group(1)) print(item.groups()) print(item.start()) print(item.end()) print(item.span()) print('--')
$ py main.py bb2 2 ('2',) 3 6 (3, 6) -- bb4 4 ('4',) 9 12 (9, 12) --
findallだとグループ化が効かないので基本的にはこのfinditerを使った方が便利かなという印象ですね。
Pythonでの書式指定出力(新しいスタイル)
入門Python3 7.1.2.2
Python3からは古いスタイルよりもこの新しいスタイルの書式指定を使った方がよいとのこと。
print("hello {}! {}".format("world",10))
$ py main.py hello world! 10
「%s」などの書き方だったのが「{}」に変わり、文字列に対して%演算子で繋げていたのがformatメソッド呼び出しに変わるといったかんじですね。
さてこの新しいスタイルでいいなと思ったのは展開対象の引数を指定できるようになったところです。
print("hello {1}! {0}".format("world",10))
$ py main.py hello 10! world
「{0}」でformatの一つ目、「{1}」で二つ目の引数を指定できます。
formatにキーワード引数を渡した場合はそのキー名でも指定できます。
print("hello {foo}! {bar}".format(foo="FOO", bar="BAR"))
$ py main.py hello FOO! BAR
辞書を渡して指定することもできます。
d = {"foo":"FOO", "bar":"BAR"} print("hello {0[foo]}! {0[bar]}".format(d))
$ py main.py hello FOO! BAR
古いスタイルのようなフォーマット指定をしたい場合はコロンで区切って指定します。
# print("hello %s! %d" % ("world",10))と同じ意味 print("hello {:s}! {:d}".format("world",10))
$ py main.py hello world! 10
新しいスタイルの方が柔軟で便利ですね。
Pythonでの書式指定出力(古いスタイル)
入門Python3 7.1.2.2
Python2でも3でも使える文字列の古いスタイルの書式指定方法。C言語でいうところのprintfみたいなものですね。
print("hello %d" % 10)
$ py main.py hello 10
文字列に%演算子で繋げると書式が展開された文字列に変換されます。
%演算子を使うのはC++のboost::formatにも似てますね。
複数の値を展開したい場合はタプルを使います
print("hello %s! %d" % ("world",10))
$ py main.py hello world! 10
他にどういった書式が指定できるかはリファレンスで確認しておきましょう。
引数リストのアンパック
例えば、リストやタプルの要素を個別に関数へ引数を渡す処理を実装したいとします。
愚直に書くと以下のようになります。
def foo(a,b,c,d): print(a + b + c + d) ary = [2,4,6,8] foo(ary[0],ary[1],ary[2],ary[3])
$ py main.py 20
いちいちary[0]とかを並べて書くのが面倒ですね、そんな時は*演算子を使って引数リストのアンパックをすることができます
def foo(a,b,c,d): print(a + b + c + d) ary = [2,4,6,8] foo(*ary) # foo(ary[0],ary[1],ary[2],ary[3])と同じ意味
$ py main.py 20
とっても便利ですね。
辞書でも同じようなことができます。辞書の場合は**演算子を使います
def foo(a,b,c,d): print(a + b + c + d) h = {"a":2,"b":4,"c":6,"d":8} foo(**h) # foo(h["a"],h["b"],h["c"],h["d"])と同じ意味
$ py main.py 20
namedtupleによる名前付きタプル
入門Python3 6.14.1
namedtupleを使うことで名前付きのタプルを作ることができます。
from collections import namedtuple Foo = namedtuple('Foo', ('bar', 'baz'))
まずはこのコードでbarとbazのプロパティを持つFooクラスが生成されます。
次にFooオブジェクトを生成。namedtupleの第二引数に指定した順に引数を渡します。
foo = Foo(1,2)
もしくは
foo = Foo(bar=1,baz=2)
といった感じで初期化します。
これで通常のタプルのように[]演算子によるアクセスも可能だし、オブジェクトのプロパティの用にアクセスも可能となります。
from collections import namedtuple Foo = namedtuple('Foo', ('bar', 'baz')) foo = Foo(bar=1,baz=2) # タプルのようにアクセス print(foo[0]) print(foo[1]) # プロパティのようにアクセス print(foo.bar) print(foo.baz) # イテレータでアクセス for item in foo: print(item)
$ py main.py 1 2 1 2 1 2
またタプルと同じくイミュータブルなので変更は不可です
from collections import namedtuple Foo = namedtuple('Foo', ('bar', 'baz')) foo = Foo(bar=1,baz=2) foo.bar = "aaa"
$ py main.py Traceback (most recent call last): File "main.py", line 8, in <module> foo.bar = "aaa" AttributeError: can't set attribute
便利ですね。
もしこのFooクラスをnamedtupleを使わずに実装した場合どうなるか、試しに実装してみました。
class Foo(): def __init__(self,bar,baz): self.__list = (bar,baz) self.__counter = 0 def __getitem__(self,key): return self.__list[key] @property def bar(self): return self.__list[0] @property def baz(self): return self.__list[1] def __iter__(self): self.__counter = 0 return self def __next__(self): self.__counter += 1 if ( self.__counter > len(self.__list) ): raise StopIteration return self.__list[self.__counter - 1]
こんな感じになりました。
実際にはnamedtupleはもっと多機能なので、こんなコード毎回書いてられないですね。活用しましょう。
Pythonで独自クラスで配列をエミュレートしてみる
今回は演算子によるアクセスと、forループのみを実装してみます。
配列のエミュレートをするには特殊メソッドを使います。
まずは演算子の実装
__getitem__で[]演算子で値を取り出す時の処理をエミュレートできます
class Foo(): def __getitem__(self,key): return key * 2 foo = Foo() print(foo[0]) print(foo[1]) print(foo[2])
$ py main.py 0 2 4
__setitem__で[]演算子で値を設定する時の処理をエミュレートできます
class Foo(): def __setitem__(self,key,value): print(key,value) foo = Foo() foo[2] = 10
$ py main.py 2 10
forループのエミュレートは__iter__を用意し、そこでイテレータオブジェクトを返す必要があります。
そしてイテレータオブジェクトには__next__を実装しておく必要があります。
例として指定した数までカウントするイテレータを実装してみます。
class Foo(): def __init__(self,max_counter): self.__max_counter = max_counter self.__counter = 0 def __iter__(self): return self def __next__(self): self.__counter += 1 if self.__counter > self.__max_counter: self.__counter = 0 raise StopIteration return self.__counter foo = Foo(10) for item in foo: print(item)
$ py main.py 1 2 3 4 5 6 7 8 9 10
ループを終了させるにはStopIterationという例外を発生させる必要があります。
配列のエミュレートには他にもたくさんの特殊メソッドを実装する必要があります。
独自クラスで真面目にやるとかなり大変ですね。
Pythonでの演算子のオーバーロード
Pythonでの演算子のオーバーロードは特殊メソッドで実現できます。
全部網羅してるわけではないですが、いくつか列挙してみます。
比較演算子系のオーバーロード
特殊メソッド名 | 例 |
---|---|
__lt__(self, other) | self < other |
__le__(self, other) | self <= other |
__eq__(self, other) | self == ohter |
__ne__(self, other) | self != other |
__gt__(self, other) | self > other |
__ge__(self, other) | self >= other |
特殊メソッドが呼ばれているかprintを入れて確認してみます。
class Foo(): def __init__(self,x): self.__x = x def __lt__(self, other): print("__lt__") return self.__x < other def __le__(self, other): print("__le__") return self.__x <= other def __eq__(self, other): print("__eq__") return self.__x == other def __ne__(self, other): print("__ne__") return self.__x != other def __gt__(self, other): print("__gt__") return self.__x > other def __ge__(self, other): print("__ge__") return self.__x >= other x = Foo(100) x < 10 x <= 10 x == 10 x != 10 x > 10 x >= 10
$ py main.py __lt__ __le__ __eq__ __ne__ __gt__ __ge__
算術演算子系のオーバーロード(左辺)
特殊メソッド名 | 例 |
---|---|
__add__(self, other) | self + other |
__sub__(self, other) | self - other |
__mul__(self, other) | self * other |
__truediv__(self, other) | self / other |
__floordiv__(self, other) | self // other |
__mod__(self, other) | self % other |
__pow__(self, other) | self ** other |
__lshift__(self, other) | self << other |
__rshift__(self, other) | self >> other |
__and__(self, other) | self & other |
__xor__(self, other) | self ^ other |
__or__(self, other) | self | other |
これは基本的には計算結果をreturnするだけなのですが、注意点としては自身と同じクラスの別オブジェクトとして返すところですね。
class Foo(): def __init__(self,x): self.__x = x def __add__(self, other): print("__add__") return self.__class__(self.__x + other) def __sub__(self, other): print("__sub__") return self.__class__(self.__x - other) def __mul__(self, other): print("__mul__") return self.__class__(self.__x * other) def __truediv__(self, other): print("__truediv__") return self.__class__(self.__x / other) def __floordiv__(self, other): print("__floordiv__") return self.__class__(self.__x // other) def __mod__(self, other): print("__mod__") return self.__class__(self.__x % other) def __pow__(self, other): print("__pow__") return self.__class__(self.__x ** other) def __lshift__(self, other): print("__lshift__") return self.__class__(self.__x << other) def __rshift__(self, other): print("__rshift__") return self.__class__(self.__x >> other) def __and__(self, other): print("__and__") return self.__class__(self.__x & other) def __xor__(self, other): print("__xor__") return self.__class__(self.__x ^ other) def __or__(self, other): print("__or__") return self.__class__(self.__x | other) x = Foo(100) x + 10 x - 10 x * 10 x / 10 x // 10 x % 10 x ** 10 x << 10 x >> 10 x & 10 x ^ 10 x | 10
$ py main.py __add__ __sub__ __mul__ __truediv__ __floordiv__ __mod__ __pow__ __lshift__ __rshift__ __and__ __xor__ __or__
ちなみにreturn時にFooを使わずにself.__class__を使っているのは継承を考慮した場合の書き方です。
算術演算子系のオーバーロード(右辺)
特殊メソッド名 | 例 |
---|---|
__radd__(self, other) | other + self |
__rsub__(self, other) | other - self |
__rmul__(self, other) | other * self |
__rtruediv__(self, other) | other / self |
__rfloordiv__(self, other) | other // self |
__rmod__(self, other) | other % self |
__rpow__(self, other) | other ** self |
__rlshift__(self, other) | other << self |
__rrshift__(self, other) | other >> self |
__rand__(self, other) | other & self |
__rxor__(self, other) | other ^ self |
__ror__(self, other) | other | self |
先ほどのは左辺のクラスの場合の特殊メソッドでしたが、こちらは対象が右辺のクラスの場合に動作します。
つまり「x + 10」ならx.__add__(10)が起動し、「10 + x」ならx.__radd__(10)が起動するといったかんじです。
実際に__radd__の動作確認をしてみます。
class Foo(): def __init__(self,x): self.__x = x def __add__(self, other): print("__add__") return self.__class__(self.__x + other) def __radd__(self, other): print("__radd__") return self.__class__(self.__x + other) x = Foo(100) x + 10 10 + x
$ py main.py __add__ __radd__
さてここで疑問が生じます、左辺のクラスに__add__が存在し、右辺のクラスに__radd__が存在する場合はどちらが呼ばれるんでしょう。
class Foo(): def __add__(self, other): print("__add__") class Bar(): def __radd__(self, other): print("__radd__") x = Foo() y = Bar() x + y
$ py main.py __add__
両方存在している場合は__add__が優先されるかんじですね。
算術代入演算子系のオーバーロード
__iadd__(self, other) | self += other |
__isub__(self, other) | self -= other |
__imul__(self, other) | self *= other |
__itruediv__(self, other) | self /= other |
__ifloordiv__(self, other) | self //= other |
__imod__(self, other) | self %= other |
__ipow__(self, other) | self **= other |
__ilshift__(self, other) | self <<= other |
__irshift__(self, other) | self >>= other |
__iand__(self, other) | self &= other |
__ixor__(self, other) | self ^= other |
__ior__(self, other) | self |= other |
これは計算した後、自身への代入を行う演算子のオーバーロードですね。
内部的にはselfの値を変更しselfをreturnすると辻褄が合います。
class Foo(): def __init__(self,x): self.__x = x def __iadd__(self, other): print("__iadd__") self.__x += other return self def __isub__(self, other): print("__isub__") self.__x -= other return self def __imul__(self, other): print("__imul__") self.__x *= other return self def __itruediv__(self, other): print("__itruediv__") self.__x /= other return self def __ifloordiv__(self, other): print("__ifloordiv__") self.__x //= other return self def __imod__(self, other): print("__imod__") self.__x %= other return self def __ipow__(self, other): print("__ipow__") self.__x **= other return self def __ilshift__(self, other): print("__ilshift__") self.__x <<= other return self def __irshift__(self, other): print("__irshift__") self.__x >>= other return self def __iand__(self, other): print("__iand__") self.__x &= other return self def __ixor__(self, other): print("__ixor__") self.__x ^= other return self def __ior__(self, other): print("__ior__") self.__x |= other return self x = Foo(100) x += 1 x = Foo(100) x -= 1 x = Foo(100) x *= 1 x = Foo(100) x /= 1 x = Foo(100) x //= 1 x = Foo(100) x %= 1 x = Foo(100) x **= 1 x = Foo(100) x <<= 1 x = Foo(100) x >>= 1 x = Foo(100) x &= 1 x = Foo(100) x ^= 1 x = Foo(100) x |= 1
$ py main.py __iadd__ __isub__ __imul__ __itruediv__ __ifloordiv__ __imod__ __ipow__ __ilshift__ __irshift__ __iand__ __ixor__ __ior__
単項演算子系のオーバーロード
特殊メソッド名 | 例 |
---|---|
__neg__(self) | -self |
__pos__(self) | +self |
__invert__(self) | ~self |
単項演算子系です。これも確認しておきます。
class Foo(): def __init__(self,x): self.__x = x def __neg__(self): print("__neg__") return self.__class__(-self.__x) def __pos__(self): print("__pos__") return self.__class__(+self.__x) def __invert__(self): print("__invert__") return self.__class__(~self.__x) x = Foo(100) -x +x ~x
$ py main.py __neg__ __pos__ __invert__