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)を使用して、 inassert '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

Source: https://habr.com/ru/post/J43935/


All Articles