Python type() or __class__, == or is
Asked Answered
B

4

77

I want to test whether an object is an instance of a class, and only this class (no subclasses). I could do it either with:

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

Are there reasons to choose one over another? (performance differences, pitfalls, etc)

In other words: a) is there any practical difference between using __class__ and type(x)? b) are class objects always safe for comparison using is?


Update: Thanks all for the feedback. I'm still puzzled by whether or not class objects are singletons, my common sense says they are, but it's been really hard to get a confirmation (try googling for "python", "class" and "unique" or "singleton").

I'd also like to clarify that, for my particular needs, the "cheaper" solution that just works is the best, since I'm trying to optimize the most out of a few, specialized classes (almost reaching the point where the sensible thing to do is to drop Python and develop that particular module in C). But the reason behind the question was to understand better the language, since some of its features are a bit too obscure for me to find that information easily. That's why I'm letting the discussion extend a little instead of settling for __class__ is, so I can hear the opinion of more experienced people. So far it's been very fruitful!

I ran a small test to benchmark the performance of the 4 alternatives. The profiler results were:

               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

Unsurprisingly, is performed better than == for all cases. type() performed better in Python (2% faster) and __class__ performed better in PyPy (6% faster). Interesting to note that __class__ == performed better in PyPy than type() is.


Update 2: many people don't seem to understand what I mean with "a class is a singleton", so I'll ilustrate with an example:

>>> 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

It doesn't matter if there are N objects of type type, given an object, only one will be its class, hence it's safe to compare for reference in this case. And since reference comparison will always be cheaper than value comparison, I wanted to know whether or not my assertion above holds. I'm reaching the conclusion that it does, unless someone presents evidence in contrary.

Breastbone answered 7/3, 2012 at 23:52 Comment(10)
If you want to know which is fastest for your use, just test it with the timeit module.Argot
Classes are not singletons - they are instances of their metaclasses - the default metaclass is "type", whcihin a normal Python porgram will have lots of instances.Anole
@Anole That's not what I meant. Of course there are many instances, but only one will be "the class" of a given object, right? In other words if x.__class__ == a and a == b then a is b (for well behaved __eq__, of course), that's the conjecture I'm trying to confirm/refute.Breastbone
a is b has nothing to do with __eq__ on the objects a, b. it checks id(a) == id(b) no more no less. jsbueno is saying that since the x.__class__, whatever it is, is not a singleton so there is no guarantee that there won't be more of them floating around - hence don't use is.Carder
Thanks everybody for your feedback, I think all answers (and comments) added something useful to the discussion. I can only choose one as the "accepted" though, so I'm picking what I felt was the more accurate.Breastbone
The title is a DoS attack on my brain's syntax unit :)Ur
possible duplicate of Difference between type(obj) and obj.__class__Schiff
@AloisMahdal That is why I think Python is way too complex than C++Trundle
@Trundle that's a logical fallacy: the title is in English mixed with Python, so the property of the title [being ddos on my brain] does not say anything about complexity of Python; it only speaks about this particular instance of mixing the two (being amplified by the fact that Python uses words of common EN as is, and the effect of absence of tick marks). (I do like the title as is --- my comment was just a joke).Ur
@Breastbone if x.__class__ == a and a == b then a is b see here -> Are classobjects singleton in python?Carder
A
50

For old-style classes, there is a difference:

>>> 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'>

The point of new-style classes was to unify class and type. Technically speaking, __class__ is the only solution that will work both for new and old-style class instances, but it will also throw an exception on old-style class objects themselves. You can call type() on any object, but not every object has __class__. Also, you can muck with __class__ in a way you can't muck with type().

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

Personally, I usually have an environment with new-style classes only, and as a matter of style prefer to use type() as I generally prefer built-in functions when they exist to using magic attributes. For example, I would also prefer bool(x) to x.__nonzero__().

Argot answered 8/3, 2012 at 0:2 Comment(9)
That would make __class__ preferable then, right? Since it will work both for new and old style classes.Breastbone
I've expanded my answer to answer this question as well as I can. I don't think there is a universal answer, but if you can be sure you won't need to use this on old-style classes, type() should work better.Argot
One weird quirk is that even in Python 3, .__class__ is still used by isinstance checks, which is something that is used deliberately by proxy objects (such as the ones provided by the wrapt library). So it actually turns out that if when proxy objects are in play, foo.__class__ is more "correct" than type(foo) unless you're actively looking to specially handle, reject, or break proxy objects within your code.Saba
Needing to use type() or .__class__ is pretty rare these days. I mainly use them during debugging, for which I would absolutely want to know if some sort of proxy class were involved. I wouldn't want to make assumptions about what behavior is desired as a universal.Argot
@MichaelHoffman Right, hence the "unless". Anyway, I actually find a lot of use nowadays for either type(self) or self.__class__: to get __name__ in the __repr__ implementations of my classes and to construct new instances of the current class (f.e. in __add__), it's nicer for subclassing, especially the __repr__ case (where a hard-coded class name is automatically wrong in the majority of cases where the rest of the repr might be perfectly fine still). (In which case the difference only comes up if code calls the unbound method with a wrapped instance as the first argument.)Saba
Oh, it's actually more painfully complicated/inconsistent than I thought: type(obj) is overwritten by setting obj.__class__ but it is not middlemanned by __getattribute__ nor by a @property named __class__. (A maximally transparent proxy object therefore can make the tradeoff of using the former method to behave exactly like the wrapped object to type calls, or using one of the latter two methods to not break objects which change their .__class__ dynamically.) Meanwhile, isinstance is overridden by any of those three methods.Saba
Another possibly relevant criteria for deciding when to do type() vs .__class__ is that certain other language features use the type behavior - for example, overriding .__class__ on an instance will cause with to use .__enter__ and .__exit__ methods from the override, but with will ignore @property and __getattribute__ middlemanning of __class__, so type would be the more "correct" choice if we were building something like contextlib.ExitStack, unless you're actively looking to behave differently than the language behaves.Saba
What are old style classes? If I do class X: pass and then X.__class__, I get <class 'type'> as expected.K
They no longer exist in Python 3. portingguide.readthedocs.io/en/latest/classes.htmlArgot
A
17

The result of type() is equivalent to obj.__class__ in new style classes, and class objects are not safe for comparison using is, use == instead.

For new style classes the preferable way here would be type(obj) == Foo.

As Michael Hoffman pointed out in his answer, there is a difference here between new and old style classes, so for backwards compatible code you may need to use obj.__class__ == Foo.

For those claiming that isinstance(obj, Foo) is preferable, consider the following scenario:

class Foo(object):
    pass

class Bar(Foo):
    pass

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

The OP wants the behavior of type(obj) == Foo, where it will be false even though Foo is a base class of Bar.

Andorra answered 7/3, 2012 at 23:56 Comment(10)
Can you give an example of a class object that is equal but not identical?Argot
@MichaelHoffman: If you use a metaclass, could you override the __eq__ method for the class itself? I can't see any use for it, but it's the only exception I can think of.Halima
@MichaelHoffman - I can't, but this is a value comparison and there are no guarantees that equivalent values will have the same identity. It is an implementation detail when that is the case, and even though there are some instances (this may be one of them) where the implementation makes it impossible to have equal and non-identical values, that doesn't make it safe.Andorra
@F.J Yeah, that's exactly my point. Python's style guide says that "Comparisons to singletons like None should always be done with 'is' or 'is not', never the equality operators." Well, a class object is like a singleton in the sense that another class will never be "the" class of an object... or at least my common sense says so. However, I'd like to find references to confirm (or refute) that.Breastbone
"The result of type() is equivalent to obj.__class__ in new style classes" is a reasonable assumption about sane code but it is not guaranteed by Python. If the class body definition includes __class__ = 123 then the builtin type returns the correct result (that is, it returns the object that effectively controls the behavior). One could also probably do nasty things with getattribute.Thereafter
In my experience, making a transparent dict proxy with a hook in __getitem__ required setting __class__ to something different to type. This is because there are fast-paths in CPython source code that would bypass the __getitem__ hook on certain actions (updating another dict with the contents of the dict proxy, for example) if the proxy directly inherited from dict, but we still need it to pass isinstance checks. Just thought y'all might like an example :)Marker
I'm not sure about guarantees in the actual language spec, but in CPython the class will be a singleton unless it's imported a second time from a different location (something to avoid all the time). But in that case, I'm not at all convinced they would pass equality checks either (does anyone know for sure?).Marker
The obj.__class__ can be overridden by way of @property decorator.Erminois
How can you be sure that "== should be preferred to is" when you can't imagine a scenario where they'd behave differently? It seems like you're assuming that in any of the hypothetical scenarios where they'd behave differently, the programmer would prefer to test for equality than identity - whether this is true would surely depend on the meaning of '==' and what the programmer is trying to do... Maybe you are more interested in the symbolic nature of the type rather than whatever '==' could mean apart from 'is'...Orta
The first paragraph of this answer is totally wrong.Carder
C
15

is should only be used for identity checks, not type checks (there is an exception to the rule where you can and should use is for check against singletons).

Note: I would generally not use type and == for type checks, either. The preferable way for type checks is isinstance(obj, Foo). If you ever have a reason to check if something is not an subclass instance, it smells like a fishy design to me. When class Bar(Foo):, then Bar is a Foo, and you should be avoiding any situations where some part of your code has to work on a Foo instance but breaks on a Bar instance.

Carder answered 7/3, 2012 at 23:59 Comment(5)
Right. If you can't do that, you usually have a broken design.Argot
BTW I agree my design is not the best one, but I'm writing a few very specialized classes that need to be optimized for space, and are not meant to be subclassed outside my library. That's not something I'd do in the general case though...Breastbone
If you're optimizing for space, consider using __slots__. Proceed with caution! :)Jeconiah
@Jeconiah Thanks a lot, didn't even imagine something like that existed in Python! Doesn't solve all my problems, but for most of them it's better than the alternatives I knew of.Breastbone
I use isinstance 99% of the time in my code, but there are a few places where I use type(x) where acting on a subclass would not work. For instance, say I have a slotted class that I know has only two attributes that can only be simple objects, but that gets copied a lot, I can save a lot of time in copying overriding __deepcopy__ to simply create a new object with those two attributes (faster than copy.py's code) and then resort to the copy.py code if it's a subclass. Then subclassing classes do not need to know to override __deepcopy__ itself.Emunctory
S
-3

Update: Thanks all for the feedback. I'm still puzzled by whether or not class objects are singletons, my common sense says they are, but it's been really hard to get a confirmation (try googling for "python", "class" and "unique" or "singleton").

I can confirm that __instance__ is a singleton. Here is the proof.

>>> 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>

As you can see both t1 and t2 print out the same value in memory even though t1 and t2 are at different values in memory. Here is the proof of that.

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

The __instance__ method only exists if you used class "name"(object):. If you use the classic style class, class "name":, than the __instance__ method doesn't exist.

What this means is to be the most generic you probably want to use type unless you know for a fact instance does exist.

Scrawly answered 14/2, 2014 at 22:6 Comment(5)
Thanks for your answer! About the last comment, just paste the whole code, then select it and click the "code sample" button (or simply indent your code by 4 spaces before pasting here, whichever is easier).Breastbone
This is not "proof", it's just one example. Here's another x, y = 7, 7; x is y will evaluate True but it is not proof that integers are singletons (they are not).Carder
What exactly is "__instance__"?Marker
Worth noting: while integers in general aren't singletons (this is dead obvious because: there is more than one of them), in cpython 7 is indeed a singleton, as are "small" strings and other small integers.Marker
#33925450Totten

© 2022 - 2024 — McMap. All rights reserved.