Duck typing, validazione dei dati e programmazione assertiva in Python

9

Informazioni su digitazione anatra :

Duck typing is aided by habitually not testing for the type of arguments in method and function bodies, relying on documentation, clear code and testing to ensure correct use.

Informazioni sulla convalida degli argomenti (EAFP: è più facile chiedere perdono che il permesso). Un esempio adattato da qui :

...it is considered more pythonic to do :

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

This means that anyone else using your code doesn't have to use a real dictionary or subclass - they can use any object that implements the mapping interface.

Unfortunately in practise it's not that simple. What if member in the above example might be an integer ? Integers are immutable - so it's perfectly reasonable to use them as dictionary keys. However they are also used to index sequence type objects. If member happens to be an integer then example two could let through lists and strings as well as dictionaries.

Informazioni sulla assertiva programmazione:

Assertions are a systematic way to check that the internal state of a program is as the programmer expected, with the goal of catching bugs. In particular, they're good for catching false assumptions that were made while writing the code, or abuse of an interface by another programmer. In addition, they can act as in-line documentation to some extent, by making the programmer's assumptions obvious. ("Explicit is better than implicit.")

I concetti menzionati sono a volte in conflitto, quindi conto i seguenti fattori quando scelgo se non eseguo alcuna convalida dei dati, faccio validazioni o asserzioni forti:

  1. strong convalida. Per validazione strong intendo aumentare un'eccezione personalizzata ( ApiError ad esempio). Se la mia funzione / metodo è parte di un'API pubblica, è meglio convalidare l'argomento per mostrare un buon messaggio di errore sul tipo inatteso. Controllando il tipo non intendo solo l'utilizzo di isinstance , ma anche se l'oggetto passato supporta l'interfaccia necessaria (digitazione anatra). Mentre registro l'API e specifica il tipo previsto e l'utente potrebbe voler utilizzare la mia funzione in modo inaspettato, mi sento più sicuro quando controllo le ipotesi. Di solito uso isinstance e se successivamente voglio supportare altri tipi o anatre, cambio la logica di validazione.

  2. Programmazione assertiva. Se il mio codice è nuovo, io uso molto. Quali sono i tuoi consigli in merito? In seguito rimuovi gli asserzioni dal codice?

  3. Se la mia funzione / metodo non fa parte di un'API, ma passa alcuni dei suoi argomenti attraverso un altro codice non scritto, studiato o testato da me, faccio un sacco di asserti in base all'interfaccia chiamata. La mia logica dietro questo - meglio fallire nel mio codice, poi da qualche parte 10 livelli più profondi in stacktrace con errore incomprensibile che costringe a fare il debug molto e poi ad aggiungere l'assert al mio codice comunque.

Commenti e consigli su quando utilizzare o non utilizzare la convalida tipo / valore, afferma? Scusa se non la migliore formulazione della domanda.

Ad esempio, considera la seguente funzione, dove Customer è un modello dichiarativo di 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 convalida:

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 usare ognuno di questi nel contesto della digitazione anatra, controllo del tipo, convalida dei dati?

    
posta warvariuc 17.06.2013 - 03:50
fonte

1 risposta

4

Consentitemi di dare alcuni principi guida.

Principio n. 1. Come descritto nel link il sovraccarico 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. Lasciare le asserzioni (Ma non fare nulla di importante negli asseriti!)

Principio # 2. Se stai affermando qualcosa, e avrai un errore fatale, allora usa un assert. Non c'è assolutamente alcun valore nel fare qualcos'altro. Se in seguito qualcuno vorrà cambiarlo, potrà cambiare il tuo codice o evitare quella chiamata al metodo.

Principio # 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 # 4. Non consentire le cose che sono segni di probabili errori. Ad esempio, considera di passare un dizionario di opzioni. Se quel dizionario contiene elementi che non sono opzioni valide, allora è un segno che qualcuno non ha capito la tua API, o ha avuto un refuso. Saltare su questo è più probabile che abbia un errore di battitura piuttosto che impedire a qualcuno di fare qualcosa di ragionevole.

Sulla base dei primi 2 principi, la tua seconda versione può essere buttata via. Quale delle altre due che preferisci è una questione di gusti. Quale pensi più probabile? Che qualcuno passerà un non cliente a add_customer e le cose si romperanno (nel qual caso la versione 3 è preferibile), o che qualcuno vorrà a un certo punto sostituire il cliente con un oggetto proxy di qualche tipo che risponda a tutti i metodi giusti (nel qual caso la versione 1 è preferibile).

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

    
risposta data 17.06.2013 - 08:58
fonte

Leggi altre domande sui tag