Каков самый быстрый способ проверить, имеет ли класс определенную функцию?
Я пишу алгоритм поиска пространства состояний 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
, в этом случае), оно вернет это вместо этого.
Ответ 2
Он работает как в 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
Ответ 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')