Je veux tester si un objet est une instance d'une classe, et seulement cette classe (pas de sous-classes). Je pourrais le faire soit avec:
obj.__class__ == Foo
obj.__class__ is Foo
type(obj) == Foo
type(obj) is Foo
Y a-t-il des raisons de choisir l'un plutôt que l'autre? (différences de performances, pièges, etc.)
En d'autres termes: a) y a-t-il une différence pratique entre l'utilisation de __class__
Et type(x)
? b) les objets de classe sont-ils toujours sûrs pour la comparaison en utilisant is
?
Mise à jour: Merci à tous pour vos commentaires. Je suis toujours perplexe quant à savoir si les objets de classe sont ou non singletons, mon bon sens dit qu'ils le sont, mais il a été vraiment difficile d'obtenir une confirmation (essayez de googler pour "python", "class" et "unique" ou "singleton") .
Je voudrais également préciser que, pour mes besoins particuliers, la solution "moins chère" qui fonctionne est la meilleure, car j'essaie d'optimiser au maximum quelques cours spécialisés (atteignant presque le point où le raisonnable chose à faire est de supprimer Python et de développer ce module particulier en C). Mais la raison derrière la question était de mieux comprendre le langage, car certaines de ses fonctionnalités sont un peu trop obscures pour moi pour trouver ces informations facilement. C'est pourquoi je laisse la discussion se prolonger un peu au lieu de se contenter de __class__ is
, afin que je puisse entendre l'opinion de personnes plus expérimentées. Jusqu'à présent, cela a été très fructueux!
J'ai effectué un petit test pour comparer les performances des 4 alternatives. Les résultats du profileur étaient:
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
Sans surprise, is
a obtenu de meilleurs résultats que ==
Dans tous les cas. type()
a mieux performé dans Python (2% plus rapide) et __class__
a mieux performé dans PyPy (6% plus rapide). Il est intéressant de noter que __class__ ==
a mieux performé dans PyPy que type() is
.
pdate 2: beaucoup de gens ne semblent pas comprendre ce que je veux dire par "une classe est un singleton", donc je vais illustrer avec un exemple:
>>> 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
Peu importe s'il y a N objets de type type
, étant donné un objet, un seul sera sa classe, il est donc sûr de comparer pour référence dans ce cas. Et puisque la comparaison de référence sera toujours moins chère que la comparaison de valeur, je voulais savoir si mon affirmation ci-dessus était vraie ou non. J'arrive à la conclusion que c'est le cas, sauf si quelqu'un présente des preuves contraires.
Pour les classes à l'ancienne, il y a une différence:
>>> 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'>
Le point des classes de nouveau style était d'unifier la classe et le type. Techniquement parlant, __class__
Est la seule solution qui fonctionnera à la fois pour les instances de classe nouvelles et anciennes, mais il lèvera également une exception sur les objets de classe anciens eux-mêmes. Vous pouvez appeler type()
sur n'importe quel objet, mais tous les objets n'ont pas __class__
. En outre, vous pouvez nettoyer avec __class__
D'une manière que vous ne pouvez pas nettoyer avec type()
.
>>> class Z(object):
... def __getattribute__(self, name):
... return "ham"
...
>>> z = Z()
>>> z.__class__
'ham'
>>> type(z)
<class '__main__.Z'>
Personnellement, j'ai généralement un environnement avec des classes de nouveau style uniquement, et pour une question de style, je préfère utiliser type()
comme je préfère généralement les fonctions intégrées lorsqu'elles existent à l'utilisation d'attributs magiques. Par exemple, je préférerais également bool(x)
à x.__nonzero__()
.
Le résultat de type()
est équivalent à obj.__class__
Dans les nouvelles classes de style, et les objets de classe ne sont pas sûrs pour la comparaison en utilisant is
, utilisez plutôt ==
.
Pour les nouvelles classes de style la manière préférable ici serait type(obj) == Foo
.
Comme Michael Hoffman l'a souligné dans sa réponse, il y a une différence entre les classes de style nouveau et ancien, donc pour du code rétrocompatible, vous devrez peut-être utiliser obj.__class__ == Foo
.
Pour ceux qui prétendent que isinstance(obj, Foo)
est préférable, envisagez le scénario suivant:
class Foo(object):
pass
class Bar(Foo):
pass
>>> obj = Bar()
>>> isinstance(obj, Foo)
True
>>> type(obj) == Foo
False
L'OP veut le comportement de type(obj) == Foo
, où il sera faux même si Foo
est une classe de base de Bar
.
is
ne doit être utilisé que pour les contrôles d'identité, pas pour les contrôles de type (il existe une exception à la règle dans laquelle vous pouvez et devez utiliser is
pour vérifier les singletons).
Remarque: Je n'utiliserais généralement pas type
et ==
Pour les vérifications de type non plus. La méthode préférable pour les vérifications de type est isinstance(obj, Foo)
. Si jamais vous avez une raison de vérifier si quelque chose n'est pas une instance de sous-classe, cela me sent comme un design louche. Lorsque class Foo(Bar):
, alors Bar
est unFoo
, et vous devriez éviter toute situation où une partie de votre code doit travailler sur un Foo
instance mais se casse sur une instance de Bar
.