Pythonには、オブジェクトにある名前の属性が存在するかどうかをチェックする hasattr という組み込み関数があります。
例えば、リストオブジェクトに append
という属性が存在するかどうか確認するときは、次のようにかきます。
L = []
print(hasattr(L, 'append'))
print(L.append)
リストオブジェクトには append
という属性が存在し、メソッドだということがわかります。
もう一つ、appppend
という属性があるかどうか調べてみましょう。
L = []
print(hasattr(L, 'appppend'))
print(L.appppend)
def my_hasattr(object, name):
try:
getattr(object, name)
return True
except AttributeError:
return False
getattr で属性値を取得し、正常に取得できれば True
を、AttributeError
例外が発生すれば False
を返します。
これはドキュメントに記載されている通りの実装ですが、見るからに遅そうです。例外処理というのは比較的重たい処理で、例外の発生を検出したら実行情報を保存し、適切な except
ブロックに移動して処理を継続できるようにしなければなりません。
存在しない属性がたくさんあるようなケースでは、AttributeError
が大量に発生するためにhasattr()
は遅くなってしまいそうです。上の my_hasattr()
を使って実験してみましょう。
まず、属性が存在する場合を測定してみます。
%%time
L = []
for i in range(10000):
my_hasattr(L, 'append')
同様に、存在しない属性を調べてみましょう。
%%time
L = []
for i in range(10000):
my_hasattr(L, 'appppend')
予想通り、存在しない属性のチェックは約2倍の時間がかかっています。
では、それぞれのケースを、本物の hasattr()
で調べてみましょう。
%%time
L = []
for i in range(10000):
hasattr(L, 'append')
%%time
L = []
for i in range(10000):
hasattr(L, 'appppend')
これはしたり。本物の hasttr
では、どちらもほとんど差がありません。なんなら例外が発生している方がちょっと速くなってしまっています。
my_hasattr()
の実験を見て分かる通り、try-except
を使った例外処理はやや時間のかかる処理です。しかし、実は例外を発生されるのはそんなに時間がかかりません。Pythonインタープリタが発生した例外を検出し、例外情報を作成したりする処理は時間がかかりますが、発生させるだけならほとんど時間はかからないのです。
my_hasattr(L, 'appppend')
のように存在しない属性をチェックすると、次のように処理が行われます。
my_hasattr()
が getattr(L, 'appppend')
を呼び出す。AttributeError
が発生し、例外情報を設定する。except AttributeError:
に移動する。return False
ここで、時間がかかるのは 3. の例外が発生したあとの処理で、2. の例外の設定そのものは、かなり短時間で終了します。
ところで、hasattr()
はPythonではなく、C言語で書かれているので、発生した例外をインタープリタに見つからないように消してしまえます。擬似的なPythonで書くと、次のようになっています。
# Pythonで擬似的に書いたhasattrの実装
def hasattr(object, name):
# getattr()を呼び出す
getattr(object, name)
# 例外が発生しているか
if is_exception_raised():
# 例外はAttributionErrorか
if exception_is_attributeerror():
# 例外をクリア
clear_exception()
# Falseを返す
return False
return True
このようにすることで、getattr()
で発生した例外をPythonインタープリタに見つかる前に消してしまうため、負荷の大きい例外処理を行わずに取得した結果だけを利用できます。このため、AttributionError
例外が発生してもしなくても、例外処理をおこなうことなく、同じような負荷で処理できています。
最近の実装を見ずに記憶だけでこの記事を書いていましたが、@methaneさんの チューニング が入っていて、サンプルに使っていた datetime.datetime
のようなオブジェクトの場合は、通常の getattr
とはちょっと違う処理が行われるように変更されています。
このため、サンプルコードで使っていたオブジェクトを datetime.datetime
からリストオブジェクトに変更しましいた。
Copyright © 2020 Atsuo Ishimoto
Powered by miyadaiku