6/19/2011

[読書] Python クックブック 第2版 読んでみる。第1章-その2

間があいたけど、第1章-その2。結構、勉強になることあるよね。
肩肘張らず、楽しく、ゆる~くがモットーです。



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.

ほう、バイト型にですか。
  1. >>> baseformat = "5s 3x 8s 8s"  
  2. >>> numremain = len("123456781234567812345678")-struct.calcsize(baseformat)  
  3. >>> format = "%s %ds" % (baseformat, numremain)  
  4. >>> leading, s1, s2, trailing = struct.unpack(format, b"123456781234567812345678")  
  5. >>> s1  
  6. b'12345678'  
  7. >>> s2  
  8. b'12345678'  
  9. >>> leading  
  10. b'12345'  
  11. >>> trailing  
  12. b''  
  13. >>>   
  14.   
  15.   
  16. くっつける  
  17. >>> struct.pack(format,leading, s1, s2, trailing)  
  18. b'12345\x00\x00\x001234567812345678'  


1.14 インデントの変更

例外の部分を3に合わせて

  1. def addSpace(s,numAdd):  
  2.  """行頭に指定数の空白を付与する。"""  
  3.  white=" "*numAdd  
  4.  return white + white.join(s.splitlines(True))  
  5.      
  6. def numSpace(s):  
  7.  """各行の行頭にあるスペース数を、リスト形式で戻す"""  
  8.  return [len(line)-len(line.lstrip()) for line in s.splitlines()]  
  9.   
  10.   
  11. def delSpace(s,numDel):  
  12.  """行頭から指定したスペース分をスライスした(除去した行を返す)"""  
  13.  if numDel > min(numSpace(s)):  
  14.   raise ValueError("removing more spaces than there are")  
  15.  return '\n'.join([ line[numDel:] for line in s.splitlines() ])  
  16.   
  17.    
  18. def unIndntBlock(s):  
  19.  """一番インデントの小さいものに合わせて成形する"""  
  20.  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()を取得するように変更してみた。
  1. def unexpand(astring, tablen=2):  
  2.     import re  
  3.     #スペースと非スペースに切断する。  
  4.     pices = re.split(r'( +)', astring.expandtabs(tablen))  
  5.     #全体の長さを初期化  
  6.     lensofar = 0  
  7.     for i,piece in enumerate(pices):  
  8.         #要素の長さ  
  9.         thislen = len(str.encode(piece))  
  10.         #全体の長さ  
  11.         lensofar += thislen  
  12.         if piece.isspace():  
  13.             #要素の長さをタブ1個のスペース数で割る。余りがタブに出来ない分なのでスペースになる。  
  14.             numblanks = thislen % tablen  
  15.             numtabs = (thislen - numblanks + tablen - 1)//tablen  
  16.             pices[i] = '\t'*numtabs + ' '*numblanks  
  17.     return ''.join(pices)  

2個の空白があえばタブにする。
>>> unexpand("1234 567   890  あ あ   い")
'1234 567\t 890\tあ あ\t い'

1.18 一度に複数のパターンを置換する
  1. import re  
  2. def multiple_replace(txt,adict):  
  3. #1.辞書の内容にメタキャラが含まれる可能性がるので、escapeする。  
  4. #mapでキーを取りだし|で結合。検索パターンの作成。  
  5. rx = re.compile('|'.join(map(re.escape,adict)))  
  6. #引数はマッチオブジェクト  
  7. def one_xlat(match):  
  8. return adict[match.group(0)]  
  9. #検索にヒットするたびに、マッチオブジェクトを引数に関数をコールバック。  
  10. 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個渡すところでエラーになったので辞書だけにした。
  1. import re  
  2. def make_xlat(**kwds):  
  3. adict = dict(**kwds)  
  4. rx = re.compile('|'.join(map(re.escape ,adict)))  
  5. def one_xlat(match):  
  6. return adict[match.group(0)]  
  7. def xlat(text):  
  8. return rx.sub(one_xlat, text)  
  9. 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'
>>>

置換方法にバリエーションを持たせたい->(単語単位)場合に置換したいとか。
クラス化して継承を利用する。
  1. import re  
  2. """ 
  3. 基底クラス 
  4. """  
  5. class make_xlat():  
  6. def __init__(self, **kwds):  
  7. self.adict = dict(**kwds)  
  8. self.rx = self.make_rx()  
  9. def make_rx(self):  
  10. return re.compile('|'.join(map(re.escape,self.adict)))  
  11. def one_xlat(self,match):  
  12. return self.adict[match.group(0)]  
  13. def __call__(self,text):  
  14. return self.rx.sub(self.one_xlat,text)  
  1. import make_xlat  
  2. import re  
  3. """ 
  4. 派生クラス 
  5. """  
  6. class make_xlat_by_wh_word(make_xlat.make_xlat):  
  7. def make_rx(self):  
  8. 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を継承したクラスを作成。

  1. class iStr(str):  
  2.     """ 
  3.     str継承。検索と比較は大文字小文字を無視。 
  4.     """  
  5.     def __init__(self,*args):  
  6.         self._lowered = str.lower(self)  
  7.     def __repr__(self):  
  8.         return '%s(%s)' % (type(self).__name__,str.__repr__(self))  
  9.     def __hash__(self):  
  10.         return hash(self._lowered)  
  11.     def lower(self):  
  12.         return self._lowered  

strのメソッドをラップ。
  1. def _make_case_insensitive(name):  
  2.     str_meth = getattr(str,name)  
  3.     def x(self,other,*args):  
  4.         try: other = other.lower()  
  5.         except(TypeError ,AttributeError ,ValueError ): pass  
  6.         return str_meth(self._lowered,other,*args)  
  7.     #Python2.4 late add x.func_name=name  
  8.     x.func_name=name  
  9.     setattr(iStr, name, x)  

case insensitiveとして扱うものを属性に追加
  1. for name in 'eq lt le gt ne contains'.split():  
  2.     _make_case_insensitive('__%s__' % name)  
  3.   
  4. for name in 'count endswith find index rfind rindex startswith'.split():  
  5.     _make_case_insensitive(name)  

こうなるものが、
>>> HOGE="Test"
>>> HOGE.startswith("t")
False

こうなるよと。
>>> FOO=iStr("Test")
>>> FOO.startswith("t")
True

このレシピは、ケースインセンシティブなコンテナ型などを作る際に応用が利く。

ケースインセンシティブなリスト。iListの各itemがiStrでラップされるだけ。
  1. class iList(list):  
  2.  def __init__(self,*args):  
  3.   self[:] = self  
  4.  wrap_each_item = iStr  
  5.  def __setitem__(self,i,v):  
  6.   if isinstance(i,slice): v = map(self.wrap_each_item,v)  
  7.   else: v = self.wrap_each_item(v)  
  8.   list.__setitem__(self,i,v)  
  9.  def append(self,item):  
  10.   list.append(self,self.wrap_each_item(item))  
  11.  def extend(self,seq):  
  12.   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
>>>

0 件のコメント: