Qual é a maneira mais rápida de verificar se uma classe tem uma função definida?


132

Estou escrevendo um algoritmo de pesquisa de espaço de estado da IA ​​e tenho uma classe genérica que pode ser usada para implementar rapidamente um algoritmo de pesquisa. Uma subclasse definiria as operações necessárias e o algoritmo fará o resto.

Aqui é onde eu fico preso: Eu quero evitar a regeneração do estado pai repetidamente, para que eu tenha a seguinte função, que retorna as operações que podem ser aplicadas legalmente em qualquer 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

E a função invert_op lança por padrão.

Existe uma maneira mais rápida de verificar se a função não está definida do que capturar uma exceção?

Eu estava pensando em algo nas linhas de verificação do presente no dir, mas isso não parece certo. O hasattr é implementado chamando getattr e verificando se aumenta, o que não é o que eu quero.


8
"hasattr é implementado chamando getattr e verificando se aumenta, o que não é o que eu quero." Por que não? Por que você se importa com o que a implementação faz?
detly

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

1
Tente: hasattr(connection, 'invert_opt').
kenorb

Respostas:


204

Sim, use getattr()para obter o atributo e callable()verificar se é um método:

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

Observe que getattr()normalmente lança uma exceção quando o atributo não existe. No entanto, se você especificar um valor padrão ( Nonenesse caso), ele retornará isso.


3
Observe também que a implementação de getattrneste caso captura uma exceção silenciosamente e retorna o valor padrão, como hasattrfaz, contra o qual o OP foi por algum motivo.
Santa

3
E se a função não estiver nessa classe, mas na classe pai? Neste caso, eu obter um verdadeiro, mesmo quando as crianças nunca implementar essa função (usando hasattr)
darkgaze

46

Funciona em Python 2 e Python 3

hasattr(connection, 'invert_opt')

hasattrretorna Truese o objeto de conexão tiver uma função invert_optdefinida. Aqui está a documentação para você pastar

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


5
Embora o código seja apreciado, ele deve sempre ter uma explicação que o acompanha. Isso não precisa ser longo, mas é esperado.
peterh - Restabelece Monica

bom, você pode apontar para um artigo que não faria mal :)
Vitaliy Terziev

5
Isso também retorna True se a conexão tiver um atributo connection.invert_opt = 'foo'.
Robert Hönig

20

Existe uma maneira mais rápida de verificar se a função não está definida do que capturar uma exceção?

Por que você é contra isso? Na maioria dos casos pitônicos, é melhor pedir perdão do que permissão. ;-)

O hasattr é implementado chamando getattr e verificando se aumenta, o que não é o que eu quero.

Novamente, por que isso? O seguinte é bastante pitônico:

    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)

Observe, no entanto, que isso getattr(obj, attr, default)é basicamente implementado capturando uma exceção também. Não há nada de errado nisso na terra dos Python!


4

As respostas aqui verificam se uma sequência é o nome de um atributo do objeto. Uma etapa extra (usando callable) é necessária para verificar se o atributo é um método.

Portanto, tudo se resume a: qual é a maneira mais rápida de verificar se um objeto obj possui um atributo attribute. A resposta é

'attrib' in obj.__dict__

Isso ocorre porque um ditado faz o hash de suas chaves, portanto, verificar a existência da chave é rápido.

Veja comparações de tempo abaixo.

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

Isso falha nas classes que usam __slots__. __slots__ajude a acelerar o acesso a atributos em ~ 10%. stackoverflow.com/a/14119024/1459669
noɥʇʎԀʎzɐɹƆ

3

Gosto da resposta de Nathan Ostgard e votei positivamente. Mas outra maneira de resolver seu problema seria usar um decorador de memorização, que armazenaria em cache o resultado da chamada de função. Portanto, você pode seguir em frente e ter uma função cara que descobre algo, mas quando você liga várias vezes, as chamadas subsequentes são rápidas; a versão memorizada da função procura os argumentos em um ditado, encontra o resultado no ditado a partir do momento em que a função real calculou o resultado e retorna o resultado imediatamente.

Aqui está uma receita para um decorador de memorização chamado "lru_cache", de Raymond Hettinger. Uma versão disso agora é padrão no módulo functools no 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

Como qualquer coisa em Python, se você se esforçar o suficiente, pode se esforçar e fazer algo realmente desagradável. Agora, aqui está a parte desagradável:

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, faça-nos um favor, continue fazendo o que você tem na sua pergunta e NÃO use isso a menos que esteja na equipe do PyPy invadindo o interpretador do Python. O que você tem lá em cima é pitônico, o que eu tenho aqui é puro MAL .


Isso será verdadeiro se o método gerar alguma exceção. Você também deve verificar se co_namesé igual a ('NotImplementedError',). Não tenho certeza se isso o torna mais ou menos mau, no entanto.
Kindall

1

Você também pode passar por cima da turma:

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

Embora a verificação de atributos na propriedade __dict__ seja realmente rápida, você não pode usá-lo para métodos, pois eles não aparecem no hash __dict__. Você pode, no entanto, recorrer a soluções alternativas hackeadas em sua classe, se o desempenho for crítico:

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

    def custom_method(self):
        pass

Em seguida, verifique o método como:

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

Comparação de tempo com 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)

Não que eu esteja encorajando essa abordagem, mas parece funcionar.

[EDIT] O aumento de desempenho é ainda maior quando o nome do método não está em uma determinada 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__pode ser substituído. Não pode ser confiável.
Xiao
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.