肩肘張らず、楽しく、ゆる~くがモットーです。
1.13 文字列の一部にアクセスする
structモジュールを使う。サンプルの方法は3だとエラー。
ドキュメントから
3.x
This module performs conversions between Python values and C structs represented as Python bytes objects.
2.x
This module performs conversions between Python values and C structs represented as Python strings.
ほう、バイト型にですか。
- >>> baseformat = "5s 3x 8s 8s"
- >>> numremain = len("123456781234567812345678")-struct.calcsize(baseformat)
- >>> format = "%s %ds" % (baseformat, numremain)
- >>> leading, s1, s2, trailing = struct.unpack(format, b"123456781234567812345678")
- >>> s1
- b'12345678'
- >>> s2
- b'12345678'
- >>> leading
- b'12345'
- >>> trailing
- b''
- >>>
- くっつける
- >>> struct.pack(format,leading, s1, s2, trailing)
- b'12345\x00\x00\x001234567812345678'
1.14 インデントの変更
例外の部分を3に合わせて
- def addSpace(s,numAdd):
- """行頭に指定数の空白を付与する。"""
- white=" "*numAdd
- return white + white.join(s.splitlines(True))
- def numSpace(s):
- """各行の行頭にあるスペース数を、リスト形式で戻す"""
- return [len(line)-len(line.lstrip()) for line in s.splitlines()]
- def delSpace(s,numDel):
- """行頭から指定したスペース分をスライスした(除去した行を返す)"""
- if numDel > min(numSpace(s)):
- raise ValueError("removing more spaces than there are")
- return '\n'.join([ line[numDel:] for line in s.splitlines() ])
- def unIndntBlock(s):
- """一番インデントの小さいものに合わせて成形する"""
- return delSpace(s,min(numSpace(s)))
試してみる。
空白が3,2,4個行頭にあるとする。
>>> s=' Hello\n world!\n goodbye'
>>> min(numSpace(s))
2
>>> print(unIndntBlock(s))
Hello
world!
goodbye
>>>
各行の行頭からスペースが2個削除。
1.15 タブとスペースの変換
スペースをタブに変更する。
サンプルだとダブルバイトが入ると変になったので、str.encode()のlen()を取得するように変更してみた。
- def unexpand(astring, tablen=2):
- import re
- #スペースと非スペースに切断する。
- pices = re.split(r'( +)', astring.expandtabs(tablen))
- #全体の長さを初期化
- lensofar = 0
- for i,piece in enumerate(pices):
- #要素の長さ
- thislen = len(str.encode(piece))
- #全体の長さ
- lensofar += thislen
- if piece.isspace():
- #要素の長さをタブ1個のスペース数で割る。余りがタブに出来ない分なのでスペースになる。
- numblanks = thislen % tablen
- numtabs = (thislen - numblanks + tablen - 1)//tablen
- pices[i] = '\t'*numtabs + ' '*numblanks
- return ''.join(pices)
2個の空白があえばタブにする。
>>> unexpand("1234 567 890 あ あ い")
'1234 567\t 890\tあ あ\t い'
1.18 一度に複数のパターンを置換する
- import re
- def multiple_replace(txt,adict):
- #1.辞書の内容にメタキャラが含まれる可能性がるので、escapeする。
- #mapでキーを取りだし|で結合。検索パターンの作成。
- rx = re.compile('|'.join(map(re.escape,adict)))
- #引数はマッチオブジェクト
- def one_xlat(match):
- return adict[match.group(0)]
- #検索にヒットするたびに、マッチオブジェクトを引数に関数をコールバック。
- return rx.sub(one_xlat,txt)
まず、検索パターンを作成する。縦棒で検索文字列をつなぎ、compileする。
そして、re.subに置換文字列の代わりに、コールバック関数を渡す。
こうした場合re.subではマッチが起きるたびにコールバック引数オブジェクトをコールする。
その際、コールバック引数オブジェクトへの唯一の引数は、re.MatchObjectインスタンスが自動的に渡される。
簡単な例だと、以下な感じ。
>>> import re
>>> rx = re.compile('a|b|c')
>>> def hoge(match):
return "x"
>>> print(rx.sub(hoge,'abc45'))
xxx45
>>> def hoge(match):
print(match.group(0))
return "x"
>>> print(rx.sub(hoge,'abc45'))
a
b
c
xxx45
>>>
multiple_replaceはコールされるたびに、正規表現の再計算とone_xlat補助関数の再定義が走るので、
クロージャ・アプローチの方が良い。
元サンプルの可変長引数 *argsは不要というか、結局辞書化する必要が出てくるしなぁ。
dictに*args,**kwdsと2個渡すところでエラーになったので辞書だけにした。
- import re
- def make_xlat(**kwds):
- adict = dict(**kwds)
- rx = re.compile('|'.join(map(re.escape ,adict)))
- def one_xlat(match):
- return adict[match.group(0)]
- def xlat(text):
- return rx.sub(one_xlat, text)
- return xlat
>>> adict={
"Larry Wall":"Hoge",
"creator":"killer",
"Perl":"World",
}
>>> text="Larry Wall is the creator of Perl"
こうでも良いし。
>>> fnc=make_xlat(**adict)
>>> fnc(text)
'Hoge is the killer of World'
>>>
こうしてもいいわけね。
>>> fnc=make_xlat(Perl="World",the="ssss")
>>> fnc(text)
'Larry Wall is ssss creator of World'
>>>
置換方法にバリエーションを持たせたい->(単語単位)場合に置換したいとか。
クラス化して継承を利用する。
- import re
- """
- 基底クラス
- """
- class make_xlat():
- def __init__(self, **kwds):
- self.adict = dict(**kwds)
- self.rx = self.make_rx()
- def make_rx(self):
- return re.compile('|'.join(map(re.escape,self.adict)))
- def one_xlat(self,match):
- return self.adict[match.group(0)]
- def __call__(self,text):
- return self.rx.sub(self.one_xlat,text)
- import make_xlat
- import re
- """
- 派生クラス
- """
- class make_xlat_by_wh_word(make_xlat.make_xlat):
- def make_rx(self):
- return re.compile(r'\b%s\b' % r'\b|\b'.join(map(re.escape,self.adict)))
単語単位にマッチしているので、PerlsはWorldに置換されない。
>>> fnc=make_xlat_wh_word.make_xlat_by_wh_word(**adict)
>>> fnc(text)
'Hoge is the killer of Perls'
1.24 一部の文字列のみ大文字小文字無関係にする
strを継承したクラスを作成。
- class iStr(str):
- """
- str継承。検索と比較は大文字小文字を無視。
- """
- def __init__(self,*args):
- self._lowered = str.lower(self)
- def __repr__(self):
- return '%s(%s)' % (type(self).__name__,str.__repr__(self))
- def __hash__(self):
- return hash(self._lowered)
- def lower(self):
- return self._lowered
strのメソッドをラップ。
- def _make_case_insensitive(name):
- str_meth = getattr(str,name)
- def x(self,other,*args):
- try: other = other.lower()
- except(TypeError ,AttributeError ,ValueError ): pass
- return str_meth(self._lowered,other,*args)
- #Python2.4 late add x.func_name=name
- x.func_name=name
- setattr(iStr, name, x)
case insensitiveとして扱うものを属性に追加
- for name in 'eq lt le gt ne contains'.split():
- _make_case_insensitive('__%s__' % name)
- for name in 'count endswith find index rfind rindex startswith'.split():
- _make_case_insensitive(name)
こうなるものが、
>>> HOGE="Test"
>>> HOGE.startswith("t")
False
こうなるよと。
>>> FOO=iStr("Test")
>>> FOO.startswith("t")
True
このレシピは、ケースインセンシティブなコンテナ型などを作る際に応用が利く。
ケースインセンシティブなリスト。iListの各itemがiStrでラップされるだけ。
- class iList(list):
- def __init__(self,*args):
- self[:] = self
- wrap_each_item = iStr
- def __setitem__(self,i,v):
- if isinstance(i,slice): v = map(self.wrap_each_item,v)
- else: v = self.wrap_each_item(v)
- list.__setitem__(self,i,v)
- def append(self,item):
- list.append(self,self.wrap_each_item(item))
- def extend(self,seq):
- list.extend(self,map(self.wrap_each_item,seq))
>>> WARA=iList()
>>> WARA
[]
>>> WARA.append("a")
>>> WARA.append("b")
>>> WARA.append("c")
>>>
>>> WARA
[iStr('a'), iStr('b'), iStr('c')]
>>> WARA.append("A")
>>> WARA
[iStr('a'), iStr('b'), iStr('c'), iStr('A')]
>>>
>>> WARA.count("a")
2
>>>