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.

ほう、バイト型にですか。
>>> 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
>>>

6/15/2011

[Python][翻訳]続・関数デコレータ

前回に続いて、Ariel OrtizのBlogからの翻訳ネタ。
初心者にも理解しやすい素敵な内容。

内容に少しでもオカシイところがあれば、 それは私の責です。
原文あたって下さいませ。突っ込みもお願いします。
More On Function Decorators


More On Function Decorators

前回の投稿で、Pythonでどのように、関数デコレータを使うか2,3の例をあげたが、これらの例は、未だかなり制限されて例示されている。まず第一に、その関数は一つだけの位置引数を受け取ってデコレートされると仮定していた。もし、2つ以上の位置引数や、一つかそれ以上のキーワード引数をとる関数をデコレートしたい場合、何ができるだろう?
第二に、任意の特別な方法で設定するデコレータを許可していなかった。 では、その動作を変える事が出来るように、どうやってデコレータ自身に入力引数を渡せばよいのだろう?

今から、もう少し複雑な例で詳しく説明する。その例は、上記のいずれの制限も持たない、もっと一般的な関数デコレータをどの様に定義するのか?について、より良い知見を与えてくれるだろう。

 NOTE:ここで例示する、すべてのPythonコードはPython3に基づいている。
ソースコードの全文は以下

デコレートされた関数が実行中にraiseする、一つか、それ以上の種類の例外に対して、「例外の飲み込み」ができる関数デコレーターが欲しいとする。

もし、特定の一つの例外が実際に生成されたら、例外が実行スタックを伝播して、プログラム終了の原因になることを許可する代わりに、返されるデフォルト値を指定できるようにしたい。デコレーターのクライアントは、基本的に、明示的なtry節を書くための努力をしなくてもよくなる。

どのようにこれが動くか、デモンストレートするために、我々にはこれのような関数があるとしよう。:
def divide(dividend=0, divisor=1):
    return dividend / divisor
この関数を呼んだ場合、ZeroDivisionError と TypeErrorの、最低2つの例外が発生する可能性がある。最初の例外はdivisorパラメーターがゼロの時に発生する。
2番目の例外は、2つのパラメータのどれかが、数値タイプではない時、2つより多いパラメータが送られた時、存在しないキーワードパラメーターを使おうとした場合、に発生する。

例としては
>>> divide(1, 2)
0.5

>>> divide(1, 0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in divide
ZeroDivisionError: int division or modulo by zero

>>> divide("hello")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in divide
TypeError: unsupported operand type(s) for /: 'str' 
and 'int' 

>>> divide(whatever=1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: divide() got an unexpected keyword 
argument 'whatever'
我々の”例外飲み込み”デコレーターは2つのキーワードパラメータを受け取る。

* exceptions:一つの例外クラスまたは、いくつかの例外クラスを含む一つのタプル。これらは”飲み込まれる”べき例外をあらわしている。
それ以外の全ての例外は通常通り、伝播する。もし、明示的な指定がなければ、デフォルトはBaseException(Pythonの例外階層のルート)。
* default: もし、指定した例外が発生した場合に返される値。明示的な指定がなければ、デフォルトはNoneである。

さあ、3つの使用例を見てみよう。
Example 1: もし、ゼロ除算がおこなわれたら、0を返す。

@swallow(exceptions=ZeroDivisionError, default=0)
def divide(dividend=0, divisor=1):
    return dividend / divisor

Example 2:もし、ZeroDivisionErrorか、TypeErrorが発生したら、0を返す。
@swallow(
    exceptions=(ZeroDivisionError, TypeError), 
    default=0)
def divide(dividend=0, divisor=1):
    return dividend / divisor

Example 3:二つの、デコレーターを連鎖させる。そうすることで、それぞれの指定した例外に、独自のデフォルト値を持たせることができる。

@swallow(exceptions=TypeError, default='Huh?')
@swallow(exceptions=ZeroDivisionError, default=0)
def divide(dividend=0, divisor=1):
    return dividend / divisor

これが、最後の例における、デコレートされたdivide関数を使う方法だ。
>>> divide(1, 2)
0.5
>>> divide(1, 0)
0
>>> divide("hello")
'Huh?'

”例外飲み込み”デコレーターを実装するために、我々はどのように@構文が動くのか、よく見なければならない。@記号の後に、評価時に呼び出し可能なオブジェクトを生成する一つの式を、実際に指定する必要がある。この呼び出し可能なオブジェクトを効果的に呼ぶと、唯一の引数として、デコレートされる関数を受け取る。そして同じ関数か、いくつかの新しい呼び出し可能なものを返す。

@記号の後ろの式は、一般的には関数の名前(デコレータ関数)だけだが、1)と2)も、また可能だ。
1)__call__メドッドの実装を含んだ、一つの新しいクラスのインスタンス。
2)他の関数の呼び出し。
これらの両方のオプションで、インプットパラメータを介して指定することにより、デコレーターに追加の情報を渡すことができる。

__call__ メソッドを定義した、クラスを使う最初の実装は、次のようにして実現できる。
class swallow:
    
    class helper:
        
        def __init__(self, outer, fun):
            self.outer = outer
            self.fun = fun            
            
        def __call__(self, *args, **kwargs):
            try:
                return self.fun(*args, **kwargs)
            except self.outer.exceptions:
                return self.outer.default
    
    def __init__(self, 
                 default=None, 
                 exceptions=BaseException):
        self.default = default
        self.exceptions = exceptions
        
    def __call__(self, fun):                            
        return swallow.helper(self, fun)

どうやってこれが、動くか理解しよう。コードは以下。
@swallow(exceptions=ZeroDivisionError, default=0)
def divide(dividend=0, divisor=1):
    return dividend / divisor
上記は、基本的に以下と同じものである。
def divide(dividend=0, divisor=1):
    return dividend / divisor     
divide = swallow(exceptions=ZeroDivisionError,default=0).__call__(divide)
上記のコードの最後の行で、我々のニーズに応じて、”例外飲み込み”クラスの一つのインスタンスが作られ、初期化されている。
それから、__call__ メソッドがまさしく同じインスタンス上で実行される。それは順番に新しいswallow.helperネストクラスを作成し、返す。

その最終的な効果として、divide変数は、この特定のクラスのインスタンスを参照する。

swallow.helperクラスはいくつでも引数、キーワード引数を受ける __call__ メソッドを実装している。
それゆえに、このクラスのインスタンスは、効果的に、必要に応じた任意の引数をとる、どんな任意の関数でもデコレートすることができる。
__call__メソッド自身は、全ての作業を行うtry節を含んでいる。
デコレートされた関数を呼び出し、返される値を送り返す。
指定された例外以外が、キャッチされた場合には、指定されたデフォルト値を返す。
swallow と swallow.helper クラスのインスタンス変数を使うことで、仕事をする為の全ての変数が便利に格納され、共有される事に注意してほしい。

”例外の飲み込み”デコレーターの2通り目の実装方法としては、関数定義(及びそれに対応するlexicalクロージャー)だけで記述することができる。
それはとても短いものだが、最初は理解する事が少し難しいかもしれない。
def swallow(default=None, exceptions=BaseException):
    def helper1(fun):
        def helper2(*args, **kwargs):
            try:
                return fun(*args, **kwargs)
            except exceptions:
                return default
        return helper2
    return helper1

見てきたように、swallow関数はデコレータに設定する為の、二つの入力パラメータをとって、
@構文が期待するように、ただ、ネストされた関数、helper1を返す。
helper1関数は、そのただ一つの引数としてデコレートされた関数を伴って、直ぐに呼ばれる。そして、その結果としてhelper2関数を返す。
これは、もし、私たちが関数fをデコレートする場合、変数fはhelper2への参照を保持することを意味する。
だから今、fが呼ばれる度に、helper2が呼ばれて、前述したようにtry節が、自身のjobを正確に実行する。

Note
1 例外の飲み込み、特定の状況下においては便利になるが、見境なくこのテクニックを使うのは避けるべきだ。
具体的には、コードのデバッグが困難になる。

2 呼び出し可能なオブジェクトは__call__という名前の特別な属性を含んでいる。もし、xが呼び出し可能なオブジェクトならば
構文
x(arg1, arg2, arg3) は
x.__call__(arg1, arg2, arg3) と等しい。
呼び出し可能なオブジェクトは、ユーザー定義関数、ビルトイン関数、ビルトインオブジェクトのメソッド、クラスインスタンスのメソッドと、自身の__call__メソッドで定義もしくは継承したクラスのインスタンスを含んでいる。

3 もし、*args や **kwargs ノーテーションに親しくなければ、Pythonチュートリアルで詳細をチェックすること。


正直、これは勉強になった。Thunks Ariel Ortiz!
訳がおかしいところは随時修正していこう・・・