正規表現の基礎

入門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__