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

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 по умолчанию выдает.

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

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

Alex
источник
8
"hasattr реализуется путем вызова getattr и проверки его повышения, чего я не хочу". Почему нет? Почему вас волнует, что делает реализация?
Detly
4
has_op = lambda obj, op: callable(getattr(obj, op, None))
samplebias
1
Попробуйте: hasattr(connection, 'invert_opt').
kenorb

Ответы:

205

Да, используйте 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
Что, если функция находится не в этом классе, а в родительском классе? В этом случае я получаю True, даже если дети никогда не реализуют эту функцию (используя hasattr)
darkgaze
46

Он работает как в Python 2, так и в Python 3.

hasattr(connection, 'invert_opt')

hasattrвозвращается, Trueесли для объекта подключения определена функция invert_opt. Вот документация для вас, чтобы пастись

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

Antony
источник
5
Хотя код приветствуется, он всегда должен сопровождаться пояснением. Это не должно длиться долго, но это ожидаемо.
Питер - Восстановить Монику
хороший, можно указать на статью, хоть бы это не помешало :)
Виталий Терзиев
5
Это также возвращает True, если у соединения есть атрибут connection.invert_opt = 'foo'.
Роберт Хёниг
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
thorwhalen
источник
Это не работает на классах, которые используют __slots__. __slots__помочь ускорить доступ к атрибутам на ~ 10%. stackoverflow.com/a/14119024/1459669
noɥʇʎԀʎzɐɹƆ
3

Мне нравится ответ Натана Остгарда, и я проголосовал за него. Но другой способ решить вашу проблему - использовать мемоизирующий декоратор, который кэширует результат вызова функции. Таким образом, вы можете использовать дорогостоящую функцию, которая что-то вычисляет, но затем, когда вы вызываете ее снова и снова, последующие вызовы выполняются быстро; мемоизированная версия функции ищет аргументы в 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

steveha
источник
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'

Пожалуйста , нам одолжение, просто продолжать делать то , что у вас есть в вашем вопросе и НЕ когда - либо использовать это , если вы не в команде PyPy взлома в интерпретатор Python. У вас там Pythonic, а у меня - чистое ЗЛО .

YH Wong
источник
Это будет верно, если метод вызовет какое-либо исключение. Вы также должны проверить, co_namesравно ли ('NotImplementedError',). Однако я не уверен, делает ли это более или менее злым.
kindall
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)

Не то чтобы я поощрял этот подход, но, похоже, он работает.

[EDIT] Повышение производительности еще выше, если имя метода не принадлежит данному классу:

>>%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__может быть отменено. Этому нельзя доверять.
Сяо