Duck typing, validazione dei dati e programmazione assertiva in Python


10

Informazioni sulla digitazione dell'anatra :

La tipizzazione delle anatre è aiutata dal non testare abitualmente il tipo di argomenti negli organismi metodo e funzione, basandosi sulla documentazione, sul codice chiaro e sui test per garantire un uso corretto.

Informazioni sulla convalida dell'argomento (EAFP: è più facile chiedere perdono che autorizzazione). Un esempio adattato da qui :

... è considerato più pitonico da fare:

def my_method(self, key):
    try:
        value = self.a_dict[member]
    except TypeError:
        # do something else

Ciò significa che chiunque utilizzi il tuo codice non deve utilizzare un dizionario o una sottoclasse reale: può utilizzare qualsiasi oggetto che implementa l'interfaccia di mappatura.

Purtroppo in pratica non è così semplice. Cosa succede se il membro nell'esempio precedente potrebbe essere un numero intero? I numeri interi sono immutabili, quindi è perfettamente ragionevole usarli come chiavi del dizionario. Tuttavia, vengono anche utilizzati per indicizzare oggetti di tipo sequenza. Se un membro risulta essere un numero intero, l'esempio due potrebbe passare attraverso elenchi e stringhe nonché dizionari.

Informazioni sulla programmazione assertiva :

Le asserzioni sono un modo sistematico per verificare che lo stato interno di un programma sia come previsto dal programmatore, con l'obiettivo di catturare i bug. In particolare, sono utili per rilevare false assunzioni fatte durante la scrittura del codice o per abuso di un'interfaccia da parte di un altro programmatore. Inoltre, possono fungere da documentazione in linea in una certa misura, rendendo evidenti le ipotesi del programmatore. ("Esplicito è meglio che implicito.")

I concetti citati a volte sono in conflitto, quindi conto sulla scelta se non eseguo alcuna convalida dei dati, eseguo una convalida forte o utilizzo assert:

  1. Convalida forte. Con convalida forte intendo sollevare un'eccezione personalizzata ( ApiErrorad esempio). Se la mia funzione / metodo fa parte di un'API pubblica, è meglio convalidare l'argomento per mostrare un buon messaggio di errore sul tipo imprevisto. Controllando il tipo non intendo solo usare isinstance, ma anche se l'oggetto passato supporta l'interfaccia necessaria (digitando duck). Mentre documento l'API e specifico il tipo previsto e l'utente potrebbe voler utilizzare la mia funzione in modo imprevisto, mi sento più sicuro quando controllo le ipotesi. Di solito uso isinstancee se in seguito voglio supportare altri tipi o anatre, cambio la logica di validazione.

  2. Programmazione assertiva. Se il mio codice è nuovo, utilizzo molto le asserzioni. Quali sono i tuoi consigli su questo? Rimuovere successivamente le asserzioni dal codice?

  3. Se la mia funzione / metodo non fa parte di un'API, ma passa alcuni dei suoi argomenti a un altro codice non scritto, studiato o testato da me, faccio molte affermazioni secondo l'interfaccia chiamata. La mia logica dietro questo - meglio fallire nel mio codice, quindi da qualche parte 10 livelli più profondi in stacktrace con errore incomprensibile che costringe a eseguire il debug molto e successivamente aggiungere l'asserzione al mio codice.

Commenti e consigli su quando utilizzare o meno la convalida del tipo / valore, afferma? Ci scusiamo per non avere la migliore formulazione della domanda.

Ad esempio, prendere in considerazione la seguente funzione, dove si Customertrova un modello dichiarativo SQLAlchemy:

def add_customer(self, customer):
    """Save new customer into the database.
    @param customer: Customer instance, whose id is None
    @return: merged into global session customer
    """
    # no validation here at all
    # let's hope SQLAlchemy session will break if `customer` is not a model instance
    customer = self.session.add(customer)
    self.session.commit()
    return customer

Quindi, ci sono diversi modi per gestire la validazione:

def add_customer(self, customer):
    # this is an API method, so let's validate the input
    if not isinstance(customer, Customer):
        raise ApiError('Invalid type')
    if customer.id is not None:
        raise ApiError('id should be None')

    customer = self.session.add(customer)
    self.session.commit()
    return customer

o

def add_customer(self, customer):
    # this is an internal method, but i want to be sure
    # that it's a customer model instance
    assert isinstance(customer, Customer), 'Achtung!'
    assert customer.id is None

    customer = self.session.add(customer)
    self.session.commit()
    return customer

Quando e perché dovresti utilizzare ognuno di questi nel contesto della tipizzazione anatra, del controllo del tipo, della convalida dei dati?


1
non dovresti rimuovere le affermazioni proprio come i test unitari a meno che per motivi di prestazioni
Bryan Chen,

Risposte:


4

Vorrei dare alcuni principi guida.

Principio n. 1. Come indicato in http://docs.python.org/2/reference/simple_stmts.html, l'overhead delle prestazioni degli assert può essere rimosso con un'opzione della riga di comando, pur rimanendo lì per il debug. Se le prestazioni sono un problema, fallo. Lascia le affermazioni. (Ma non fare nulla di importante nelle affermazioni!)

Principio n. 2. Se stai affermando qualcosa e avrai un errore fatale, usa un'asserzione. Non c'è assolutamente alcun valore nel fare qualcos'altro. Se in seguito qualcuno desidera modificarlo, può modificare il codice o evitare quella chiamata al metodo.

Principio n. 3. Non vietare qualcosa solo perché pensi che sia una cosa stupida da fare. Quindi cosa succede se il tuo metodo consente le stringhe? Se funziona, funziona.

Principio n. 4. Non consentire cose che sono segni di probabili errori. Ad esempio, considera il passaggio a un dizionario di opzioni. Se quel dizionario contiene elementi che non sono opzioni valide, significa che qualcuno non ha capito la tua API, oppure aveva un refuso. Fare esplodere è più probabile che si verifichi un errore di battitura che impedire a qualcuno di fare qualcosa di ragionevole.

Basato sui primi 2 principi, la tua seconda versione può essere buttata via. Quale delle altre due preferite è una questione di gusti. Quale pensi che sia più probabile? Che qualcuno passerà a un non-cliente add_customere le cose si romperanno (nel qual caso è preferita la versione 3), o che qualcuno vorrà a un certo punto voler sostituire il cliente con un oggetto proxy di qualche tipo che risponde a tutti i metodi giusti (nel qual caso è preferita la versione 1).

Personalmente ho visto entrambe le modalità di errore. Tenderei ad andare con la versione 1 fuori dal principio generale che sono pigro ed è meno digitante. (Anche quel tipo di fallimento di solito tende a presentarsi prima o poi in un modo abbastanza ovvio. E quando voglio usare un oggetto proxy, mi arrabbio molto per le persone che mi hanno legato le mani.) Ma ci sono programmatori che rispetto chi andrebbe dall'altra parte.


Preferisco la v.3, specialmente quando si progetta l'interfaccia: scrivere nuove classi e metodi. Inoltre considero v.3 utile per i metodi API - perché il mio codice è nuovo per gli altri. Penso che l'approccio assertivo sia un buon compromesso, perché viene rimosso in produzione quando viene eseguito in modalità ottimizzata. > Fare esplodere è più probabile che si verifichi un refuso piuttosto che impedire a qualcuno di fare qualcosa di ragionevole. <Quindi, non ti dispiace avere tale convalida?
Warvariuc,

Mettiamola così. Trovo che l'eredità si associ male a come mi piace evolvere i progetti. Preferisco la composizione. Quindi evito di affermare che questo deve appartenere a quella classe. Ma non sono contrario alle affermazioni in cui penso che mi salvino qualcosa.
btilly
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.