関数やメソッドの修飾構文の機能向上は、Python 2.4の拡張のうち、目玉ともいえる部分です。この構文については開発者メーリングリストにおいて、長期間にわたって議論が繰り返されました。関数やメソッドに対する修飾機能そのものはPython 2.2から存在していましたが、専用の構文は用意されていませんでした。
例えば、クラスメソッドを定義する場合、従来はリスト1のように記述していました。この方法はクラスの記述が複雑になったり、メソッドのコード量が増えると、メソッドの定義部分とクラスメソッドとしての登録部分が離れてしまい、可読性が低下する問題がありました。
class foo:
def method(args):
:
:
method = classmethod(method)
Python 2.4では、「@」という記号を使って、メソッドを修飾する構文が提供され、リスト2のように書くことが可能になりました。この@を使った構文は、クラスメソッドを定義する場合以外にも、自由に使用できます。
class foo:
@classmethod
def method(args):
:
:
実行例12では、関数オブジェクトにcreatedTimeという属性を作成し、その値として、時刻を代入する修飾用の関数createdTimeを定義しています。関数fooとbarを定義する際にこの修飾を指定しています。結果として、それぞれの関数オブジェクトにはcreatedTimeという属性が作られ、それを参照すれば定義された時刻が分かります。このような関数を修飾する関数のことをデコレータといいます。
>>> import time
>>> def createdTime(funcObj):
... funcObj.createdTime = time.time()
... return funcObj
...
>>> @createdTime
... def foo(x):
... print x + 1
...
>>> @createdTime
... def bar(name):
... print "Hello %s" % name
...
>>> foo.createdTime
1103025973.760793
>>> bar.createdTime
1103026042.8535249
>>> foo(10)
11
>>> bar("World")
Hello World
ここで重要なのは、修飾用の関数が適用されるのは実行時ではなく、定義時である点です。つまり、デコレータによって複数の関数に共通したシグネチャを持たせる操作を簡単に記述できるようになったわけです。
デコレータには引数を与えることもでき、これを利用するといろいろと面白いことができます。リスト3は関数の引数の型をチェックするデコレータの例です。実行結果は実行例13のようになり、正しく引数の型チェックが行われていることが分かります。
import math
def declareArgs(*argTypes):
def checkArguments(func):
assert func.func_code.co_argcount == len(argTypes)
def wrapper(*args, **kwargs):
pos = 1
for (arg, argType) in zip(args, argTypes):
assert isinstance(arg, argType), \
"Value %r dose not match %s at %d" % (arg, argType, pos)
pos += 1
return func(*args, **kwargs)
wrapper.func_name = func.func_name
return wrapper
return checkArguments
@declareArgs(float, float)
def calcDistance(x, y):
return math.sqrt(x * x + y * y)
print calcDistance(3.14, 1.592)
print calcDistance(2.0, 4)
また、この一連の動作はリスト4のようにコードを書いた場合とまったく同じです。リスト4で、print calcDistance(3.14, 1.592)を実行すると、実際に起動する関数本体はwrapper(*args, **kwargs)ということになります。wrapper関数の引数argsは(3.14, 1.592)になっています。この実引数がargTypesで与えられている型に対応しているかを順番にチェックし、すべて正しければ func(*args, **kwargs)が実行され、結果を返します。変数funcは、上位の関数checkArgumentsでオリジナルのcalcDistanceオブジェクトが代入されていますので、結果としてオリジナルのcalcDistance(3.14, 1.592)が実行されることになります。
def calcDistance(x, y):
return math.sqrt(x * x + y * y)
checkArgFuncObj = declareArgs(float, float)
wrapperFuncObj = checkArgFuncObj(calcDistance)
calcDistance = wrapperFuncObj
このような関数のカスタマイズは、従来も行えましたが、デコレータを使うことでさらに簡潔に記述できるようになったわけです。デコレータはこれ以外にも多くの利用法がありますので、研究してみると面白いと思います。
Python 2.4から、10進数演算の機能を提供するDecimalモジュールが標準になりました。これで、1.1を10回足すと正しく11になる計算ができるようになりました(リスト5)。浮動小数点数による計算では、丸め誤差のため計算結果は11.0になりませんが、decimalを使った10進数計算では正しい結果が得られます。
>>> import decimal
>>> dnum = decimal.Decimal("1.1")
>>> dsum = decimal.Decimal("0")
>>> rnum = 1.1
>>> rsum = 0.0
>>> for i in range(10):
... dsum = dsum + dnum
... rsum = rsum + rnum
...
>>> rsum
10.999999999999998
>>> dsum
Decimal("11.0")
>>> str(dsum)
'11.0'
Copyright(c)2010 SOFTBANK Creative Inc. All rights reserved.