2011-12-19 8 views
14
>>> False in [0] 
True 
>>> type(False) == type(0) 
False 

La ragione per cui mi sono imbattuto in questo:Python "in" non controlla il tipo?

Per la mia unit test ho creato gli elenchi dei valori di esempio validi e non validi per ciascuno dei miei tipi. (con 'i miei tipi' intendo, non sono al 100% uguali ai tipi di python) Quindi voglio ripetere l'elenco di tutti i valori e aspettarmi che passino se sono nei miei valori validi, e d'altra parte, fallire se non lo sono. che non funziona così bene ora:

>>> valid_values = [-1, 0, 1, 2, 3] 
>>> invalid_values = [True, False, "foo"] 
>>> for value in valid_values + invalid_values: 
...  if value in valid_values: 
...   print 'valid value:', value 
... 
valid value: -1 
valid value: 0 
valid value: 1 
valid value: 2 
valid value: 3 
valid value: True 
valid value: False 

Naturalmente sono d'accordo con gli ultimi due valori 'validi'.

Ciò significa che devo davvero ripetere i miei valori validi e confrontare il tipo?

+0

+1 Hmm, non ho mai pensato che il 'in' di Python non controlli il tipo. Molto interessante . . . – OnesimusUnbound

+0

@BenJames, hmm, mi chiedo come farebbe a rompere l'anatra digitando in Python? – OnesimusUnbound

risposta

4

Come altri hanno scritto, il "in" codice non fa quello che lo vuoi fare. Avrai bisogno di qualcos'altro.

Se davvero si vuole un controllo di tipo (dove il controllo è esattamente lo stesso tipo), allora è possibile includere il tipo nell'elenco:

>>> valid_values = [(int, i) for i in [-1, 0, 1, 2, 3]] 
>>> invalid_values = [True, False, "foo"] 
>>> for value in [v[1] for v in valid_values] + invalid_values: 
... if (type(value), value) in valid_values: 
...  print value, "is valid" 
... else: 
...  print value, "is invalid" 
... 
-1 is valid 
0 is valid 
1 is valid 
2 is valid 
3 is valid 
True is invalid 
False is invalid 
foo is invalid 
>>> 

Handling sottotipi è un po 'più difficile, e dipenderanno su quello che vuoi fare.

+0

Ho usato la soluzione per evitare il ciclo aggiuntivo. Ma l'ho modificato in: valid_values ​​= [(tipo (v), v) per v in [-1, 0, 1, 2, 3]] –

15

Il problema non è il controllo del tipo mancante, ma perché in Python bool è una sottoclasse di int. Prova questo:

>>> False == 0 
True 
>>> isinstance(False, int) 
True 
+0

Non mi lamento di False == 0, ma solo del fatto che "in" non controlla il tipo. –

+6

'in' restituisce true se un elemento nella sequenza è' uguale' all'oggetto richiesto. Quindi il tuo problema non è l'operatore 'in', ma l'operatore' == '. – Constantinius

+0

Mi piacerebbe solo avere un assegno per l'identità lì. C'è un modo per farlo senza usare un loop? –

6

Secondo il documentation, __contains__ è fatto da iterare su raccolta e al controllo elementi da ==. Quindi il problema reale è causato dal fatto che è True.

+0

Per il pedante ... controlla prima un metodo '' __taintain __ (self, thing) '', poi * ricade per iterare sull'oggetto. Per un vero '' elenco'', c'è '' __contains__'', quindi il fallback. –

+0

@GreggLind, ovviamente hai ragione.Tuttavia, non sono riuscito a trovare una definizione migliore di "in". Per conformarsi al principio della meno sorpresa, un metodo '__contains__' sovraccarico dovrebbe comunque fare qualcosa di simile. –

+1

Ho avuto questo morso di me quando determina il contenimento esaurisce un generatore e/o gira per sempre. Evitabile scrivendo un * più intelligente * '' __contains__''. (pensa a cose come '' itertools.cycle'') –

3

Dal True == 1 e False == 0 è difficile distinguere tra i due.

Una possibile ma brutto approccio (che non è garantita anche di lavorare in tutte le implementazioni di Python, ma dovrebbe essere OK in CPython):

>>> for value in valid_values + invalid_values: 
... if value in valid_values and not any(v is value for v in invalid_values): 
...  print ('valid value:', value) 
... 
valid value: -1 
valid value: 0 
valid value: 1 
valid value: 2 
valid value: 3 
+0

Mi sarebbe piaciuto prevenire un altro ciclo. Dato che questo controllo verrà eseguito abbastanza spesso durante la maggior parte dei miei test. –