クラスに関数が定義されているかどうかを確認する最も速い方法は何ですか?


132

私はAI状態空間検索アルゴリズムを作成しています。検索アルゴリズムをすばやく実装するために使用できる汎用クラスがあります。サブクラスは必要な操作を定義し、残りはアルゴリズムが行います。

ここで私は行き詰まっています:親の状態を何度も再生成しないようにしたいので、任意の状態に合法的に適用できる操作を返す次の関数があります。

def get_operations(self, include_parent=True):
    ops = self._get_operations()
    if not include_parent and self.path.parent_op:
        try:
            parent_inverse = self.invert_op(self.path.parent_op)
            ops.remove(parent_inverse)
        except NotImplementedError:
            pass
    return ops

そして、invert_op関数はデフォルトでスローします。

例外をキャッチするよりも関数が定義されていないかどうかを確認するより速い方法はありますか?

dirに存在するかどうかのチェックの行で何かを考えていましたが、それは正しくないようです。hasattrは、getattrを呼び出し、それが発生するかどうかをチェックすることで実装されます。


8
「hasattrは、getattrを呼び出し、それが発生するかどうかをチェックすることで実装されます。これは私が望んでいることではありません。」何故なの?なぜ実装が何をするのか気にするのですか?
detly

4
has_op = lambda obj, op: callable(getattr(obj, op, None))
samplebias 2011年

1
試してみてください:hasattr(connection, 'invert_opt')
kenorb

回答:


204

はい、使用getattr()して属性を取得し、callable()それがメソッドであることを確認します。

invert_op = getattr(self, "invert_op", None)
if callable(invert_op):
    invert_op(self.path.parent_op)

getattr()属性が存在しない場合、通常は例外をスローすることに注意してください。ただし、デフォルト値(Noneこの場合は)を指定すると、代わりにデフォルト値が返されます。


3
getattrこの場合のの実装はhasattr、OPが何らかの理由でそうであったのと同じように、黙って例外をキャッチし、代わりにデフォルト値を返すことにも注意してください。
サンタ

3
関数がそのクラスではなく親クラスにある場合はどうなりますか?この場合、子供がその機能を実装しない場合でも(hasattrを使用して)、Trueを取得します
darkgaze

46

Python 2とPython 3の両方で動作します

hasattr(connection, 'invert_opt')

hasattrTrue接続オブジェクトに関数がinvert_opt定義されている場合に返されます。ここに放牧するためのドキュメントがあります

https://docs.python.org/2/library/functions.html#hasattr https://docs.python.org/3/library/functions.html#hasattr


5
コードは評価されますが、常に説明が必要です。これは長い時間である必要はありませんが、予想されます。
peterh-モニカを2015年

良いことですが、害はありませんが、記事を指すことができます:)
Vitaliy Terziev

5
これは、接続に属性がある場合にもTrueを返しますconnection.invert_opt = 'foo'
RobertHönig2017年

20

例外をキャッチするよりも関数が定義されていないかどうかを確認するより速い方法はありますか?

なぜそれに反対ですか?ほとんどのPythonicのケースでは、許可よりも許しを求める方が適切です。;-)

hasattrは、getattrを呼び出し、それが発生するかどうかをチェックすることで実装されます。

繰り返しますが、それはなぜですか?以下はかなりPythonicです:

    try:
        invert_op = self.invert_op
    except AttributeError:
        pass
    else:
        parent_inverse = invert_op(self.path.parent_op)
        ops.remove(parent_inverse)

または、

    # if you supply the optional `default` parameter, no exception is thrown
    invert_op = getattr(self, 'invert_op', None)  
    if invert_op is not None:
        parent_inverse = invert_op(self.path.parent_op)
        ops.remove(parent_inverse)

ただし、getattr(obj, attr, default)基本的には例外をキャッチすることでも実装されています。Pythonの土地に問題はありません。


4

ここでの応答は、文字列がオブジェクトの属性の名前であるかどうかをチェックします。属性がメソッドかどうかを確認するには、追加の手順(callableを使用)が必要です。

つまり、オブジェクトobjに属性attribがあるかどうかを確認する最速の方法は何でしょうか。答えは

'attrib' in obj.__dict__

これは、dictがキーをハッシュするため、キーの存在のチェックが高速であるためです。

以下のタイミング比較を参照してください。

>>> class SomeClass():
...         pass
...
>>> obj = SomeClass()
>>>
>>> getattr(obj, "invert_op", None)
>>>
>>> %timeit getattr(obj, "invert_op", None)
1000000 loops, best of 3: 723 ns per loop
>>> %timeit hasattr(obj, "invert_op")
The slowest run took 4.60 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 674 ns per loop
>>> %timeit "invert_op" in obj.__dict__
The slowest run took 12.19 times longer than the fastest. This could mean that an intermediate result is being cached.
10000000 loops, best of 3: 176 ns per loop

これは、を使用するクラスでは失敗します__slots____slots__属性アクセスを最大10%高速化します。stackoverflow.com/a/14119024/1459669
noɥʇʎԀʎzɐɹƆ

3

私はネイサンオストガードの答えが好きで、私はそれを賛成票で投票しました。しかし、問題を解決できる別の方法は、関数呼び出しの結果をキャッシュするメモ化デコレータを使用することです。したがって、先に進んで何かを計算する高価な関数を使用できますが、それを繰り返し呼び出すと、後続の呼び出しは高速になります。関数のメモ化されたバージョンは、dictで引数を検索し、実際の関数が結果を計算したときからdictで結果を見つけ、すぐに結果を返します。

レイモンドヘッティンガーによる「lru_cache」と呼ばれるメモ用デコレータのレシピを次に示します。このバージョンは、Python 3.2のfunctoolsモジュールで標準になりました。

http://code.activestate.com/recipes/498245-lru-and-lfu-cache-decorators/

http://docs.python.org/release/3.2/library/functools.html


2

Pythonでのすべてのことと同様に、十分に努力すると、根性に陥って、本当に厄介なことができるようになります。さて、これが厄介な部分です:

def invert_op(self, op):
    raise NotImplementedError

def is_invert_op_implemented(self):
    # Only works in CPython 2.x of course
    return self.invert_op.__code__.co_code == 't\x00\x00\x82\x01\x00d\x00\x00S'

ちょうどあなたがあなたの質問を持っていると何をやり続ける、私たちに好意を行ってくださいしないでくださいあなたがPythonインタプリタにハッキングPyPyチームである場合を除き、これまでこれを使います。あなたが持っているのはPythonicで、私がここに持っているのは純粋なEVILです。


これは、メソッドが例外を発生させる場合に当てはまります。また、co_namesがに等しいかどうかを確認する必要もあり('NotImplementedError',)ます。しかし、これが多かれ少なかれ悪になるかどうかはわかりません。
キンダル

1

また、クラスを受講することもできます。

import inspect


def get_methods(cls_):
    methods = inspect.getmembers(cls_, inspect.isfunction)
    return dict(methods)

# Example
class A(object):
    pass

class B(object):
    def foo():
        print('B')


# If you only have an object, you can use `cls_ = obj.__class__`
if 'foo' in get_methods(A):
    print('A has foo')

if 'foo' in get_methods(B):
    print('B has foo')

0

__dict__プロパティの属性のチェックは非常に高速ですが、__ dict__ハッシュに表示されないため、これをメソッドに使用することはできません。ただし、パフォーマンスがそれほど重要である場合は、クラスでハックの回避策をとることができます。

class Test():
    def __init__():
        # redefine your method as attribute
        self.custom_method = self.custom_method

    def custom_method(self):
        pass

次に、メソッドを次のように確認します。

t = Test()
'custom_method' in t.__dict__

との時間比較getattr

>>%timeit 'custom_method' in t.__dict__
55.9 ns ± 0.626 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

>>%timeit getattr(t, 'custom_method', None)
116 ns ± 0.765 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

私がこのアプローチを奨励しているわけではありませんが、うまくいくようです。

[編集]メソッド名が特定のクラスにない場合、パフォーマンスがさらに向上します。

>>%timeit 'rubbish' in t.__dict__
65.5 ns ± 11 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

>>%timeit getattr(t, 'rubbish', None)
385 ns ± 12.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

1
__dict__オーバーライドできます。信用できません。
Xiao、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.