Questionando verificação de tipo pythonic

5

Eu já vi inúmeras vezes a seguinte abordagem sugerida para "pegar uma coleção de objetos e fazer X se X for Y e ignorar o objeto de outra forma"

def quackAllDucks(ducks):
  for duck in ducks:
    try:
      duck.quack("QUACK")
    except AttributeError:
      #Not a duck, can't quack, don't worry about it
      pass

A implementação alternativa abaixo sempre recebe críticas pelo impacto no desempenho causado pela verificação de tipo

def quackAllDucks(ducks):
  for duck in ducks:
    if hasattr(duck,"quack"):
      duck.quack("QUACK")

No entanto, parece-me que em 99% dos cenários você gostaria de usar a segunda solução devido ao seguinte:

  • Se o usuário errar os parâmetros, eles não serão tratados como um pato e não haverá indicação. Muito tempo será desperdiçado depurando por que não há quacking acontecendo até que o usuário finalmente perceba seu erro bobo. A segunda solução lançaria um rastreio de pilha assim que o usuário tentasse fazer um charlatão.
  • Se o usuário tiver algum bug no método quack () que causa um AttributeError, esses bugs serão engolidos silenciosamente. Mais uma vez o tempo será desperdiçado procurando pelo bug quando a segunda solução simplesmente fornecer um rastreamento de pilha mostrando o problema imediato.

Na verdade, parece-me que a única vez que você iria querer usar o primeiro método é quando:

  • O bloco de código em questão está em uma seção extremamente crítica do desempenho do seu aplicativo. Seguindo o princípio de "evitar a otimização prematura", você só perceberia isso, obviamente, depois de ter implementado a abordagem mais segura e descoberto que era um gargalo.
  • Existem muitos tipos de objetos quacking e você só está interessado em fazer um quack nos objetos que usam um conjunto muito específico de argumentos (esse parece ser um caso muito raro para mim).

Dado isto, porque é que tantas pessoas preferem a primeira abordagem sobre a segunda abordagem? O que é que eu estou sentindo falta?

Além disso, percebo que existem outras soluções (como usar o abcs), mas essas são as duas soluções que pareço ver com mais frequência no caso básico.

    
por Pace 29.11.2012 / 15:23
fonte

2 respostas

6

O motivo pelo qual você deve usar a primeira versão é o desempenho. Aumentar uma exceção geralmente é mais barato do que chamar um método ou uma função.

O primeiro argumento que você apresenta para usar a segunda versão não é válido, já que usar if hasattr(duck, 'quack'): também evitaria a chamada silenciosa para duck.quack() e nenhuma exceção seria levantada.

Seu segundo argumento só se aplica porque sua implementação não está correta. Você deve fazê-lo da seguinte forma:

def quackAllDucks(ducks):
  for duck in ducks:
    try:
      duck.quack
    except AttributeError:
      #Not a duck, can't quack, don't worry about it
      continue
    duck.quack("QUACK")

Então qualquer exceção levantada dentro de duck.quack() seria propagada para o chamador de quackAllDucks() .

[EDITAR]

delnan levantou uma boa pergunta em seu comentário abaixo. Isso me levou a investigar mais, e a forma recomendada agora parece ser para usar hasattr() .

Mas a melhor solução é provavelmente projetar seu programa para que ele não dependa de tais testes. Como Steve Oualline disse: " Se não escrevermos código como este, então não precisamos nos preocupar tais perguntas " .

    
por 29.11.2012 / 18:24
fonte
0

O problema é que não há diferença entre os dois. duck.quack () chama __ getattr__ para procurar o atributo charlatão e tenta invocar o que volta. hasattr faz a mesma chamada de função __ getattr__ e retorna false se lançar uma exceção. Então, basicamente, a segunda versão é idêntica à primeira versão, exceto que você tem um duplo olhar para cima do atributo charlatão.

    
por 29.11.2012 / 20:09
fonte

Tags