Le migliori pratiche per affermare Python


483
  1. Esiste un problema di prestazioni o di manutenzione del codice con l'utilizzo assertcome parte del codice standard anziché utilizzarlo solo per scopi di debug?

    È

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

    meglio o peggio di

    if x < 0:
        raise Exception, 'x is less than zero'
  2. Inoltre, esiste un modo per impostare una regola aziendale come if x < 0 raise errorquella che viene sempre verificata senza try/except/finallytale, se in qualsiasi momento nel codice xè inferiore a 0 viene generato un errore, come se si imposta assert x < 0all'inizio di una funzione, in qualsiasi punto della funzione dove xdiventa meno di 0 viene sollevata un'eccezione?



29
I parametri Python -O e -OO elimineranno le tue asserzioni. Ciò dovrebbe guidare il tuo pensiero su ciò che è buono per.
Peter Lada,

4
Il link di Thomasz Zielinski si è rotto, ora è: mail.python.org/pipermail/python-list/2013-November/660568.html . Sono abbastanza sicuro che pipermail abbia una funzione ID instabile, ho trovato altri collegamenti all'interno dello stesso pipermail che puntano allo stesso url con la stessa intenzione.
quodlibetor,

3
Nel caso in cui mail.python.org/pipermail/python-list/2013-November/660568.html si sposta di nuovo, viene archiviato su archive.is/5GfiG . Il titolo del post è "Quando usare assert" ed è un eccellente post (un articolo davvero) sulle migliori pratiche per Python assert.
clacke,

Risposte:


144

Essere in grado di generare automaticamente un errore quando x diventa inferiore a zero durante la funzione. È possibile utilizzare i descrittori di classe . Ecco un esempio:

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

10
Sebbene le proprietà siano implementate come descrittori, non lo definirei un esempio di utilizzo. Questo è più un esempio di proprietà in sé e per sé: docs.python.org/library/functions.html#property
Jason Baker,

3
Le proprietà devono essere utilizzate in MyClass quando si imposta x. Questa soluzione è troppo generale.

113
Bella risposta, piace, ma non ha nulla a che fare con la domanda ... Non possiamo contrassegnare la risposta di Deestan o John Mee come la risposta valida?
Vajk Hermecz,

4
Questo non sembra rispondere al titolo della domanda. Inoltre, questa è una pessima alternativa alla funzione di proprietà di classe di Python.
Dooms101

10
@VajkHermecz: In realtà, se rileggi la domanda, si tratta di due domande in una. Le persone che guardano solo il titolo hanno familiarità solo con la prima domanda, a cui questa risposta non risponde. Questa risposta contiene effettivamente una risposta alla seconda domanda.
ArtOfWarfare il

742

Gli assert dovrebbero essere usati per testare condizioni che non dovrebbero mai accadere . Lo scopo è di arrestarsi in anticipo nel caso di uno stato di programma corrotto.

Le eccezioni dovrebbero essere usate per errori che possono verosimilmente verificarsi e dovresti quasi sempre creare le tue classi di eccezioni .


Ad esempio, se stai scrivendo una funzione da leggere da un file di configurazione in un dict, una formattazione impropria nel file dovrebbe sollevare un ConfigurationSyntaxError, mentre puoi assertche non stai per tornare None.


Nel tuo esempio, se xè un valore impostato tramite un'interfaccia utente o da una fonte esterna, un'eccezione è la migliore.

Se xè impostato solo dal tuo codice nello stesso programma, procedi con un'asserzione.


126
Questo è il modo giusto di usare assert. Non dovrebbero essere usati per controllare il flusso del programma.
Thane Brimhall,

41
+1 per l'ultimo paragrafo - anche se dovresti menzionare esplicitamente che assertcontiene un implicito if __debug__e può essere ottimizzato via - come afferma la risposta di John Mee
Tobias Kienzler

3
Rileggere la tua risposta Penso che probabilmente non intendevi condizioni che non dovrebbero mai essere intese come regola, ma piuttosto lo scopo è quello di arrestarsi in anticipo nel caso di uno stato di programma corrotto che di solito coincide con una condizione che non ti aspetti mai accadere .
Bentley4,

10
assert dovrebbe essere usato solo per rilevare problemi senza recupero noto; quasi sempre bug del codice (non input errati). quando viene attivata un'asserzione, ciò dovrebbe significare che il programma è in uno stato in cui può essere pericoloso continuare, poiché potrebbe iniziare a parlare con la rete o a scrivere su disco. il codice robusto si sposta "atomicamente" da uno stato valido a uno stato valido di fronte a input errati (o dannosi). il livello superiore di ogni thread dovrebbe avere una barriera di errore. le barriere di errore che consumano input dal mondo esterno generalmente falliscono per una sola iterazione della barriera (while / try), errore di rollback / accesso.
Rob,

10
"Gli assertori dovrebbero essere usati per testare condizioni che non dovrebbero mai accadere." Sì. E il significato del secondo "dovrebbe" è: se ciò accade, il codice del programma non è corretto.
Lutz Prechelt,

362

Le istruzioni "assert" vengono rimosse quando la compilazione è ottimizzata . Quindi sì, ci sono differenze sia in termini di prestazioni che funzionali.

Il generatore di codice corrente non emette codice per un'istruzione assert quando l'ottimizzazione è richiesta al momento della compilazione. - Python 2 Docs Python 3 Docs

Se si utilizza assertper implementare la funzionalità dell'applicazione, quindi ottimizzare l'implementazione alla produzione, si verrà colpiti da difetti "ma funziona" in-dev ".

Vedi PYTHONOPTIMIZE e -O -OO


26
Wow! Nota super importante che è! Avevo programmato di utilizzare assert per verificare alcune cose che non avrebbero mai dovuto fallire, il cui fallimento avrebbe indicato che qualcuno stava manipolando con molta cura i miei dati che stavano inviando nel tentativo di ottenere l'accesso a dati a cui non avrebbero avuto accesso. Non funzionerebbe, ma voglio fermare rapidamente il loro tentativo con un'affermazione, quindi avere quell'ottimizzato via nella produzione vanificherebbe lo scopo. Immagino che ne prenderò solo raiseuno Exception. Oh, ho appena scoperto un nome appropriatamente SuspiciousOperation Exceptioncon sottoclassi in Django! Perfetto!
ArtOfWarfare dal

A proposito di @ArtOfWarfare se esegui banditil tuo codice, ti avviserà di questo.
Nagev,

132

I quattro scopi di assert

Supponi di lavorare su 200.000 righe di codice con quattro colleghi Alice, Bernd, Carl e Daphne. Chiamano il tuo codice, tu chiami il loro codice.

Quindi assertha quattro ruoli :

  1. Informa Alice, Bernd, Carl e Daphne cosa si aspetta il tuo codice.
    Supponiamo di avere un metodo che elabora un elenco di tuple e che la logica del programma può interrompersi se tali tuple non sono immutabili:

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

    Questo è più affidabile di informazioni equivalenti nella documentazione e molto più facile da mantenere.

  2. Informa il computer su cosa si aspetta il tuo codice.
    assertapplica un comportamento corretto da parte dei chiamanti del tuo codice. Se il tuo codice chiama Alices e il codice di Bernd chiama il tuo, quindi senza il assert, se il programma si arresta in modo anomalo nel codice Alices, Bernd potrebbe presumere che fosse colpa di Alice, Alice indaga e potrebbe presumere che fosse colpa tua, indaghi e dici a Bernd che era in realtà il suo. Molto lavoro perso.
    Con le asserzioni, chiunque sbaglia una chiamata, sarà presto in grado di vedere che è stata colpa sua, non tua. Alice, Bernd e tutti voi beneficiate. Salva immense quantità di tempo.

  3. Informa i lettori del tuo codice (incluso te stesso) su ciò che il tuo codice ha raggiunto ad un certo punto.
    Supponiamo di avere un elenco di voci e ognuna di esse può essere pulita (il che è buono) o può essere smorsh, trale, gullup o scintillio (che non sono tutti accettabili). Se è puzzolente, non deve essere abbandonato; se è banale, deve essere bilanciato; se è gullup deve essere tracciato (e quindi eventualmente anche stimolato); se è scintillato, deve essere nuovamente scintillato, tranne il giovedì. Ti viene l'idea: è roba complicata. Ma il risultato finale è (o dovrebbe essere) che tutte le voci sono pulite. La cosa giusta da fare è riassumere l'effetto del ciclo di pulizia come

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

    Questa affermazione evita il mal di testa per tutti coloro che cercano di capire esattamente cosa sta realizzando il meraviglioso ciclo. E la più frequente di queste persone sarà probabilmente te stesso.

  4. Informa il computer su ciò che il tuo codice ha raggiunto ad un certo punto.
    Se dovessi mai dimenticare di accelerare una voce che ne ha bisogno dopo il trotto, assertti salverà la giornata ed eviterai che il tuo codice rompa il caro Daphne molto più tardi.

Secondo me, asserti due scopi della documentazione (1 e 3) e della salvaguardia (2 e 4) sono ugualmente preziosi.
Informare le persone può persino essere più prezioso che informare il computer perché può impedire gli stessi errori che l' assertobiettivo mira a rilevare (nel caso 1) e in ogni caso un sacco di errori successivi.


34
5. assert isinstance () aiuta PyCharm (python IDE) a conoscere il tipo di variabile, viene usato per il completamento automatico.
Cjkjvfnby,

1
Asserisce assunzioni di codice autocertificate per ciò che è vero al momento dell'esecuzione corrente. È un commento di presupposto, che viene verificato.
Pyj

9
Riguardo a 2 e 4: dovresti stare molto attento che le tue affermazioni non siano troppo rigide. Altrimenti le affermazioni stesse potrebbero essere l'unica cosa che consente di utilizzare il programma in un contesto più generale. In particolare, affermare i tipi va contro la battitura a macchina di Python.
zwirbeltier,

9
@Cjkjvfnby Fai attenzione a un uso eccessivo di isinstance () come descritto in questo post di blog: " isinstance () considerato dannoso ". Ora puoi usare i docstring per specificare i tipi in Pycharm.
binarysubstrate

2
Usare le asserzioni in un modo per garantire il contratto. Maggiori informazioni su Design by Contract en.wikipedia.org/wiki/Design_by_contract
Leszek Zarna

22

Oltre alle altre risposte, si afferma che generano eccezioni, ma solo AssertionErrors. Da un punto di vista utilitaristico, le asserzioni non sono adatte quando hai bisogno di un controllo accurato sulle eccezioni che intercetti.


3
Giusto. Sembrerebbe sciocco cogliere eccezioni di errore di asserzione nel chiamante.
Raffi Khatchadourian,

Ottimo punto Una sfumatura che può essere facilmente trascurata quando si guardano le domande originali da un livello macro. Anche se non fosse per il problema delle dichiarazioni perse durante l'ottimizzazione, perdere i dettagli specifici del tipo di errore renderebbe il debugging molto più impegnativo. Saluti, outis!
cfwschmidt,

La tua risposta può essere letta come se volessi catturarla AssertionErrors, quando sei a posto, essendo a grana grossa. In realtà, non dovresti prenderli.
Tomasz Gandor,

19

L'unica cosa che è veramente sbagliata in questo approccio è che è difficile fare un'eccezione molto descrittiva usando le dichiarazioni assert. Se stai cercando la sintassi più semplice, ricorda che puoi anche fare qualcosa del genere:

class XLessThanZeroException(Exception):
    pass

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

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

Un altro problema è che l'uso di assert per il normale controllo delle condizioni è che rende difficile disabilitare gli avvisi di debug usando il flag -O.


24
È possibile aggiungere un messaggio di errore a un'asserzione. È il secondo parametro. Ciò lo renderà descrittivo.
Raffi Khatchadourian,

10

La parola inglese asserire qui è usata nel senso di imprecare , affermare , dichiarare . Non significa "controlla" o "dovrebbe essere" . Ciò significa che si da un coder stanno facendo una dichiarazione giurata qui:

# 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 il codice è corretto, salvo sconvolgimenti a evento singolo , guasti hardware e simili, nessuna asserzione fallirà mai . Ecco perché il comportamento del programma per un utente finale non deve essere influenzato. Soprattutto, un'asserzione non può fallire nemmeno sotto condizioni programmatiche eccezionali . Semplicemente non succede mai. Se succede, il programmatore dovrebbe essere zapped per questo.


8

Come è stato detto in precedenza, le asserzioni dovrebbero essere utilizzate quando il tuo codice NON DOVREBBE mai raggiungere un punto, il che significa che c'è un bug lì. Probabilmente il motivo più utile che posso vedere per usare un'asserzione è un invariante / pre / postcondizione. Questi sono qualcosa che deve essere vero all'inizio o alla fine di ogni iterazione di un ciclo o di una funzione.

Ad esempio, una funzione ricorsiva (2 funzioni separate, quindi 1 gestisce un input errato e l'altra gestisce un codice errato, poiché è difficile distinguerlo con la ricorsione). Ciò renderebbe ovvio se mi dimenticassi di scrivere la dichiarazione if, che cosa era andato storto.

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

Questi invarianti ad anello spesso possono essere rappresentati con un'asserzione.


2
È meglio farlo con i decoratori (@precondition e @postcondition)
Caridorc,

@Caridorc qual è il vantaggio concreto di ciò?
Chiel ten Brinke,

@ChieltenBrinke codice auto-documentante, invece di #precondition: n >= 0 e @precondition(lambda n: n >= 0)
un'asserzione

@Caridorc Sono quei decoratori integrati allora? E come si genera una documentazione da ciò?
Chiel ten Brinke,

@ChieltenBrinke non integrato ma facile da implementare stackoverflow.com/questions/12151182/… . Per la documentazione basta __doc__
correggere

4

C'è un problema di prestazioni?

  • Ricorda di "farlo funzionare prima di farlo funzionare velocemente" .
    Pochissimo percento di qualsiasi programma è in genere rilevante per la sua velocità. Puoi sempre eliminare o semplificare un assertcaso in cui si dimostrasse mai un problema di prestazioni - e la maggior parte di loro non lo farà mai.

  • Sii pragmatico :
    supponi di avere un metodo che elabora un elenco non vuoto di tuple e la logica del programma si interromperà se quelle tuple non sono immutabili. Dovresti scrivere:

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

    Questo probabilmente va bene se le tue liste tendono ad essere lunghe dieci voci, ma può diventare un problema se hanno un milione di voci. Ma invece di scartare completamente questo prezioso controllo, puoi semplicemente declassarlo

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

    che è a buon mercato, ma è probabile che cattura la maggior parte degli attuali errori di programma in ogni caso.


2
Dovrebbe essere assert(len(listOfTuples)==0 or type(listOfTyples[0])==tuple).
osa,

No, non dovrebbe. Sarebbe un test molto più debole, perché non controlla più la proprietà "non vuota", che la seconda asserzione verifica. (Il primo no, anche se dovrebbe.)
Lutz Prechelt,

1
La seconda affermazione non controlla esplicitamente la proprietà non vuota; è più di un effetto collaterale. Se dovesse sollevare un'eccezione a causa dell'elenco vuoto, la persona che lavora con il codice (qualcun altro o l'autore, un anno dopo averlo scritto) lo fisserebbe, cercando di capire se l'affermazione fosse davvero destinata a catturare la situazione dell'elenco vuoto o se si tratta di un errore nell'affermazione stessa. Inoltre, non vedo come non verificare che il caso vuoto sia "molto più debole", mentre solo il controllo del primo elemento è "97% corretto".
osa,

3

Bene, questa è una domanda aperta e ho due aspetti che voglio toccare: quando aggiungere asserzioni e come scrivere i messaggi di errore.

Scopo

Per spiegarlo a un principiante, le asserzioni sono dichiarazioni che possono generare errori, ma non le intercetterai. E normalmente non dovrebbero essere allevati, ma nella vita reale a volte vengono sollevati comunque. E questa è una situazione grave, dalla quale il codice non può recuperare, quello che chiamiamo un "errore fatale".

Successivamente, è per "scopi di debug", che, sebbene corretto, sembra molto sprezzante. Mi piace la formulazione "dichiaranti invarianti, che non dovrebbe mai essere violata", anche se funziona in modo diverso su diversi principianti ... Alcuni "capiscono", e altri o non trovano alcun uso o sostituiscono le normali eccezioni, o addirittura controllare il flusso con esso.

Stile

In Python assertè un'affermazione, non una funzione! (Ricorda assert(False, 'is true')che non rilancerà. Ma, tenendo fuori questo:

Quando e come scrivere il "messaggio di errore" facoltativo?

Ciò si applica in modo concreto ai framework di unit test, che spesso hanno molti metodi dedicati per fare affermazioni ( assertTrue(condition),assertFalse(condition), assertEqual(actual, expected) ecc.). Spesso forniscono anche un modo per commentare l'affermazione.

Nel codice usa e getta potresti fare a meno dei messaggi di errore.

In alcuni casi, non c'è nulla da aggiungere all'asserzione:

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

Ma a parte questo, un messaggio è utile per la comunicazione con altri programmatori (che a volte sono utenti interattivi del tuo codice, ad esempio in Ipython / Jupyter ecc.).

Fornisci loro informazioni, non solo dettagli di implementazione interni.

invece di:

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

Scrivi:

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

o forse anche:

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

Lo so, lo so - questo non è un caso per un'asserzione statica, ma voglio indicare il valore informativo del messaggio.

Messaggio negativo o positivo?

Questo può essere controverso, ma mi fa male leggere cose come:

assert a == b, 'a is not equal to b'
  • queste sono due cose contraddittorie scritte l'una accanto all'altra. Quindi ogni volta che ho un'influenza sulla base di codice, spingo per specificare ciò che vogliamo, usando verbi extra come 'must' e 'should', e non dire ciò che non vogliamo.

    asserire a == b, 'a deve essere uguale a b'

Quindi, anche ottenere AssertionError: a must be equal to bè leggibile e l'istruzione appare logica nel codice. Inoltre, puoi ottenere qualcosa da esso senza leggere il traceback (che a volte non può nemmeno essere disponibile).


1

Sia l'uso assertche la raccolta di eccezioni riguardano la comunicazione.

  • Le asserzioni sono dichiarazioni sulla correttezza del codice indirizzato agli sviluppatori : un'asserzione nel codice informa i lettori del codice sulle condizioni che devono essere soddisfatte perché il codice sia corretto. Un'affermazione che non riesce in fase di esecuzione informa gli sviluppatori che esiste un difetto nel codice che deve essere corretto.

  • Le eccezioni sono indicazioni su situazioni non tipiche che possono verificarsi in fase di esecuzione ma che non possono essere risolte dal codice a disposizione, indirizzate al codice chiamante per essere gestite lì. Il verificarsi di un'eccezione non indica la presenza di un bug nel codice.

La migliore pratica

Pertanto, se consideri il verificarsi di una situazione specifica in fase di runtime come un bug di cui desideri informare gli sviluppatori ("Ciao sviluppatore, questa condizione indica che esiste un bug da qualche parte, correggi il codice.") fare una dichiarazione. Se l'asserzione verifica gli argomenti di input del tuo codice, in genere dovresti aggiungere alla documentazione che il tuo codice ha "comportamento indefinito" quando gli argomenti di input violano tali condizioni.

Se invece il verificarsi di quella stessa situazione non è indice di un bug nei tuoi occhi, ma invece una (forse rara ma) possibile situazione che ritieni debba piuttosto essere gestita dal codice client, solleva un'eccezione. Le situazioni in cui viene sollevata un'eccezione dovrebbero far parte della documentazione del rispettivo codice.

C'è un problema [...] di prestazione con l'utilizzo assert

La valutazione delle asserzioni richiede del tempo. Tuttavia, possono essere eliminati in fase di compilazione. Ciò ha alcune conseguenze, tuttavia, vedi sotto.

C'è un [...] problema di manutenzione del codice con l'utilizzo assert

Normalmente le asserzioni migliorano la manutenibilità del codice, poiché migliorano la leggibilità rendendo esplicite le assunzioni e verificando regolarmente durante il runtime queste assunzioni. Ciò contribuirà anche a catturare le regressioni. C'è un problema, tuttavia, che deve essere tenuto presente: le espressioni utilizzate nelle asserzioni non dovrebbero avere effetti collaterali. Come accennato in precedenza, le asserzioni possono essere eliminate al momento della compilazione, il che significa che anche i potenziali effetti collaterali scomparirebbero. Questo può - involontariamente - cambiare il comportamento del codice.


1

Un'asserzione è di verificare:
1. la condizione valida,
2. l'istruzione valida,
3. la logica vera;
del codice sorgente. Invece di fallire l'intero progetto, dà l'allarme che qualcosa non è appropriato nel tuo file sorgente.

Nell'esempio 1, poiché la variabile 'str' non è nulla. Quindi nessuna affermazione o eccezione viene sollevata.

Esempio 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

Nell'esempio 2, var 'str' è nullo. Quindi stiamo salvando l'utente di andare avanti di programma difettoso da assert dichiarazione.

Esempio 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

Nel momento in cui non vogliamo il debug e abbiamo capito il problema delle asserzioni nel codice sorgente. Disabilita il flag di ottimizzazione

python -O assertStatement.py
non verrà stampato nulla


0

In IDE come PTVS, PyCharm, le assert isinstance()istruzioni Wing possono essere utilizzate per abilitare il completamento del codice per alcuni oggetti poco chiari.


Questo sembra precedere l'uso di annotazioni di tipo o di typing.cast.
Acumenus,

-1

Per quello che vale, se hai a che fare con il codice su cui si basa assertper funzionare correttamente, quindi l'aggiunta del codice seguente assicurerà che gli asserti siano abilitati:

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

2
Questo non risponde alla domanda di OP sulle migliori pratiche.
codeforester,
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.