Movatterモバイル変換


[0]ホーム

URL:


Pythonのhasattr()は遅い?

Pythonには、オブジェクトにある名前の属性が存在するかどうかをチェックするhasattr という組み込み関数があります。

例えば、リストオブジェクトにappend という属性が存在するかどうか確認するときは、次のようにかきます。

In [57]:
L=[]print(hasattr(L,'append'))print(L.append)
True<built-in method append of list object at 0x7fbc80542d80>

リストオブジェクトにはappend という属性が存在し、メソッドだということがわかります。

もう一つ、appppend という属性があるかどうか調べてみましょう。

In [59]:
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'

appppend 属性は存在しないようです。

hasattr() は遅い?

ところで、このhasattr()ドキュメント には、次のように書かれています。

この関数は、 getattr(object, name) を呼び出して AttributeError を送出するかどうかを見ることで実装されています。

この説明を読むと、hasattr() は次のように実装されているように思えます。

In [50]:
defmy_hasattr(object,name):try:getattr(object,name)returnTrueexceptAttributeError:returnFalse

getattr で属性値を取得し、正常に取得できればTrue を、AttributeError 例外が発生すればFalse を返します。

これはドキュメントに記載されている通りの実装ですが、見るからに遅そうです。例外処理というのは比較的重たい処理で、例外の発生を検出したら実行情報を保存し、適切なexcept ブロックに移動して処理を継続できるようにしなければなりません。

存在しない属性がたくさんあるようなケースでは、AttributeError が大量に発生するためにhasattr()は遅くなってしまいそうです。上のmy_hasattr() を使って実験してみましょう。

まず、属性が存在する場合を測定してみます。

In [60]:
%%timeL=[]foriinrange(10000):my_hasattr(L,'append')
CPU times: user 2.91 ms, sys: 0 ns, total: 2.91 msWall time: 2.92 ms

同様に、存在しない属性を調べてみましょう。

In [61]:
%%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() で調べてみましょう。

In [62]:
%%timeL=[]foriinrange(10000):hasattr(L,'append')
CPU times: user 1.56 ms, sys: 0 ns, total: 1.56 msWall time: 1.56 ms
In [63]:
%%timeL=[]foriinrange(10000):hasattr(L,'appppend')
CPU times: user 1.54 ms, sys: 0 ns, total: 1.54 msWall time: 1.54 ms

これはしたり。本物のhasttr では、どちらもほとんど差がありません。なんなら例外が発生している方がちょっと速くなってしまっています。

hasattr() の仕組み

my_hasattr() の実験を見て分かる通り、try-except を使った例外処理はやや時間のかかる処理です。しかし、実は例外を発生されるのはそんなに時間がかかりません。Pythonインタープリタが発生した例外を検出し、例外情報を作成したりする処理は時間がかかりますが、発生させるだけならほとんど時間はかからないのです。

my_hasattr(L, 'appppend') のように存在しない属性をチェックすると、次のように処理が行われます。

  1. my_hasattr()getattr(L, 'appppend') を呼び出す。
  2. getattr() 内部でAttributeError が発生し、例外情報を設定する。
  3. Pythonインタープリタが例外の発生を検出し、情報を整理してexcept AttributeError: に移動する。
  4. 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 例外が発生してもしなくても、例外処理をおこなうことなく、同じような負荷で処理できています。

2021/1/8

最近の実装を見ずに記憶だけでこの記事を書いていましたが、@methaneさんの チューニング が入っていて、サンプルに使っていたdatetime.datetime のようなオブジェクトの場合は、通常のgetattr とはちょっと違う処理が行われるように変更されています。

このため、サンプルコードで使っていたオブジェクトをdatetime.datetime からリストオブジェクトに変更しましいた。

Amazon.co.jpアソシエイト:
2021-01-07


nomanコマンド


UoPeople ENGL 0008 顛末記


UoPeople ENGL 0008 中間報告


Pythonのパターンマッチ


Python で Google Analytics Reporting APIを使う


Copyright © 2020 Atsuo Ishimoto

Powered bymiyadaiku


[8]ページ先頭

©2009-2025 Movatter.jp