Prática recomendada para afirmação do Python

483
  1. Existe um problema de desempenho ou manutenção de código ao usar assertcomo parte do código padrão em vez de usá-lo apenas para fins de depuração?

    É

    assert x >= 0, 'x is less than zero'

    melhor ou pior que

    if x < 0:
        raise Exception, 'x is less than zero'
  2. Além disso, existe alguma maneira de definir uma regra de negócios como if x < 0 raise erroressa sempre verificada sem try/except/finallyisso, se a qualquer momento em todo o código xfor menor que 0 um erro for gerado, como se você definisse assert x < 0no início de uma função, em qualquer lugar da função onde xse torna menor que 0, uma exceção é gerada?

meade
fonte
29
Os parâmetros -O e -OO python removerão suas afirmações. Isso deve direcionar seu pensamento para o que é bom.
Peter Lada
4
O link de Thomasz Zielinski foi quebrado, agora é: mail.python.org/pipermail/python-list/2013-November/660568.html . Tenho certeza de que o pipermail tem uma função de ID instável, encontrei outros links de dentro do mesmo pipermail apontando para o mesmo URL com a mesma intenção.
quodlibetor
3
Caso o mail.python.org/pipermail/python-list/2013-November/660568.html se mova novamente, ele será arquivado em archive.is/5GfiG . O título da postagem é "Quando usar assert" e é uma excelente publicação (na verdade, um artigo) sobre as melhores práticas para Python assert.
Clacke

Respostas:

144

Ser capaz de gerar um erro automaticamente quando x se tornar menor que zero em toda a função. Você pode usar descritores de classe . Aqui está um exemplo:

class LessThanZeroException(Exception):
    pass

class variable(object):
    def __init__(self, value=0):
        self.__x = value

    def __set__(self, obj, value):
        if value < 0:
            raise LessThanZeroException('x is less than zero')

        self.__x  = value

    def __get__(self, obj, objType):
        return self.__x

class MyClass(object):
    x = variable()

>>> m = MyClass()
>>> m.x = 10
>>> m.x -= 20
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "my.py", line 7, in __set__
    raise LessThanZeroException('x is less than zero')
LessThanZeroException: x is less than zero
Nadia Alramli
fonte
10
Embora as propriedades sejam implementadas como descritores, eu não chamaria isso de exemplo. Este é mais um exemplo de propriedades em si mesmos: docs.python.org/library/functions.html#property
Jason Baker
3
As propriedades devem ser usadas no MyClass ao definir x. Esta solução é muito geral.
113
Resposta bastante agradável, como ela, mas não tem nada a ver com a pergunta ... Não podemos marcar a resposta de Deestan ou John Mee como a resposta válida?
Vajk Hermecz 08/08/13
4
Isso não parece responder ao título da pergunta. Além disso, esta é uma péssima alternativa ao recurso de propriedade de classe do Python.
Dooms101
10
@VajkHermecz: Na verdade, se você reler a pergunta, são duas perguntas em uma. As pessoas que olham apenas para o título estão familiarizadas apenas com a primeira pergunta, que esta resposta não responde. Esta resposta realmente contém uma resposta para a segunda pergunta.
ArtOfWarfare
742

As declarações devem ser usadas para testar condições que nunca devem acontecer . O objetivo é travar no início no caso de um estado de programa corrompido.

Exceções devem ser usadas para erros que possam acontecer, e você quase sempre deve criar suas próprias classes de exceção .


Por exemplo, se você estiver escrevendo uma função para ler de um arquivo de configuração em um arquivo dict, a formatação incorreta no arquivo deve gerar a ConfigurationSyntaxError, enquanto você pode assertnão estar prestes a retornar None.


No seu exemplo, se xfor um valor definido por meio de uma interface com o usuário ou de uma fonte externa, uma exceção é a melhor.

Se xfor definido apenas pelo seu próprio código no mesmo programa, vá com uma asserção.

Deestan
fonte
126
Esta é a maneira correta de usar afirmações. Eles não devem ser usados ​​para controlar o fluxo do programa.
precisa
41
+1 para o último parágrafo - embora você deve explicitamente mencionar que assertcontém uma implícita if __debug__e pode ser otimizada de distância - como resposta de John Mee estados
Tobias KIENZLER
3
Relendo sua resposta, acho que você provavelmente não quis dizer condições que nunca deveriam ser entendidas como regra, mas o objetivo é travar cedo no caso de um estado de programa corrompido que geralmente coincide com uma condição que você não espera. para acontecer .
Bentley4
10
afirmar apenas deve ser usado para detectar problemas sem recuperação conhecida; quase sempre codifica bugs (não entradas ruins). quando uma afirmação é acionada, isso significa que o programa está em um estado perigoso para continuar, pois pode começar a conversar com a rede ou gravar em disco. código robusto move 'atomicamente' de um estado válido para um estado válido em face de entrada incorreta (ou maliciosa). o nível superior de cada thread deve ter uma barreira de falha. as barreiras de falha que consomem entrada do mundo externo geralmente falham por apenas uma iteração da barreira (while / try), erro de reversão / logon.
Rob
10
"As afirmações devem ser usadas para testar condições que nunca devem acontecer." Sim. E o significado do segundo "deveria" é: Se isso acontecer, o código do programa está incorreto.
Lutz Prechelt
362

As instruções "assert" são removidas quando a compilação é otimizada . Então, sim, existem diferenças de desempenho e funcionais.

O gerador de código atual não emite nenhum código para uma declaração assert quando a otimização é solicitada no tempo de compilação. - Documentos do Python 2 Documentos do Python 3

Se você costuma assertimplementar a funcionalidade do aplicativo e otimizar a implantação na produção, será flagelado por defeitos "mas-funciona-no-dev".

Veja PITONOTIMIZAR e -O -OO

John Mee
fonte
26
Uau! Nota super importante que é! Eu estava pensando em usar declarações para verificar algumas coisas que nunca deveriam falhar, cuja falha indicaria que alguém estava manipulando meus dados com muito cuidado, na tentativa de obter acesso a dados aos quais não deveria ter acesso. Não funcionaria, mas quero interromper rapidamente a tentativa com uma afirmação, para que a otimização da produção acabe com o objetivo. Eu acho que vou apenas raiseum em Exceptionvez disso. Oh - Acabei de descobrir um nome apropriado SuspiciousOperation Exceptioncom subclasses em Django! Perfeito!
ArtOfWarfare
A propósito, @ArtOfWarfare, se você executar o banditseu código, ele avisará disso.
Nagev 27/02
132

Os quatro propósitos de assert

Suponha que você trabalhe em 200.000 linhas de código com quatro colegas Alice, Bernd, Carl e Daphne. Eles chamam seu código, você chama o código deles.

Depois, asserttem quatro funções :

  1. Informe Alice, Bernd, Carl e Daphne o que seu código espera.
    Suponha que você tenha um método que processa uma lista de tuplas e a lógica do programa pode ser quebrada se essas tuplas não forem imutáveis:

    def mymethod(listOfTuples):
        assert(all(type(tp)==tuple for tp in listOfTuples))

    Isso é mais confiável do que informações equivalentes na documentação e muito mais fácil de manter.

  2. Informe ao computador o que seu código espera.
    assertimpõe o comportamento adequado dos chamadores do seu código. Se o seu código ligar para o código de Alices e Bernd, ligar para o seu, então sem o assert, se o programa travar no código de Alices, Bernd pode assumir que foi culpa de Alice, Alice investiga e pode assumir que foi sua culpa, você investiga e diz a Bernd que era de fato seu. Muito trabalho perdido.
    Com afirmações, quem quer que receba uma ligação errada, será capaz de perceber rapidamente que a culpa foi deles, não sua. Alice, Bernd, e todos vocês se beneficiam. Economiza imenso tempo.

  3. Informe os leitores do seu código (incluindo você mesmo) o que seu código alcançou em algum momento.
    Suponha que você tenha uma lista de entradas e cada uma delas possa estar limpa (o que é bom) ou pode ser smorsh, trale, gullup ou twinkled (que não são aceitáveis). Se for torcido, deve ser firme; se for verdade, deve ser ignorado; se for gullup, deve ser trotado (e possivelmente passeado também); se estiver brilhando, deve voltar a brilhar, exceto às quintas-feiras. Você entende: são coisas complicadas. Mas o resultado final é (ou deveria ser) que todas as entradas estejam limpas. A coisa certa a fazer é resumir o efeito do seu ciclo de limpeza como

    assert(all(entry.isClean() for entry in mylist))

    Essas declarações evitam uma dor de cabeça para todos que tentam entender exatamente o que o ciclo maravilhoso está alcançando. E a mais frequente dessas pessoas provavelmente será você mesma.

  4. Informe ao computador o que seu código alcançou em algum momento.
    Se você esquecer um ritmo de uma entrada que precisa dela após trotar, assertisso salvará seu dia e evitará que seu código quebre o querido Daphne muito mais tarde.

Na minha opinião, assertos dois propósitos da documentação (1 e 3) e da proteção (2 e 4) são igualmente valiosos.
Informar as pessoas pode até ser mais valioso do que informar o computador, pois pode evitar os mesmos erros que os assertobjetivos visam detectar (no caso 1) e muitos erros subsequentes em qualquer caso.

Lutz Prechelt
fonte
34
5. assert isinstance () ajuda o PyCharm (python IDE) a conhecer o tipo de variável, é usado para o preenchimento automático.
Cjkjvfnby
1
Assume suposições de código de auto-documento para o que é verdadeiro no tempo de execução atual. É um comentário de suposição, que é verificado.
Pyj # 19/14
9
Em relação a 2 e 4: você deve ter muito cuidado para que suas afirmações não sejam muito rigorosas. Caso contrário, as afirmações em si podem ser a única coisa que mantém seu programa para ser usado em uma configuração mais geral. Especialmente afirmar tipos vai contra a digitação de pato do python.
zwirbeltier
9
@Cjkjvfnby Tenha cuidado com o uso excessivo de isinstance () conforme descrito nesta entrada do blog: " isinstance () considerado prejudicial ". Agora você pode usar documentos para especificar tipos no Pycharm.
precisa saber é o seguinte
2
Usando afirmações de uma maneira de garantir contrato. Mais informações sobre Design by Contract pt.wikipedia.org/wiki/Design_by_contract
Leszek Zarna
22

Além das outras respostas, as próprias declarações lançam exceções, mas apenas AssertionErrors. Do ponto de vista utilitarista, as asserções não são adequadas para quando você precisa de um controle fino sobre quais exceções você captura.

fora
fonte
3
Direita. Parece bobagem capturar exceções de erro de declaração no chamador.
Raffi Khatchadourian 13/10
Muito bom ponto. Uma nuance que pode ser facilmente ignorada quando se olha apenas as perguntas originais do nível macro. Mesmo que não fosse o problema com afirmações sendo descartadas durante a otimização, perder os detalhes específicos de que tipo de erro ocorreu tornaria a depuração muito mais desafiadora. Saúde, outis!
cfwschmidt 26/09
Sua resposta pode ser lida como se você quisesse entender AssertionErrors, quando você estiver bem com a granulação grossa. Na realidade, você não deveria estar pegando eles.
Tomasz Gandor
19

A única coisa realmente errada com essa abordagem é que é difícil criar uma exceção muito descritiva usando declarações assert. Se você está procurando a sintaxe mais simples, lembre-se de que também pode fazer algo assim:

class XLessThanZeroException(Exception):
    pass

def CheckX(x):
    if x < 0:
        raise XLessThanZeroException()

def foo(x):
    CheckX(x)
    #do stuff here

Outro problema é que o uso de assert para verificação de condição normal é dificultar a desabilitação das declarações de depuração usando o sinalizador -O.

Jason Baker
fonte
24
Você pode anexar uma mensagem de erro a uma afirmação. É o segundo parâmetro. Isso tornará descritivo.
Raffi Khatchadourian 13/10
10

A palavra em inglês aqui afirmada é usada no sentido de jurar , afirmar , declarar . Não significa "cheque" ou "deveria ser" . Isso significa que você, como codificador, está fazendo uma declaração juramentada aqui:

# I solemnly swear that here I will tell the truth, the whole truth, 
# and nothing but the truth, under pains and penalties of perjury, so help me FSM
assert answer == 42

Se o código estiver correto, exceto distúrbios de evento único , falhas de hardware e outras, nenhuma declaração jamais falhará . É por isso que o comportamento do programa para um usuário final não deve ser afetado. Especialmente, uma afirmação não pode falhar, mesmo sob condições programáticas excepcionais . Isso simplesmente nunca acontece. Se isso acontecer, o programador deve ser acionado.

Antti Haapala
fonte
8

Como já foi dito anteriormente, asserções devem ser usadas quando seu código NÃO deve chegar a um ponto, o que significa que existe um erro. Provavelmente, a razão mais útil que vejo para usar uma asserção é uma invariável / pré / pós-condição. Isso é algo que deve ser verdadeiro no início ou no final de cada iteração de um loop ou de uma função.

Por exemplo, uma função recursiva (2 funções separadas para que 1 lide com entrada incorreta e a outra lida com código incorreto, pois é difícil distinguir com recursão). Isso tornaria óbvio se eu esquecesse de escrever a declaração if, o que havia dado errado.

def SumToN(n):
    if n <= 0:
        raise ValueError, "N must be greater than or equal to 0"
    else:
        return RecursiveSum(n)

def RecursiveSum(n):
    #precondition: n >= 0
    assert(n >= 0)
    if n == 0:
        return 0
    return RecursiveSum(n - 1) + n
    #postcondition: returned sum of 1 to n

Esses invariantes de loop geralmente podem ser representados com uma asserção.

matts1
fonte
2
Este é o melhor feito com decoradores (@precondition e @postcondition)
Caridorc
@Caridorc, qual é o benefício concreto disso?
Chiel ten Brinke 06/02
@ChieltenBrinke código de auto documentar, em vez de #precondition: n >= 0 e uma declaração, ele pode apenas escrever@precondition(lambda n: n >= 0)
Caridorc
@Caridorc São esses decoradores embutidos então? E como se gera documentação a partir disso?
Chiel ten Brinke 06/02
@ChieltenBrinke não embutido, mas fácil de implementar stackoverflow.com/questions/12151182/… . Para documentação apenas corrigir o __doc__atributo dando uma cadeia adicional
Caridorc
4

Existe um problema de desempenho?

  • Lembre-se de "fazê-lo funcionar primeiro antes de fazê-lo funcionar rapidamente" .
    Muito poucos por cento de qualquer programa geralmente são relevantes para sua velocidade. Você sempre pode lançar ou simplificar um assertcaso se mostre um problema de desempenho - e a maioria deles nunca será.

  • Seja pragmático :
    suponha que você tenha um método que processe uma lista não vazia de tuplas e a lógica do programa será interrompida se essas tuplas não forem imutáveis. Você deve escrever:

    def mymethod(listOfTuples):
        assert(all(type(tp)==tuple for tp in listOfTuples))

    Provavelmente, isso é bom se suas listas tendem a ter dez entradas, mas pode se tornar um problema se tiverem um milhão de entradas. Mas, em vez de descartar completamente esse valioso cheque, você pode simplesmente fazer o downgrade para

    def mymethod(listOfTuples):
        assert(type(listOfTuples[0])==tuple)  # in fact _all_ must be tuples!

    que é barato, mas provavelmente captura a maioria dos erros reais do programa.

Lutz Prechelt
fonte
2
Deveria ser assert(len(listOfTuples)==0 or type(listOfTyples[0])==tuple).
Osa
Não, não deveria. Esse seria um teste muito mais fraco, porque não verifica mais a propriedade 'não vazia', que o segundo afirma verificar. (O primeiro não, embora deveria.)
Lutz Prechelt
1
A segunda afirmação não verifica explicitamente a propriedade não vazia; é mais um efeito colateral. Se fosse para gerar uma exceção devido à lista estar vazia, a pessoa que trabalha com o código (outra pessoa ou o autor, um ano depois de escrevê-lo) ficava olhando para ele, tentando descobrir se a declaração realmente deveria a situação da lista vazia ou se isso é um erro na própria declaração. Além disso, não vejo como a verificação do caso vazio é "muito mais fraca", enquanto apenas a verificação do primeiro elemento é "97% correta".
Osa
3

Bem, essa é uma pergunta em aberto e tenho dois aspectos nos quais quero abordar: quando adicionar asserções e como escrever as mensagens de erro.

Objetivo

Para explicar isso para um iniciante - asserções são declarações que podem gerar erros, mas você não as pegará. E eles normalmente não devem ser criados, mas na vida real às vezes são criados de qualquer maneira. E essa é uma situação séria, da qual o código não pode se recuperar, do que chamamos de 'erro fatal'.

Em seguida, é para 'fins de depuração', que, embora corretos, parece muito desdenhoso. Gosto mais da formulação 'declarar invariantes, que nunca deve ser violada', embora funcione de maneira diferente em iniciantes diferentes ... Alguns 'apenas entendem', e outros não encontram utilidade ou substituem exceções normais, ou mesmo controlar o fluxo com ele.

Estilo

Em Python, asserté uma declaração, não uma função! (lembre- assert(False, 'is true')se que não aumentará. Mas, tendo isso fora do caminho:

Quando e como escrever a 'mensagem de erro' opcional?

Isso se aplica de fato a estruturas de teste de unidade, que geralmente têm muitos métodos dedicados para fazer asserções ( assertTrue(condition), assertFalse(condition), assertEqual(actual, expected)etc.). Eles geralmente também fornecem uma maneira de comentar a afirmação.

No código descartável, você pode ficar sem as mensagens de erro.

Em alguns casos, não há nada a acrescentar à asserção:

def dump (algo): assert isinstance (algo, Dumpable) # ...

Além disso, uma mensagem é útil para comunicação com outros programadores (que às vezes são usuários interativos do seu código, por exemplo, no Ipython / Jupyter etc.).

Forneça informações, não apenas vaze detalhes de implementação interna.

ao invés de:

assert meaningless_identifier <= MAGIC_NUMBER_XXX, 'meaningless_identifier is greater than MAGIC_NUMBER_XXX!!!'

Escreva:

assert meaningless_identifier > MAGIC_NUMBER_XXX, 'reactor temperature above critical threshold'

ou talvez até:

assert meaningless_identifier > MAGIC_NUMBER_XXX, f'reactor temperature({meaningless_identifier }) above critical threshold ({MAGIC_NUMBER_XXX})'

Eu sei, eu sei - esse não é um caso de afirmação estática, mas quero apontar para o valor informativo da mensagem.

Mensagem negativa ou positiva?

Isso pode ser absurdo, mas me dói ler coisas como:

assert a == b, 'a is not equal to b'
  • estas são duas coisas contraditórias escritas próximas uma da outra. Portanto, sempre que influencio a base de código, insisto em especificar o que queremos, usando verbos extras como 'must' e 'should', sem dizer o que não queremos.

    afirme a == b, 'a deve ser igual a b'

Então, get AssertionError: a must be equal to btambém é legível e a instrução parece lógica no código. Além disso, você pode obter algo sem ler o traceback (que às vezes nem pode estar disponível).

Tomasz Gandor
fonte
1

Tanto o uso assertcomo o levantamento de exceções são sobre comunicação.

  • Asserções são declarações sobre a exatidão do código endereçado aos desenvolvedores : Uma asserção no código informa os leitores do código sobre as condições que devem ser cumpridas para que o código esteja correto. Uma asserção que falha no tempo de execução informa aos desenvolvedores que há um defeito no código que precisa ser corrigido.

  • Exceções são indicações sobre situações não típicas que podem ocorrer em tempo de execução, mas não podem ser resolvidas pelo código em questão, endereçado no código de chamada a ser tratado lá. A ocorrência de uma exceção não indica que há um erro no código.

Melhor prática

Portanto, se você considerar a ocorrência de uma situação específica em tempo de execução como um bug sobre o qual gostaria de informar os desenvolvedores ("Olá desenvolvedor, essa condição indica que há um bug em algum lugar, corrija o código"). vá para uma afirmação. Se a asserção verificar os argumentos de entrada do seu código, você deverá adicionar à documentação que seu código tem "comportamento indefinido" quando os argumentos de entrada violarem essas condições.

Se, em vez disso, a ocorrência dessa mesma situação não é uma indicação de um erro em seus olhos, mas uma situação (talvez rara, mas possível) que você acha que deveria ser tratada pelo código do cliente, crie uma exceção. As situações em que a exceção é gerada devem fazer parte da documentação do respectivo código.

Existe um problema de [...] desempenho ao usar assert

A avaliação das afirmações leva algum tempo. Eles podem ser eliminados no momento da compilação. Isso tem algumas consequências, no entanto, veja abaixo.

Existe um problema de [...] manutenção de código ao usar assert

Normalmente, as asserções melhoram a capacidade de manutenção do código, pois melhoram a legibilidade, tornando explícitas as suposições e durante o tempo de execução, verificando regularmente essas suposições. Isso também ajudará a capturar regressões. Há um problema, no entanto, que precisa ser lembrado: as expressões usadas nas asserções não devem ter efeitos colaterais. Como mencionado acima, as asserções podem ser eliminadas em tempo de compilação - o que significa que também os possíveis efeitos colaterais desapareceriam. Isso pode - sem querer - alterar o comportamento do código.

Dirk Herrmann
fonte
1

Uma afirmação é verificar:
1. a condição válida,
2. a declaração válida,
3. verdadeira lógica;
do código fonte. Em vez de falhar em todo o projeto, é emitido um alarme de que algo não é apropriado no seu arquivo de origem.

No exemplo 1, como a variável 'str' não é nula. Portanto, nenhuma afirmação ou exceção é levantada.

Exemplo 1:

#!/usr/bin/python

str = 'hello Python!'
strNull = 'string is Null'

if __debug__:
    if not str: raise AssertionError(strNull)
print str

if __debug__:
    print 'FileName '.ljust(30,'.'),(__name__)
    print 'FilePath '.ljust(30,'.'),(__file__)


------------------------------------------------------

Output:
hello Python!
FileName ..................... hello
FilePath ..................... C:/Python\hello.py

No exemplo 2, var 'str' é nulo. Portanto, estamos a poupar o usuário de ir à frente do programa defeituoso por assert comunicado.

Exemplo 2:

#!/usr/bin/python

str = ''
strNull = 'NULL String'

if __debug__:
    if not str: raise AssertionError(strNull)
print str

if __debug__:
    print 'FileName '.ljust(30,'.'),(__name__)
    print 'FilePath '.ljust(30,'.'),(__file__)


------------------------------------------------------

Output:
AssertionError: NULL String

No momento em que não queremos depuração, percebemos o problema de afirmação no código-fonte. Desativar o sinalizador de otimização

python -O assertStatement.py
nada será impresso

akD
fonte
0

Em IDEs como PTVS, PyCharm, as assert isinstance()instruções Wing podem ser usadas para permitir a conclusão do código para alguns objetos pouco claros.

denfromufa
fonte
Isso parece anteceder o uso de anotações de tipo ou de typing.cast.
Acumenus 19/02
-1

Pelo que vale a pena, se você estiver lidando com um código que depende assertpara funcionar corretamente, a adição do código a seguir garantirá que as declarações sejam ativadas:

try:
    assert False
    raise Exception('Python assertions are not working. This tool relies on Python assertions to do its job. Possible causes are running with the "-O" flag or running a precompiled (".pyo" or ".pyc") module.')
except AssertionError:
    pass
Emilio M Bumachar
fonte
2
Isso não responde à pergunta do OP, que trata das melhores práticas.
codeforester