Quel est le moyen le plus rapide de vérifier si une classe a une fonction définie?

132

J'écris un algorithme de recherche d'espace d'état AI, et j'ai une classe générique qui peut être utilisée pour implémenter rapidement un algorithme de recherche. Une sous-classe définirait les opérations nécessaires, et l'algorithme s'occupe du reste.

Voici où je suis bloqué: je veux éviter de régénérer l'état parent encore et encore, j'ai donc la fonction suivante, qui renvoie les opérations qui peuvent être légalement appliquées à n'importe quel état:

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

Et la fonction invert_op lance par défaut.

Existe-t-il un moyen plus rapide de vérifier si la fonction n'est pas définie que de détecter une exception?

Je pensais à quelque chose sur la vérification du présent dans dir, mais cela ne semble pas correct. hasattr est implémenté en appelant getattr et en vérifiant s'il déclenche, ce qui n'est pas ce que je veux.

Alex
la source
8
"hasattr est implémenté en appelant getattr et en vérifiant s'il déclenche, ce qui n'est pas ce que je veux." Pourquoi pas? Pourquoi vous souciez-vous de ce que fait la mise en œuvre?
detly
4
has_op = lambda obj, op: callable(getattr(obj, op, None))
samplebias
1
Essayez: hasattr(connection, 'invert_opt').
kenorb

Réponses:

205

Oui, utilisez getattr()pour obtenir l'attribut et callable()pour vérifier qu'il s'agit d'une méthode:

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

Notez que getattr()lève normalement une exception lorsque l'attribut n'existe pas. Cependant, si vous spécifiez une valeur par défaut ( Nonedans ce cas), il la renverra à la place.

Nathan Ostgard
la source
3
Notez également que l'implémentation de getattrdans ce cas intercepte une exception silencieusement et renvoie la valeur par défaut à la place, tout comme le hasattrfait, ce que l'OP était pour une raison quelconque contre.
Santa
3
Que faire si la fonction n'est pas dans cette classe, mais sur la classe parente ?. Dans ce cas, j'obtiens un vrai, même si les enfants n'implémentent jamais cette fonction (en utilisant hasattr)
darkgaze
46

Cela fonctionne à la fois en Python 2 et Python 3

hasattr(connection, 'invert_opt')

hasattrretourne Truesi l'objet de connexion a une fonction invert_optdéfinie. Voici la documentation pour vous faire paître

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

Antony
la source
5
Bien que le code soit apprécié, il doit toujours être accompagné d'une explication. Cela ne doit pas être long mais c'est attendu.
peterh - Réintégrer Monica le
bon, vous pouvez pointer vers un article même si cela ne ferait pas de mal :)
Vitaliy Terziev
5
Cela renvoie également True si la connexion a un attribut connection.invert_opt = 'foo'.
Robert Hönig
20

Existe-t-il un moyen plus rapide de vérifier si la fonction n'est pas définie que de détecter une exception?

Pourquoi êtes-vous contre ça? Dans la plupart des cas pythoniques, il vaut mieux demander pardon que la permission. ;-)

hasattr est implémenté en appelant getattr et en vérifiant s'il déclenche, ce qui n'est pas ce que je veux.

Encore une fois, pourquoi cela? Ce qui suit est assez pythonique:

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

Ou,

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

Notez, cependant, que cela getattr(obj, attr, default)est essentiellement implémenté en interceptant une exception. Il n'y a rien de mal à cela dans le pays Python!

Père Noël
la source
4

Les réponses ici vérifient si une chaîne est le nom d'un attribut de l'objet. Une étape supplémentaire (en utilisant callable) est nécessaire pour vérifier si l'attribut est une méthode.

Cela se résume donc à: quel est le moyen le plus rapide de vérifier si un objet obj a un attribut attrib. La réponse est

'attrib' in obj.__dict__

Il en est ainsi parce qu'un dict hache ses clés, donc la vérification de l'existence de la clé est rapide.

Voir les comparaisons de temps ci-dessous.

>>> 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
la source
Cela échoue sur les classes qui utilisent __slots__. __slots__Aide à accélérer l'accès aux attributs d'environ 10%. stackoverflow.com/a/14119024/1459669
noɥʇʎԀʎzɐɹƆ
3

J'aime la réponse de Nathan Ostgard et je l'ai votée à la hausse. Mais une autre façon de résoudre votre problème serait d'utiliser un décorateur de mémorisation, qui mettrait en cache le résultat de l'appel de fonction. Ainsi, vous pouvez aller de l'avant et avoir une fonction coûteuse qui comprend quelque chose, mais lorsque vous l'appelez encore et encore, les appels suivants sont rapides; la version mémorisée de la fonction recherche les arguments dans un dict, trouve le résultat dans le dict à partir du moment où la fonction réelle a calculé le résultat et renvoie le résultat immédiatement.

Voici une recette pour un décorateur mémo appelé "lru_cache" par Raymond Hettinger. Une version de ceci est désormais standard dans le module functools de 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
la source
2

Comme n'importe quoi en Python, si vous essayez assez fort, vous pouvez vous attaquer aux tripes et faire quelque chose de vraiment méchant. Maintenant, voici la partie la plus méchante:

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'

S'il vous plaît, faites-nous une faveur, continuez simplement à faire ce que vous avez dans votre question et ne l' utilisez jamais à moins que vous ne fassiez partie de l'équipe PyPy en train de pirater l'interpréteur Python. Ce que vous avez là-haut, c'est Pythonic, ce que j'ai ici est du mal pur .

YH Wong
la source
Cela sera vrai si la méthode lève une exception. Vous devriez également vérifier si co_namesest égal à ('NotImplementedError',). Cependant, je ne suis pas sûr que cela en fasse plus ou moins mal.
kindall
1

Vous pouvez également parcourir la classe:

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')
Martin Thoma
la source
0

Bien que la vérification des attributs dans la propriété __dict__ soit très rapide, vous ne pouvez pas l'utiliser pour les méthodes, car elles n'apparaissent pas dans le hachage __dict__. Vous pouvez cependant recourir à une solution de contournement piratée dans votre classe, si les performances sont si critiques:

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

    def custom_method(self):
        pass

Ensuite, vérifiez la méthode comme:

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

Comparaison du temps avec 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)

Non pas que j'encourage cette approche, mais cela semble fonctionner.

[EDIT] L'amélioration des performances est encore plus élevée lorsque le nom de la méthode n'est pas dans une classe donnée:

>>%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)
Jaroslav Loebl
la source
1
__dict__pourrait être annulée. On ne peut pas y faire confiance.
Xiao