Pythonには、オブジェクトにある名前の属性が存在するかどうかをチェックするhasattr という組み込み関数があります。
例えば、リストオブジェクトにappend という属性が存在するかどうか確認するときは、次のようにかきます。
L=[]print(hasattr(L,'append'))print(L.append)
True<built-in method append of list object at 0x7fbc80542d80>
リストオブジェクトにはappend という属性が存在し、メソッドだということがわかります。
もう一つ、appppend という属性があるかどうか調べてみましょう。
L=[]print(hasattr(L,'appppend'))print(L.appppend)
False
---------------------------------------------------------------------------AttributeError Traceback (most recent call last)<ipython-input-59-1cbdd23b05f2> in<module> 2 L=[] 3 print(hasattr(L,'appppend'))----> 4print(L.appppend)AttributeError: 'list' object has no attribute 'appppend'
defmy_hasattr(object,name):try:getattr(object,name)returnTrueexceptAttributeError:returnFalse
getattr で属性値を取得し、正常に取得できればTrue を、AttributeError 例外が発生すればFalse を返します。
これはドキュメントに記載されている通りの実装ですが、見るからに遅そうです。例外処理というのは比較的重たい処理で、例外の発生を検出したら実行情報を保存し、適切なexcept ブロックに移動して処理を継続できるようにしなければなりません。
存在しない属性がたくさんあるようなケースでは、AttributeError が大量に発生するためにhasattr()は遅くなってしまいそうです。上のmy_hasattr() を使って実験してみましょう。
まず、属性が存在する場合を測定してみます。
%%timeL=[]foriinrange(10000):my_hasattr(L,'append')
CPU times: user 2.91 ms, sys: 0 ns, total: 2.91 msWall time: 2.92 ms
同様に、存在しない属性を調べてみましょう。
%%timeL=[]foriinrange(10000):my_hasattr(L,'appppend')
CPU times: user 8.26 ms, sys: 0 ns, total: 8.26 msWall time: 8.16 ms
予想通り、存在しない属性のチェックは約2倍の時間がかかっています。
では、それぞれのケースを、本物のhasattr() で調べてみましょう。
%%timeL=[]foriinrange(10000):hasattr(L,'append')
CPU times: user 1.56 ms, sys: 0 ns, total: 1.56 msWall time: 1.56 ms
%%timeL=[]foriinrange(10000):hasattr(L,'appppend')
CPU times: user 1.54 ms, sys: 0 ns, total: 1.54 msWall time: 1.54 ms
これはしたり。本物の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の実装defhasattr(object,name):# getattr()を呼び出すgetattr(object,name)# 例外が発生しているかifis_exception_raised():# 例外はAttributionErrorかifexception_is_attributeerror():# 例外をクリアclear_exception()# Falseを返すreturnFalsereturnTrue
このようにすることで、getattr() で発生した例外をPythonインタープリタに見つかる前に消してしまうため、負荷の大きい例外処理を行わずに取得した結果だけを利用できます。このため、AttributionError 例外が発生してもしなくても、例外処理をおこなうことなく、同じような負荷で処理できています。
最近の実装を見ずに記憶だけでこの記事を書いていましたが、@methaneさんの チューニング が入っていて、サンプルに使っていたdatetime.datetime のようなオブジェクトの場合は、通常のgetattr とはちょっと違う処理が行われるように変更されています。
このため、サンプルコードで使っていたオブジェクトをdatetime.datetime からリストオブジェクトに変更しましいた。
Copyright © 2020 Atsuo Ishimoto
Powered bymiyadaiku