Який найшвидший спосіб перевірити, чи визначений у класі функцію?


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 і перевірки, чи він підвищується, що не те, що я хочу.


8
"hasattr реалізується за допомогою виклику getattr і перевірки, чи він підвищується, що я не хочу." Чому ні? Чому вам байдуже, що робить реалізація?
деті

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

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і у випадку, з яким чомусь проти був ОП.
Санта-

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


5
Хоча код цінується, він завжди повинен мати супровідне пояснення. Це не повинно бути довгим, але очікується.
peterh

добре, ви можете вказати на статтю, хоча це не зашкодить :)
Терзієв Віталій

5
Це також повертає True, якщо з'єднання має атрибут connection.invert_opt = 'foo'.
Роберт Хоніг

20

Чи є більш швидкий спосіб перевірити, чи функція не визначена, ніж пошук винятку?

Чому ви проти цього? У більшості піфонічних випадків краще просити пробачення, ніж дозволу. ;-)

hasattr реалізується за допомогою виклику getattr і перевірки, чи він підвищується, що не те, що я хочу.

Знову ж, чому це? Далі досить пітонічно:

    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

Тут відповіді перевіряють, чи рядок є ім'ям атрибута об'єкта. Необхідний додатковий крок (з використанням дзвінка), щоб перевірити, чи є атрибут методом.

Отже, це зводиться до: який найшвидший спосіб перевірити, чи об’єкт obj має атрибут attrib. Відповідь така

'attrib' in obj.__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

Мені подобається відповідь Натана Остгарда, і я її схвалив. Але ще одним способом вирішити вашу проблему було б використовувати декоратор запам'ятовування, який кешуватиме результат виклику функції. Таким чином, ви можете йти вперед і мати дорогу функцію, яка щось з'ясовує, але тоді, коли ви будете викликати це знову і знову, наступні дзвінки швидко проходять; запам'ятована версія функції шукає аргументи в дикті, знаходить результат у дикті, коли фактична функція обчислила результат, і повертає результат відразу.

Ось рецепт запам'ятовуючого декоратора під назвою "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


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. Те, що у вас там є пітонічне, те, що я маю тут, - це чисте ЗЛИВО .


Це буде справедливо, якщо метод породжує будь-який виняток. Ви також повинні перевірити, чи 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__може бути відмінено Йому не можна довіряти
Сяо
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.