Pythonでオブジェクト属性をチェックする5つの方法の究極のベンチマーク

ここでは、オブジェクトに属性があるかどうかを判断する方法と、できる限り迅速に属性を実行する方法についての質問が提起されましたが、トピックは十分に詳しく調査されていません。





実際、これがこの短い記事を書く理由でした。 テストのために、属性の存在を判別するために次の(私に知られている)メソッドを選択しました。

  1. おそらく最も明白なのは、 hasattr(obj, name)



    組み込み関数を使用することです。
  2. 別の一般的な方法は、属性にアクセスして、そうでない場合はAttributeError



    処理してアクションを実行することです。
  3. AttributeError



    処理も使用しますが、プロパティに直接アクセスするのではなく、 getattr(obj, name)



    介してアクセスします。 状況は大げさなように見えますが、実際のアプリケーションでは、検証用の属性の名前が動的に生成され、 getattr



    が非常に便利な場合があります。
  4. 非常に簡単な方法(以下のテスト結果を参照) __dict__



    オブジェクトの__dict__



    (もしあれば)を調べることです。 このメソッドを適用する際の問題は、おそらく、クラスのインスタンス、クラス自体、およびそのすべての祖先について__dict__



    が別個であることだけです。 必要な属性の場所が正確にわからない場合、このメソッドには実用的な価値はありません。 dict .has_key(key_name)



    およびdict .__contains__(key_name)



    を使用して、 in



    assert 'My Key Name' in my_dict



    )に対応する2つの方法で__dict__



    を確認することもできます。 すべての長所、短所、および2つの実装オプションを考えると、 __dict__



    は2つの別個のメソッドを使用しないため、「1.5」と見なします。
  5. 最後の、最もエキゾチックな方法は、 dir(obj)



    で必要な属性の名前を調べることです。 ところで、ネストされた__slots__



    クラスをチェックする過程で、 dir()



    関連する興味深い点がいくつか発見されましたが、それについては別の記事で詳しく説明しています:)
メソッドを使用して、理解したことを願っています。 より現実的な状況を得るために、「チェーン」に継承される3つのクラスを作成しましたTestClass3



は、先祖がobject



であるTestClass3



からTestClass3



。 各クラスには、クラスコンストラクターで割り当てられたc3_ ia



という形式の名前を持つ「インスタンス属性」と、クラスのコンパイル段階で定義された「クラス属性」 c2_ ca



があります。



各テストでは、最上位クラスTestClass



の「インスタンス属性」と「クラス属性」、 TestClass3



クラスで定義された「インスタンス属性」と「クラス属性」、および存在しないfake



属性を取得しようとします。



すべてのテストは1万回実行されました。 これらの10Mの同一の操作を完了するために合計で費やした時間は、テスト時間と見なされます。 誰かを傷つけないために、合計時間は、既存の属性と存在しない属性について等しく計算されます。



すべてが好きです。 今の結果。



:

dict_lookup_contains : 5.800250 [2 subtests failed]

dict_lookup : 7.672500 [2 subtests failed]

hasattr : 12.171750 [0 subtests failed]

exc_direct : 27.785500 [0 subtests failed]

exc_getattr : 32.088875 [0 subtests failed]

dir : 267.500500 [0 subtests failed]



:

test_dict_lookup_true_this_ca : FAILED [AssertionError()]

test_dict_lookup_true_parent_ca : FAILED [AssertionError()]

test_dict_lookup_contains_true_this_ca : FAILED [AssertionError()]

test_dict_lookup_contains_true_parent_ca : FAILED [AssertionError()]

test_exc_direct_true_this_ca : 5.133000

test_exc_direct_true_parent_ca : 5.710000

test_dict_lookup_contains_true_parent_ia : 5.789000

test_dict_lookup_contains_false : 5.804000

test_dict_lookup_contains_true_this_ia : 5.804000

test_exc_direct_true_this_ia : 6.037000

test_exc_direct_true_parent_ia : 6.412000

test_hasattr_true_this_ca : 6.615000

test_exc_getattr_true_this_ca : 7.144000

test_hasattr_true_this_ia : 7.193000

test_hasattr_true_parent_ca : 7.240000

test_dict_lookup_false : 7.614000

test_dict_lookup_true_this_ia : 7.645000

test_exc_getattr_true_this_ia : 7.769000

test_dict_lookup_true_parent_ia : 7.817000

test_hasattr_true_parent_ia : 7.926000

test_exc_getattr_true_parent_ca : 8.003000

test_exc_getattr_true_parent_ia : 8.691000

test_hasattr_false : 17.100000

test_exc_direct_false : 49.748000

test_exc_getattr_false : 56.276000

test_dir_true_this_ia : 266.847000

test_dir_true_this_ca : 267.053000

test_dir_false : 267.398000

test_dir_true_parent_ca : 267.849000

test_dir_true_parent_ia : 268.663000








原則として、コメントする特別なことは何もありません-テーブルはそれ自体を語っています。 要約: テスターのソースコードは次のとおりです。



#!/usr/bin/env python

# coding: utf8



import time



__times__ = 10000000



def timeit ( func, res ) :

'' 'Check if ' func ' returns ' res ', if true, execute it ' __times__ ' times (__times__ should be defined in parent namespace) measuring elapsed time.' ''

assert func ( ) == res



t_start = time . clock ( )

for i in xrange ( __times__ ) :

func ( )

return time . clock ( ) - t_start



# Define test classes and create instance of top-level class.

class TestClass3 ( object ) :

c3_ca = 1



def __init__ ( self ) :

self . c3_ia = 1



class TestClass2 ( TestClass3 ) :

c2_ca = 1



def __init__ ( self ) :

TestClass3. __init__ ( self )

self . c2_ia = 2



class TestClass ( TestClass2 ) :

c1_ca = 1



def __init__ ( self ) :

TestClass2. __init__ ( self )

self . c1_ia = 2



obj = TestClass ( )



# Legend:

#

# hasattr, exc_direct, exc_getattr, dict_lookup, dict_lookup_contains, dir - attribute accessing methods.

# true, false - if 'true' we are checking for really existing attribute.

# this, parent - if 'this' we are looking for attribute in the top-level class, otherwise in the top-level class' parent's parent.

# ca, ia - test class attribute ('ca') or instance attribute ('ia') access.

#

# Note about __dict__ lookups: they are not suitable for generic attribute lookup because instance's __dict__ stores only instance's attributes. To look for class attributes we should query them from class' __dict__.



# Test query through hasattr

def test_hasattr_true_this_ca ( ) :

return hasattr ( obj, 'c1_ca' )



def test_hasattr_true_this_ia ( ) :

return hasattr ( obj, 'c1_ia' )



def test_hasattr_true_parent_ca ( ) :

return hasattr ( obj, 'c3_ca' )



def test_hasattr_true_parent_ia ( ) :

return hasattr ( obj, 'c3_ia' )



def test_hasattr_false ( ) :

return hasattr ( obj, 'fake' )



# Test direct access to attribute inside try/except

def test_exc_direct_true_this_ca ( ) :

try :

obj. c1_ca

return True

except AttributeError :

return False



def test_exc_direct_true_this_ia ( ) :

try :

obj. c1_ia

return True

except AttributeError :

return False



def test_exc_direct_true_parent_ca ( ) :

try :

obj. c3_ca

return True

except AttributeError :

return False



def test_exc_direct_true_parent_ia ( ) :

try :

obj. c3_ia

return True

except AttributeError :

return False



def test_exc_direct_false ( ) :

try :

obj. fake

return True

except AttributeError :

return False



# Test getattr access to attribute inside try/except

def test_exc_getattr_true_this_ca ( ) :

try :

getattr ( obj, 'c1_ca' )

return True

except AttributeError :

return False



def test_exc_getattr_true_this_ia ( ) :

try :

getattr ( obj, 'c1_ia' )

return True

except AttributeError :

return False



def test_exc_getattr_true_parent_ca ( ) :

try :

getattr ( obj, 'c3_ca' )

return True

except AttributeError :

return False



def test_exc_getattr_true_parent_ia ( ) :

try :

getattr ( obj, 'c3_ia' )

return True

except AttributeError :

return False



def test_exc_getattr_false ( ) :

try :

getattr ( obj, 'fake' )

return True

except AttributeError :

return False



# Test attribute lookup in dir()

def test_dir_true_this_ca ( ) :

return 'c1_ca' in dir ( obj )



def test_dir_true_this_ia ( ) :

return 'c1_ia' in dir ( obj )



def test_dir_true_parent_ca ( ) :

return 'c3_ca' in dir ( obj )



def test_dir_true_parent_ia ( ) :

return 'c3_ia' in dir ( obj )



def test_dir_false ( ) :

return 'fake' in dir ( obj )



# Test attribute lookup in __dict__

def test_dict_lookup_true_this_ca ( ) :

return obj. __dict__ . has_key ( 'c1_ca' )



def test_dict_lookup_true_this_ia ( ) :

return obj. __dict__ . has_key ( 'c1_ia' )



def test_dict_lookup_true_parent_ca ( ) :

return obj. __dict__ . has_key ( 'c3_ca' )



def test_dict_lookup_true_parent_ia ( ) :

return obj. __dict__ . has_key ( 'c3_ia' )



def test_dict_lookup_false ( ) :

return obj. __dict__ . has_key ( 'fake' )



# Test attribute lookup in __dict__ through __contains__

def test_dict_lookup_contains_true_this_ca ( ) :

return 'c1_ca' in obj. __dict__



def test_dict_lookup_contains_true_this_ia ( ) :

return 'c1_ia' in obj. __dict__



def test_dict_lookup_contains_true_parent_ca ( ) :

return 'c3_ca' in obj. __dict__



def test_dict_lookup_contains_true_parent_ia ( ) :

return 'c3_ia' in obj. __dict__



def test_dict_lookup_contains_false ( ) :

return 'fake' in obj. __dict__



# TEST

tests = {

'hasattr' : {

'test_hasattr_true_this_ca' : True ,

'test_hasattr_true_this_ia' : True ,

'test_hasattr_true_parent_ca' : True ,

'test_hasattr_true_parent_ia' : True ,

'test_hasattr_false' : False ,

} ,

'exc_direct' : {

'test_exc_direct_true_this_ca' : True ,

'test_exc_direct_true_this_ia' : True ,

'test_exc_direct_true_parent_ca' : True ,

'test_exc_direct_true_parent_ia' : True ,

'test_exc_direct_false' : False ,

} ,

'exc_getattr' : {

'test_exc_getattr_true_this_ca' : True ,

'test_exc_getattr_true_this_ia' : True ,

'test_exc_getattr_true_parent_ca' : True ,

'test_exc_getattr_true_parent_ia' : True ,

'test_exc_getattr_false' : False ,

} ,

'dict_lookup' : {

'test_dict_lookup_true_this_ca' : True ,

'test_dict_lookup_true_this_ia' : True ,

'test_dict_lookup_true_parent_ca' : True ,

'test_dict_lookup_true_parent_ia' : True ,

'test_dict_lookup_false' : False ,

} ,

'dict_lookup_contains' : {

'test_dict_lookup_contains_true_this_ca' : True ,

'test_dict_lookup_contains_true_this_ia' : True ,

'test_dict_lookup_contains_true_parent_ca' : True ,

'test_dict_lookup_contains_true_parent_ia' : True ,

'test_dict_lookup_contains_false' : False ,

} ,

'dir' : {

'test_dir_true_this_ca' : True ,

'test_dir_true_this_ia' : True ,

'test_dir_true_parent_ca' : True ,

'test_dir_true_parent_ia' : True ,

'test_dir_false' : False ,

} ,

}



# Perform tests

results = { }

results_exc = { }



for ( test_group_name, test_group ) in tests. iteritems ( ) :

results_group = results [ test_group_name ] = { }

results_exc_group = results_exc [ test_group_name ] = { }

for ( test_name, test_expected_result ) in test_group. iteritems ( ) :

test_func = locals ( ) [ test_name ]

print '%s::%s...' % ( test_group_name, test_name )

try :

test_time = timeit ( test_func, test_expected_result )

results_group [ test_name ] = test_time

except Exception , exc:

results_group [ test_name ] = None

results_exc_group [ test_name ] = exc



# Process results

group_results = [ ]



for ( group_name, group_tests ) in results. iteritems ( ) :

group_true_time = 0.0

group_true_count = 0

group_false_time = 0.0

group_false_count = 0

group_fail_count = 0



for ( test_name, test_time ) in group_tests. iteritems ( ) :

if test_time is not None :

if tests [ group_name ] [ test_name ] :

group_true_count += 1

group_true_time += test_time

else :

group_false_count += 1

group_false_time += test_time

else :

group_fail_count += 1



group_time = ( group_true_time / group_true_count + group_false_time / group_false_count ) / 2

group_results. append ( ( group_name, group_time, group_fail_count ) )



group_results. sort ( key = lambda ( group_name, group_time, group_fail_count ) : group_time )



# Output results

print

print ' :'



for ( group_name, group_time, group_fail_count ) in group_results:

print '%-25s: %10f [%d subtests failed]' % ( group_name, group_time, group_fail_count )



print ' :'

all_results = [ ]

for ( group_name, group_tests ) in results. iteritems ( ) :

for ( test_name, test_time ) in group_tests. iteritems ( ) :

all_results. append ( ( group_name, test_name, test_time ) )

all_results. sort ( key = lambda ( group_name, test_name, test_time ) : test_time )



for ( group_name, test_name, test_time ) in all_results:

if test_time is not None :

print '%-50s: %10f' % ( test_name, test_time )

else :

print '%-50s: FAILED [%r]' % ( test_name, results_exc [ group_name ] [ test_name ] )









私はほとんど忘れていました...テストが実行されたコンピューター: CPU:Intel Pentium D CPU 3.40GHz(2コア(しかし、明らかに、1つだけが使用されました)); RAM:2Gb もちろん、誰かが興味を持っているなら。



更新#1( __getattribute__



テスト結果と以前の結果との比較):


次のクラスは、以前のチェーンの代替として使用されました。



__attributes__ = ( 'c1_ca' , 'c3_ca' , 'c1_ia' , 'c3_ia' )

class TestClass ( object ) :

def __getattribute__ ( self , name ) :

if name in __attributes__:

return 1

else :

raise AttributeError ( )








getattr(obj, name, None) is not None



結果( getattr(obj, name, None) is not None



を考慮するgetattr(obj, name, None) is not None







:

dict_lookup : n/a [5 subtests failed]

dict_lookup_contains : n/a [5 subtests failed]

hasattr : 20.181182 [0 subtests failed]

getattr : 26.283962 [0 subtests failed]

exc_direct : 41.779489 [0 subtests failed]

exc_getattr : 47.757879 [0 subtests failed]

dir : 98.622183 [4 subtests failed]



:

test_dir_true_parent_ia : FAILED [AssertionError()]

test_dir_true_this_ia : FAILED [AssertionError()]

test_dir_true_this_ca : FAILED [AssertionError()]

test_dir_true_parent_ca : FAILED [AssertionError()]

test_dict_lookup_true_parent_ia : FAILED [AttributeError()]

test_dict_lookup_true_this_ia : FAILED [AttributeError()]

test_dict_lookup_true_this_ca : FAILED [AttributeError()]

test_dict_lookup_true_parent_ca : FAILED [AttributeError()]

test_dict_lookup_false : FAILED [AttributeError()]

test_dict_lookup_contains_true_this_ia : FAILED [AttributeError()]

test_dict_lookup_contains_true_parent_ia : FAILED [AttributeError()]

test_dict_lookup_contains_true_parent_ca : FAILED [AttributeError()]

test_dict_lookup_contains_true_this_ca : FAILED [AttributeError()]

test_dict_lookup_contains_false : FAILED [AttributeError()]

test_exc_direct_true_this_ca : 13.346949

test_exc_direct_true_parent_ca : 13.970407

test_exc_direct_true_this_ia : 14.621696

test_hasattr_true_this_ca : 15.077735

test_exc_direct_true_parent_ia : 15.146182

test_exc_getattr_true_parent_ca : 16.305500

test_getattr_true_this_ia : 16.976973

test_hasattr_true_parent_ia : 17.196719

test_hasattr_true_parent_ca : 17.613231

test_getattr_true_this_ca : 18.331266

test_exc_getattr_true_parent_ia : 18.720518

test_hasattr_false : 21.983571

test_getattr_true_parent_ca : 22.087115

test_exc_getattr_true_this_ca : 23.072045

test_hasattr_true_this_ia : 23.627484

test_getattr_true_parent_ia : 24.474635

test_getattr_false : 32.100426

test_exc_getattr_true_this_ia : 34.555669

test_exc_direct_false : 69.287669

test_exc_getattr_false : 72.352324

test_dir_false : 98.622183








次に、結果の比較に移りましょう...



キー: , [ ] | -- __getattribute__- | , __getattribute__- [ ]



, [ ] | -- __getattribute__- | , __getattribute__- [ ]





( ):

dict_lookup : 7.672500 [2] | n/a -- n/a | n/a [5]

dict_lookup_contains : 5.800250 [2] | n/a -- n/a | n/a [5]

hasattr : 12.171750 [0] | 16.176466 -- 1.658035 | 20.181182 [0]

getattr : 15.350072 [0] | 20.817017 -- 1.712302 | 26.283962 [0]

exc_direct : 27.785500 [0] | 34.782495 -- 1.503644 | 41.779489 [0]

exc_getattr : 32.088875 [0] | 39.923377 -- 1.488300 | 47.757879 [0]

dir : 267.500500 [0] | 183.061342 -- 0.368680 | 98.622183 [4]



( ):

dict_lookup : 7.672500 [2] | n/a -- n/a | n/a [5]

dict_lookup_contains : 5.800250 [2] | n/a -- n/a | n/a [5]

dir : 267.500500 [0] | 183.061342 -- 0.368680 | 98.622183 [4]

exc_getattr : 32.088875 [0] | 39.923377 -- 1.488300 | 47.757879 [0]

exc_direct : 27.785500 [0] | 34.782495 -- 1.503644 | 41.779489 [0]

hasattr : 12.171750 [0] | 16.176466 -- 1.658035 | 20.181182 [0]

getattr : 15.350072 [0] | 20.817017 -- 1.712302 | 26.283962 [0]








キー: , | -- __getattribute__- | , __getattribute__-



, | -- __getattribute__- | , __getattribute__-





( ):

test_dict_lookup_true_parent_ia







All Articles