2012-03-07 11 views
38

Voglio verificare se un oggetto è un'istanza di una classe e solo questa classe (nessuna sottoclasse). Potrei farlo con:Tipo Python() o __class__, == oppure è

obj.__class__ == Foo 
obj.__class__ is Foo 
type(obj) == Foo 
type(obj) is Foo 

Ci sono motivi per scegliere l'uno rispetto all'altro? (differenze di prestazioni, insidie, ecc.)

In altre parole: a) c'è qualche differenza pratica tra l'utilizzo di __class__ e type(x)? b) gli oggetti di classe sono sempre sicuri per il confronto usando is?


Update: Grazie a tutti per il feedback. Sono ancora sconcertato dal fatto che gli oggetti di classe siano o meno singleton, il mio buon senso dice che lo sono, ma è stato davvero difficile ottenere una conferma (prova a cercare su google "python", "class" e "unique" o "singleton") .

Mi piacerebbe anche chiarire che, per le mie particolari esigenze, la soluzione "più economica" che funziona è la migliore, dal momento che sto cercando di ottimizzare il massimo da poche classi specializzate (quasi raggiungendo il punto in cui la cosa sensata da fare è far cadere Python e sviluppare quel particolare modulo in C). Ma la ragione alla base della domanda era capire meglio il linguaggio, dal momento che alcune delle sue caratteristiche sono un po 'troppo oscure per me per trovare facilmente quell'informazione. Ecco perché sto lasciando che la discussione si estenda un po 'invece di accontentarsi di __class__ is, così posso sentire l'opinione di persone più esperte. Finora è stato molto fruttuoso!

Ho eseguito un piccolo test per confrontare le prestazioni delle 4 alternative. I risultati sono stati profiler:

   Python PyPy (4x) 
type() is 2.138 2.594 
__class__ is 2.185 2.437 
type() == 2.213 2.625 
__class__ == 2.271 2.453 

sorprende, is eseguito meglio di == per tutti i casi. type() ha ottenuto prestazioni migliori in Python (2% più veloce) e __class__ ha ottenuto risultati migliori in PyPy (il 6% più veloce). È interessante notare che __class__ == ha ottenuto risultati migliori in PyPy rispetto a type() is.


Aggiornamento 2: molte persone non sembrano capire cosa intendo con "una classe è un singleton", quindi mi ilustrate con un esempio:

>>> class Foo(object): pass 
... 
>>> X = Foo 
>>> class Foo(object): pass 
... 
>>> X == Foo 
False 
>>> isinstance(X(), Foo) 
False 
>>> isinstance(Foo(), X) 
False 

>>> x = type('Foo', (object,), dict()) 
>>> y = type('Foo', (object,), dict()) 
>>> x == y 
False 
>>> isinstance(x(), y) 
False 

>>> y = copy.copy(x) 
>>> x == y 
True 
>>> x is y 
True 
>>> isinstance(x(), y) 
True 
>>> y = copy.deepcopy(x) 
>>> x == y 
True 
>>> x is y 
True 
>>> isinstance(x(), y) 
True 

doesn Non importa se ci sono oggetti N di tipo type, dato un oggetto, solo uno sarà la sua classe, quindi è sicuro confrontarsi per riferimento in questo caso. E dal momento che il confronto dei riferimenti sarà sempre più economico rispetto al confronto dei valori, volevo sapere se la mia affermazione precedente valesse o meno. Sto arrivando alla conclusione che lo fa, a meno che qualcuno non presenti prove contrarie.

+0

Se si desidera sapere quale è il più veloce da utilizzare, è sufficiente verificarlo con il modulo timeit. –

+0

Le classi non sono singletons - sono istanze delle loro metaclassi - il metaclass di default è "type", mentre un normale python di Python avrà molte istanze. – jsbueno

+2

@jsbueno Questo non è quello che intendevo. Naturalmente ci sono molte istanze, ma solo una sarà "la classe" di un dato oggetto, giusto? In altre parole se 'x .__ class__ == a' e' a == b' allora 'a is b' (per ben comportarsi' __eq__', ovviamente), questa è la congettura che sto cercando di confermare/confutare. – mgibsonbr

risposta

19

Per le classi di vecchio stile, c'è una differenza:

>>> class X: pass 
... 
>>> type(X) 
<type 'classobj'> 
>>> X.__class__ 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
AttributeError: class X has no attribute '__class__' 
>>> x = X() 
>>> x.__class__ 
<class __main__.X at 0x171b5d50> 
>>> type(x) 
<type 'instance'> 

Il punto di classi di nuovo stile è stato quello di unificare classe e tipo. Tecnicamente parlando, l'__class__ è l'unica soluzione che funzionerà sia per le istanze di classi nuove che di vecchio stile, ma genererà anche un'eccezione sugli stessi oggetti di classe vecchio stile. È possibile chiamare type() su qualsiasi oggetto, ma non tutti gli oggetti hanno __class__. Inoltre, è possibile distruggere con __class__ in un modo che non è possibile distruggere con type().

>>> class Z(object): 
...  def __getattribute__(self, name): 
...    return "ham" 
... 
>>> z = Z() 
>>> z.__class__ 
'ham' 
>>> type(z) 
<class '__main__.Z'> 

Personalmente, di solito ho un ambiente con solo classi di nuovo stile, e per una questione di stile preferisco usare type() come io in genere preferisco funzioni built-in, quando esistono per utilizzando attributi magici. Ad esempio, preferirei anche bool(x) a x.__nonzero__().

+0

Ciò renderebbe preferibile "__class__", giusto? Dal momento che funzionerà sia per classi nuove che vecchie. – mgibsonbr

+2

Ho ampliato la mia risposta per rispondere a questa domanda come meglio posso. Non penso che ci sia una risposta universale, ma se si può essere sicuri che non sarà necessario usarlo sulle classi vecchio stile, 'type()' dovrebbe funzionare meglio. –

10

Il risultato di type() equivale a obj.__class__ in nuove classi di stile e gli oggetti di classe non sono sicuri per il confronto utilizzando is, utilizzare invece ==.

Per le nuove classi di stile il modo preferibile qui sarebbe type(obj) == Foo.

Come ha sottolineato Michael Hoffman nella sua risposta, qui c'è una differenza tra classi nuove e vecchie, quindi per il codice compatibile con le versioni precedenti potrebbe essere necessario utilizzare obj.__class__ == Foo.

Per coloro sostenendo che isinstance(obj, Foo) è preferibile, si consideri il seguente scenario:

class Foo(object): 
    pass 

class Bar(Foo): 
    pass 

>>> obj = Bar() 
>>> isinstance(obj, Foo) 
True 
>>> type(obj) == Foo 
False 

L'OP vuole che il comportamento di type(obj) == Foo, dove sarà false anche se Foo è una classe base di Bar.

+3

Can fai un esempio di un oggetto di classe che è uguale ma non identico? –

+0

@MichaelHoffman: Se si utilizza una metaclasse, è possibile sovrascrivere il metodo '__eq__' per la classe stessa? Non riesco a vederne l'uso, ma è l'unica eccezione che riesco a pensare. –

+1

@MichaelHoffman - Non posso, ma questo è un confronto di valori e non ci sono garanzie che i valori equivalenti abbiano la stessa identità. È un dettaglio di implementazione quando questo è il caso, e anche se ci sono alcune istanze (questa potrebbe essere una di queste) in cui l'implementazione rende impossibile avere valori uguali e non identici, che non rendono sicuro. –

9

is deve essere utilizzato solo per i controlli di identità, non per i controlli di tipo (esiste un'eccezione alla regola in cui è possibile e deve utilizzare is per il controllo contro i singleton).

Nota: in genere non utilizzo type e == per i controlli di tipo. Il modo preferibile per i controlli di tipo è isinstance(obj, Foo). Se hai mai un motivo per controllare se qualcosa non è un'istanza di sottoclassi, per me ha un odore di pesce. Quando class Foo(Bar):, quindi Barè unFoo e dovresti evitare situazioni in cui parte del codice deve funzionare su un'istanza ma si interrompe su un'istanza Bar.

+0

Giusto. Se non puoi farlo, di solito hai un design rotto. –

+2

Grazie, ma ho detto esplicitamente "nessuna sottoclasse" ... – mgibsonbr

+0

oops, mi sono perso (!) – wim

-1

Aggiornamento: Grazie a tutti per il feedback. Sono ancora sconcertato dal fatto che gli oggetti di classe siano o meno singleton, il mio buon senso dice che lo sono, ma è stato davvero difficile ottenere una conferma (prova a cercare su google "python", "class" e "unique" o "singleton") .

Posso confermare che __instance__ è un singleton. Ecco la prova.

>>> t1=File.test() 
made class 
>>> t2=File.test() 
made class 
>>> print t1.__class__() 
made class 
<File.test object at 0x1101bdd10> 
>>> print t2.__class__() 
made class 
<File.test object at 0x1101bdd10> 

Come si può vedere sia t1 e t2 stampare lo stesso valore in memoria anche se t1 e t2 sono a valori diversi in memoria. Ecco la prova di ciò.

>>> print t1 
<File.test object at 0x1101bdc90> 
>>> print t2 
<File.test object at 0x1101bdcd0> 

Il metodo __instance__ esiste solo se è stato utilizzato class "name"(object):. Se si utilizza la classe di stile classica, class "name":, il metodo __instance__ non esiste.

Ciò significa che è il più generico che probabilmente si desidera utilizzare type a meno che non si sappia che esiste un'istanza di fatto.

+1

Grazie per la tua risposta! Sull'ultimo commento, incolla l'intero codice, quindi selezionalo e fai clic sul pulsante "codice di esempio" (o semplicemente rientra il tuo codice di 4 spazi prima di incollare qui, a seconda di quale sia più semplice). – mgibsonbr

+2

Questo non è "prova", è solo un esempio. Ecco un altro 'x, y = 7, 7; x is y' valuterà True ma non è la prova che gli interi sono singleton (non lo sono). – wim