2012-01-19 12 views
5

Mi trovo a cercare di convertire i parametri del costruttore nei tipi giusti molto spesso nei miei programmi Python. Finora ho usato il codice simile a questo, in modo da non dover ripetere le argomentazioni di eccezione:Pratiche di Python: esiste un modo migliore per verificare i parametri del costruttore?

class ClassWithThreads(object): 
    def __init__(self, num_threads): 
     try: 
      self.num_threads= int(num_threads) 
      if self.num_threads <= 0: 
       raise ValueError() 
     except ValueError: 
      raise ValueError("invalid thread count") 

Si tratta di una buona pratica? Dovrei semplicemente non preoccuparmi di rilevare eventuali eccezioni alla conversione e lasciarle propagare al chiamante, con l'eventuale svantaggio di avere messaggi di errore meno significativi e coerenti?

+2

se non è un'interfaccia utente, lasciarla inalterata e lasciare che l'eccezione si propaghi naturalmente. Stai facendo più lavoro per te stesso e avvolgere l'eccezione non aggiunge molto valore. – monkut

risposta

11

Quando ho una domanda come questa, vado a cercare nella libreria standard per codice che posso modellare il mio codice dopo. multiprocessing/pool.py ha una classe un po 'vicino al tuo:

class Pool(object): 

    def __init__(self, processes=None, initializer=None, initargs=(), 
       maxtasksperchild=None): 
     ... 
     if processes is None: 
      try: 
       processes = cpu_count() 
      except NotImplementedError: 
       processes = 1 
     if processes < 1: 
      raise ValueError("Number of processes must be at least 1") 

     if initializer is not None and not hasattr(initializer, '__call__'): 
      raise TypeError('initializer must be a callable') 

Si noti che non dice

processes = int(processes) 

Si presuppone appena inviato è un intero, non un galleggiante o una stringa, o qualsiasi altra cosa. Dovrebbe essere abbastanza ovvio, ma se pensate che non lo sia, penso che sia sufficiente documentarlo.

Alza ValueError se processes < 1 e controlla che initializer, quando specificato, sia richiamabile.

Quindi, se prendiamo multiprocessing.Pool come modello, la classe dovrebbe essere simile a questo:

class ClassWithThreads(object): 
    def __init__(self, num_threads): 
     self.num_threads = num_threads 
     if self.num_threads < 1: 
      raise ValueError('Number of threads must be at least 1') 

Non sarebbe questo approccio possibilmente fallire molto imprevedibile per alcune condizioni?

penso tipo preemptive verifica in genere va contro il grano della filosofia di design di Python (dynamic-, anatra tipizzazione).

duck typing dà Python programmatori opportunità di grande forza espressiva, e lo sviluppo del codice rapido, ma (qualcuno potrebbe dire) è pericoloso perché non fa alcuna tentativo di catturare errori di tipo.

Alcuni sostengono che gli errori logici sono molto più gravi e frequenti degli errori di tipo . Hai bisogno di test unitari per cogliere quegli errori più gravi. Pertanto, anche se si esegue il controllo dei tipi preventivi con lo , non si aggiunge molta protezione.

Questo dibattito si trova nel campo delle opinioni, non dei fatti, quindi non è un argomento risolvibile. Su quale lato della recinzione si può dipendere dalla vostra esperienza, il vostro giudizio sulla probabilità di errori tipo . Potrebbe essere influenzato dalle lingue che già conosci. Potrebbe dipendere dal dominio .

Devi solo decidere per te.


PS. In un linguaggio tipizzato in modo statico, i controlli del tipo possono essere eseguiti in fase di compilazione, non impedendo quindi la velocità del programma. In Python, i controlli di tipo devono essere eseguiti in fase di esecuzione. Questo rallenterà un po 'il programma, e forse molto se il controllo avviene in un ciclo. Man mano che il programma cresce, così sarà il numero di controlli di tipo. E sfortunatamente, molti di questi controlli potrebbero essere ridondanti. Quindi, se davvero credi di aver bisogno di un controllo dei tipi, probabilmente dovresti usare un linguaggio tipizzato staticamente.


PPS. Ci sono decoratori per il controllo del tipo (Python 2) e (Python 3). Ciò separerebbe il codice di controllo del tipo dal resto della funzione e ti consentirà di disattivare più facilmente il controllo del tipo in futuro, se lo desideri.

+0

Questo approccio non potrebbe fallire in modo imprevedibile per alcune condizioni? Ad esempio, il passaggio di una stringa non verrebbe rilevato come un errore (poiché non compareranno mai come '<1'). La mia intenzione con i controlli preventivi sta cercando di garantire una certa consistenza nello stato dell'oggetto, rilevando gli errori il prima possibile invece di avere qualche eccezione casuale generata in seguito. E '' più pitonioso 'lasciare semplicemente le cose e fallire in un secondo momento? – danielkza

4

È possibile utilizzare un decoratore di tipo type this activestate recipe o this other one for python 3. Essi consentono di scrivere codice simile al seguente:

@require("x", int, float) 
@require("y", float) 
def foo(x, y): 
    return x+y 

che sollevano un'eccezione se gli argomenti non sono del tipo richiesto. Potresti facilmente estendere i decoratori per verificare che anche gli argomenti abbiano valori validi.

+2

Queste ricette per decoratori sono belle (e sono facilmente disattivabili se non ne hai più bisogno). –

+0

Questa sembra la soluzione più elegante finora, la controllerò sicuramente. Su una nota non correlata, activestate.com blocca Firefox (10.0) per chiunque altro? – danielkza

2

questo è soggettivo, ma ecco un contro-argomento:

>>> obj = ClassWithThreads("potato") 
ValueError: invalid thread count 

aspetta, cosa? Questo dovrebbe essere un TypeError. Vorrei farlo:

if not isinstance(num_threads, int): 
    raise TypeError("num_threads must be an integer") 
if num_threads <= 0: 
    raise ValueError("num_threads must be positive") 

Okay, quindi questo viola i principi "duck typing". Ma non userei la digitazione anatra per oggetti primitivi come int.

Problemi correlati