Qual è il modo più veloce per verificare se una classe ha una funzione definita?


132

Sto scrivendo un algoritmo di ricerca nello spazio di stato AI e ho una classe generica che può essere utilizzata per implementare rapidamente un algoritmo di ricerca. Una sottoclasse definirebbe le operazioni necessarie e l'algoritmo farà il resto.

Qui è dove rimango bloccato: voglio evitare di rigenerare lo stato principale più e più volte, quindi ho la seguente funzione, che restituisce le operazioni che possono essere legalmente applicate a qualsiasi stato:

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

E la funzione invert_op viene generata per impostazione predefinita.

Esiste un modo più rapido per verificare se la funzione non è definita che rilevare un'eccezione?

Stavo pensando a qualcosa sulla falsariga di controllare il presente in dir, ma questo non sembra giusto. hasattr viene implementato chiamando getattr e verificando se genera, che non è quello che voglio.


8
"hasattr viene implementato chiamando getattr e verificando se genera, che non è quello che voglio". Perchè no? Perché ti interessa cosa fa l'implementazione?
detenere l'

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

1
Prova: hasattr(connection, 'invert_opt').
Kenorb,

Risposte:


205

Sì, utilizzare getattr()per ottenere l'attributo e callable()per verificare che sia un metodo:

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

Si noti che getattr()normalmente genera un'eccezione quando l'attributo non esiste. Tuttavia, se si specifica un valore predefinito ( None, in questo caso), verrà restituito tale valore.


3
Si noti inoltre che l'implementazione di getattrin questo caso rileva un'eccezione in modo silenzioso e restituisce invece il valore predefinito, proprio come hasattrfa, contro cui l'OP era per qualche motivo.
Santa

3
Cosa succede se la funzione non è in quella classe, ma nella classe genitore ?. In questo caso ottengo un True, anche quando i bambini non implementano mai quella funzione (usando hasattr)
darkgaze

46

Funziona sia in Python 2 che in Python 3

hasattr(connection, 'invert_opt')

hasattrritorna Truese l'oggetto connessione ha una funzione invert_optdefinita. Ecco la documentazione per farti pascolare

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


5
Sebbene il codice sia apprezzato, dovrebbe sempre avere una spiegazione di accompagnamento. Questo non deve essere lungo ma è previsto.
Peter - Ripristina Monica il

buono, puoi indicare un articolo anche se non farebbe male :)
Vitaliy Terziev

5
Ciò restituisce anche True se la connessione ha un attributo connection.invert_opt = 'foo'.
Robert Hönig,

20

Esiste un modo più rapido per verificare se la funzione non è definita che rilevare un'eccezione?

Perché sei contrario? Nella maggior parte dei casi Pythonic, è meglio chiedere perdono che permesso. ;-)

hasattr viene implementato chiamando getattr e verificando se genera, che non è quello che voglio.

Ancora una volta, perché? Quanto segue è abbastanza 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)

Si noti, tuttavia, che getattr(obj, attr, default)viene sostanzialmente implementato anche rilevando un'eccezione. Non c'è niente di sbagliato in questo in Python land!


4

Le risposte nel presente documento verificano se una stringa è il nome di un attributo dell'oggetto. È necessario un passaggio aggiuntivo (utilizzando callable) per verificare se l'attributo è un metodo.

Quindi si riduce a: qual è il modo più veloce per verificare se un oggetto obj ha un attributo attrib. La risposta è

'attrib' in obj.__dict__

Questo perché un dict esegue l'hashing delle chiavi in ​​modo da verificare rapidamente l'esistenza della chiave.

Vedi i confronti temporali di seguito.

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

Questo non riesce sulle classi che usano __slots__. __slots__contribuire ad accelerare l'accesso agli attributi del ~ 10%. stackoverflow.com/a/14119024/1459669
noɥʇʎԀʎzɐɹƆ

3

Mi piace la risposta di Nathan Ostgard e ho votato a favore. Ma un altro modo in cui potresti risolvere il tuo problema sarebbe usare un decoratore di memoizing, che memorizzerebbe nella cache il risultato della chiamata di funzione. Quindi puoi andare avanti e avere una funzione costosa che capisce qualcosa, ma poi quando lo chiami più e più volte le chiamate successive sono veloci; la versione memorizzata della funzione cerca gli argomenti in un dict, trova il risultato nel dict da quando la funzione effettiva ha calcolato il risultato e restituisce immediatamente il risultato.

Ecco una ricetta per un decoratore memorabile chiamato "lru_cache" di Raymond Hettinger. Una versione di questo è ora standard nel modulo functools in 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

Come qualsiasi cosa in Python, se ci provi abbastanza, puoi ottenere il coraggio e fare qualcosa di veramente brutto. Ora, ecco la parte cattiva:

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'

Per favore, facci un favore, continua a fare quello che hai nella tua domanda e NON usarlo mai a meno che tu non faccia parte del team di PyPy mentre si intromette nell'interprete Python. Quello che hai lassù è Pythonic, quello che ho qui è puro MALE .


Questo sarà vero se il metodo solleva qualche eccezione. Dovresti anche controllare per vedere se co_namesè uguale a ('NotImplementedError',). Non sono sicuro se questo lo renda più o meno malvagio, comunque.
kindall

1

Puoi anche andare oltre la lezione:

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

Mentre il controllo degli attributi nella proprietà __dict__ è molto veloce, non è possibile utilizzarlo per i metodi, poiché non vengono visualizzati nell'hash __dict__. Tuttavia, potresti ricorrere a soluzioni alternative di hacking nella tua classe, se le prestazioni sono così importanti:

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

    def custom_method(self):
        pass

Quindi controlla il metodo come:

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

Confronto temporale 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)

Non che sto incoraggiando questo approccio, ma sembra funzionare.

[EDIT] L'aumento delle prestazioni è ancora maggiore quando il nome del metodo non appartiene a una determinata classe:

>>%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__potrebbe essere ignorato. Non ci si può fidare.
Xiao
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.