Каков самый быстрый способ проверить, имеет ли класс определенную функцию?

Я пишу алгоритм поиска пространства состояний 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 выбрасывается по умолчанию.

Есть ли более быстрый способ проверить, не определена ли функция, кроме того, как выхватить исключение?

Я что-то думал о проверке присутствия в каталоге, но это не кажется правильным. hasattr реализуется путем вызова getattr и проверки, если он поднимается, чего я не хочу.

Ответ 1

Да, используйте getattr(), чтобы получить атрибут, и callable(), чтобы убедиться, что это метод:

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

Обратите внимание, что getattr() обычно генерирует исключение, если атрибут не существует. Однако, если вы укажете значение по умолчанию (None, в этом случае), оно вернет это вместо этого.

Ответ 3

Есть ли более быстрый способ проверить, не определена ли функция, кроме того, как выхватить исключение?

Почему вы против этого? В большинстве случаев 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) в основном реализуется путем исключения исключения. Нет ничего плохого в том, что на земле Питона!

Ответ 4

Мне нравится Натан Остгард, и я проголосовал за это. Но другим способом, который вы могли бы решить свою проблему, было бы использование memoizing decorator, в котором был бы кеш результатом вызова функции. Таким образом, вы можете идти вперед и иметь дорогостоящую функцию, которая что-то отличает, но тогда, когда вы вызываете ее снова и снова, последующие вызовы бывают быстрыми; memoized версия функции ищет аргументы в dict, находит результат в dict, когда фактическая функция вычисляет результат и сразу возвращает результат.

Вот рецепт для памятного декоратора под названием "lru_cache" Раймонда Хеттингера. Версия этого стандарта теперь стандартная в модуле functools в Python 3.2.

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

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

Ответ 5

В ответах здесь проверяется, является ли строка именем атрибута объекта. Дополнительный шаг (с использованием вызываемого) необходим, чтобы проверить, является ли атрибут методом.

Таким образом, это сводится к следующему: какой самый быстрый способ проверить, связан ли объект obj с атрибутом. Ответ

'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

Ответ 6

Как и в 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'

Пожалуйста, сделайте нам одолжение, просто продолжайте делать то, что у вас есть в своем вопросе, и НЕ используйте, если вы не используете команду PyPy, взламывающую интерпретатор Python. Что у вас там есть Pythonic, то, что у меня здесь, является чистым EVIL.

Ответ 7

Хотя проверка атрибутов в свойстве __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)

Ответ 8

Вы также можете пройти через класс:

import inspect


def get_methods(cls_):
    members = inspect.getmembers(cls_)
    return dict([member for member in members if inspect.isfunction(member[1])])

# 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')