2011-03-11 11 views
63

Estoy escribiendo un algoritmo de búsqueda de espacio de estado AI, y tengo una clase genérica que se puede usar para implementar rápidamente un algoritmo de búsqueda. Una subclase definiría las operaciones necesarias, y el algoritmo hará el resto.¿Cuál es la manera más rápida de verificar si una clase tiene una función definida?

Aquí es donde se queda bloqueado: Quiero evitar regenerar el estado de los padres una y otra vez, así que tengo la siguiente función, que devuelve las operaciones que se pueden aplicar legalmente a cualquier estado:

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 

Y la función invert_op arroja por defecto.

¿Hay una manera más rápida de verificar si la función no está definida que capturar una excepción?

Estaba pensando algo en las líneas de comprobar el presente en dir, pero eso no parece correcto. hasattr se implementa llamando a getattr y comprobando si aumenta, que no es lo que quiero.

+0

Algo suena rota, pero no puedo poner mi dedo en la llaga ... –

+6

* "es hasattr implementado llamando a getattr y comprobando si aumenta, que no es lo que quiero. "* ¿Por qué no? ¿Por qué te importa lo que hace la implementación? – detly

+3

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

Respuesta

106

Sí, utilizar getattr() para obtener el atributo y callable() para verificar que es un método:

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

Tenga en cuenta que normalmente getattr() tiros excepción cuando no existe el atributo. Sin embargo, si especifica un valor predeterminado (None, en este caso), lo devolverá en su lugar.

+2

Tenga en cuenta también que la implementación de 'getattr' en este caso captura una excepción silenciosamente y devuelve el valor predeterminado en su lugar, al igual que hace' hasattr', que el OP fue por alguna razón en contra. – Santa

+2

¿Qué sucede si la función no está en esa clase, sino en la clase principal ?. En este caso obtengo un True, incluso cuando los niños nunca implementan esa función (usando hasattr) – darkgaze

17

¿Hay una manera más rápida de verificar si la función no está definida que capturar una excepción?

¿Por qué estás en contra de eso? En la mayoría de los casos Pythonic, es mejor pedir perdón que permiso. ;-)

hasattr se implementa llamando a getattr y comprobando si aumenta, que no es lo que quiero.

De nuevo, ¿por qué es eso? La siguiente es bastante Pythonic:

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

O

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

Tenga en cuenta, sin embargo, que es básicamente getattr(obj, attr, default) implementado por la captura de una excepción, también. ¡No hay nada de malo en eso en Python!

2

Como cualquier cosa en Python, si lo intentas lo suficiente, puedes ponerte a prueba y hacer algo realmente desagradable. Ahora, aquí está la parte desagradable:

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' 

Por favor, háganos un favor, sigue haciendo lo que tiene en su pregunta y NO ha consumido alguna vez esto a menos que usted está en el equipo PyPy la piratería en el intérprete de Python . Lo que tienes arriba es Pythonic, lo que tengo aquí es puro EVIL.

+0

Esto será cierto si el método genera alguna excepción. También debe verificar si 'co_names' es igual a' ('NotImplementedError',) '. Sin embargo, no estoy seguro de si esto lo hace más o menos malo. – kindall

3

Me gusta la respuesta de Nathan Ostgard y la voté. Pero otra forma en que podría resolver su problema sería utilizar un decorador de memoria, que almacenaría en caché el resultado de la llamada a la función.De modo que puede seguir adelante y tener una función costosa que resuelva algo, pero luego cuando la repite una y otra vez, las llamadas siguientes son rápidas; la versión memorizada de la función busca los argumentos en un dict, encuentra el resultado en el dict de cuando la función real calculó el resultado, y devuelve el resultado de inmediato.

Aquí está una receta para un decorador de memorando llamado "lru_cache" por Raymond Hettinger. Una versión de esto ahora es estándar en el módulo functools en Python 3.2.

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

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

18

que funciona en ambos Python 2 y Python 3

hasattr(connection, 'invert_opt') 

hasattr rendimientos True si objeto de conexión tiene una función invert_opt definido. Aquí está la documentación para que pueda pastar

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

+3

Aunque se agradece el código, siempre debe tener una explicación adjunta. Esto no tiene por qué ser largo, pero se espera. – peterh

+0

bueno, puedes señalar un artículo aunque no estaría mal :) –

+0

Esto también devuelve Verdadero si la conexión tiene un atributo 'connection.invert_opt = 'foo''. –

3

Las respuestas en este documento comprobar si una cadena es el nombre de un atributo del objeto. Se necesita un paso adicional (usando invocable) para verificar si el atributo es un método.

Por lo tanto, se reduce a: ¿cuál es la forma más rápida de comprobar si un objeto obj tiene un atributo attrib. La respuesta es

'attrib' in obj.__dict__ 

Esto es así porque un dict hashes sus claves para verificar la existencia de la clave es rápido.

Ver comparaciones de tiempos a continuación.

>>> 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 
0

Mientras que el registro de atributos en la propiedad __dict__ es muy rápido, no se puede utilizar esto para los métodos, ya que no aparecen en __dict__ hash. sin embargo, puede recurrir a la solución hacker en su clase, si el rendimiento es tan crítica:

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

    def custom_method(self): 
     pass 

a continuación, Consultar método:

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

comparación del tiempo con 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) 
No

que estoy fomentando este enfoque, pero parece funcionar.

[EDIT] aumento de rendimiento es aún mayor cuando el nombre de método no está en la clase dada:

>>%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) 
Cuestiones relacionadas