Python’s isinstance()
function helps you determine if an object is an instance of a specified class or its superclass, aiding in writing cleaner and more robust code. You use it to confirm that function parameters are of the expected types, allowing you to handle type-related issues preemptively. This tutorial explores how isinstance()
works, its use with subclasses, and how it differs from type()
.
By the end of this tutorial, you’ll understand that:
isinstance()
checks if an object is a member of a class or superclass.type()
checks an object’s specific class, whileisinstance()
considers inheritance.isinstance()
correctly identifies instances of subclasses.- There’s an important difference between
isinstance()
andtype()
.
Exploring isinstance()
will deepen your understanding of the objects you work with and help you write more robust, error-free code.
To get the most out of this tutorial, it’s recommended that you have a basic understanding of object-oriented programming. More specifically, you should understand the concepts of classes, objects—also known as instances—and inheritance.
For this tutorial, you’ll mostly use the Python REPL and some Python files. You won’t need to install any libraries since everything you’ll need is part of core Python. All the code examples are provided in the downloadable materials, and you can access these by clicking the link below:
Get Your Code: Click here to download the free sample code that you’ll use to learn about isinstance() in Python.
Take the Quiz: Test your knowledge with our interactive “What Does isinstance() Do in Python?” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
What Does isinstance() Do in Python?Take this quiz to learn how Python's isinstance() introspection function reveals object classes and why it might not always show what you expect.
It’s time to start this learning journey, where you’ll discover the nature of the objects you use in your code.
Why Would You Use the Python isinstance()
Function?
The isinstance()
function determines whether an object is an instance of a class. It also detects whether the object is an instance of a superclass. To use isinstance()
, you pass it two arguments:
- The instance you want to analyze
- The class you want to compare the instance against
These arguments must only be passed by position, not by keyword.
If the object you pass as the first argument is an instance of the class you pass as the second argument, then isinstance()
returns True
. Otherwise, it returns False
.
Note: You’ll commonly see the terms object and instance used interchangeably. This is perfectly correct, but remembering that an object is an instance of a class can help you see the relationship between the two more clearly.
When you first start learning Python, you’re told that objects are everywhere. Does this mean that every integer, string, list, or function you come across is an object? Yes, it does! In the code below, you’ll analyze some basic data types:
>>> shape = "sphere"
>>> number = 8
>>> isinstance(shape, str)
True
>>> isinstance(number, int)
True
>>> isinstance(number, float)
False
You create two variables, shape
and number
, which hold str
and int
objects, respectively. You then pass shape
and str
to the first call of isinstance()
to prove this. The isinstance()
function returns True
, showing that "sphere"
is indeed a string.
Next, you pass number
and int
to the second call to isinstance()
, which also returns True
. This tells you 8
is an integer. The third call returns False
because 8
isn’t a floating-point number.
Knowing the type of data you’re passing to a function is essential to prevent problems caused by invalid types. While it’s better to avoid passing incorrect data in the first place, using isinstance()
gives you a way to avert any undesirable consequences.
Take a look at the code below:
>>> def calculate_area(length, breadth):
... return length * breadth
>>> calculate_area(5, 3)
15
>>> calculate_area(5, "3")
'33333'
Your function takes two numeric values, multiplies them, and returns the answer. Your function works, but only if you pass it two numbers. If you pass it a number and a string, your code won’t crash, but it won’t do what you expect either.
The string gets replicated when you pass a string and an integer to the multiplication operator (*
). In this case, the "3"
gets replicated five times to form "33333"
, which probably isn’t the result you expected.
Things get worse when you pass in two strings:
>>> calculate_area("5", "3")
Traceback (most recent call last):
File "<python-input-8>", line 1, in <module>
calculate_area("5", "3")
~~~~~~~~~~~~~~^^^^^^^^^^
File "<python-input-5>", line 2, in calculate_area
return length * breadth
~~~~~~~^~~~~~~~~
TypeError: can't multiply sequence by non-int of type 'str'
The multiplication operator can’t cope with two strings, so the code crashes. This is where you could use isinstance()
to warn the user about the invalid data.
The improved version of your calculate_area()
function demonstrates this:
>>> def calculate_area(length, breadth):
... if isinstance(length, int) and isinstance(breadth, int):
... return length * breadth
... raise TypeError("Both arguments must be integers")
>>> calculate_area(5, 3)
15
>>> calculate_area(5, "3")
Traceback (most recent call last):
...
TypeError: Both arguments must be integers
>>> calculate_area("5", "3")
Traceback (most recent call last):
...
TypeError: Both arguments must be integers
To avoid unexpected results, you use two calls to isinstance()
, along with the Boolean and
operator, to check that you haven’t passed either length
or breadth
as a string—or any other non-integer—by mistake.
If isinstance()
detects invalid data, the if
statement causes your function to raise a TypeError
exception. Of course, if two integers are passed, the results will be the same as before.
In practice, you should also check whether the length
and breadth
parameters could be float
types. You’ll learn how to incorporate multiple checks into isinstance()
later.
This example of checking for a data type illustrates a common usage of isinstance()
. You’ve reduced the chance of invalid results wandering further through your code.
Now that you’ve been introduced to the basics of isinstance()
, you’ll move on to learn how it can be used to analyze instances within a class hierarchy.
Can isinstance()
Detect Subclasses?
In addition to detecting the class of an instance, isinstance()
can also tell you if your instance is an object of a superclass. Remember that an instance is considered an object of its parent class, any of its superclasses, and the class you used to create it.
Suppose you’re creating a class hierarchy for a pool game simulator. You could begin with a Ball
class containing .color
and .shape
data attributes, along with .rebound()
and .detect_collision()
methods. You could then create a PoolBall
subclass for your game. It would inherit everything Ball
has, but you could also tweak its contents to meet the specific requirements of the pool game.
Having done this, you might decide to design some more ball game simulations. Instead of creating a fresh Ball
class, you could reuse your existing one and create more subclasses.
For example, Ball
could serve as the superclass for the SoccerBall
, PoolBall
, and AmericanFootBall
classes, each sharing the same basic content but implementing methods differently to behave appropriately within their respective games.
Take the .rebound()
method defined within the AmericanFootBall
class, whose shape resembles a prolate spheroid. This method will require different calculations from those of SoccerBall
and PoolBall
instances, which are both spheres.
Note: In this tutorial, football refers to American football, while soccer refers to association football.
The code below defines some Ball
subclasses:
balls.py
class Ball:
def __init__(self, color, shape):
self.color = color
self.shape = shape
class PoolBall(Ball):
def __init__(self, color, number):
super().__init__(color, shape="sphere")
self.number = number
class AmericanFootBall(Ball):
def __init__(self, color):
super().__init__(color, shape="prolate spheroid")
This code defines a straightforward class hierarchy containing a Ball
superclass with two subclasses named PoolBall
and AmericanFootBall
. When you create an instance of the Ball
class, you must pass it values for its .color
and .shape
attributes.
The data you pass when you create your instance is defined within the .__init__()
instance initializer method. This gets called automatically each time you create an instance of the class. You can use it to make any colored ball and give it any shape you wish.
Now, take a closer look at the PoolBall
class. To create this as a subclass of Ball
, you define it using class PoolBall(Ball)
. Your PoolBall
will have access to all the methods and data attributes from Ball
.
When you create a PoolBall
instance, you pass it a color
and number
, but not a shape
, since this is all .__init__()
demands. However, PoolBall
instances still have a .shape
data attribute that needs to be initialized.
To initialize .shape
, you call the original .__init__()
method defined in the Ball
superclass using super().__init__(color, shape="sphere")
. This passes your desired color
and the string "sphere"
to the superclass initializer. The string "sphere"
is assigned to the shape
parameter in the parent class. All PoolBall
instances will always be spherical in shape with the color you desire.
To ensure that the number
value is assigned to the .number
attribute, you again use self.number
. Your new PoolBall
instance will have a color, a shape of "sphere"
, and a number.
Similarly, any AmericanFootBall
instances will have a .color
attribute and "prolate spheroid"
as their .shape
attribute value. They won’t have a .number
attribute because it isn’t needed.
Investigating How isinstance()
Treats Subclasses
Using the class hierarchy you’ve just developed, you need to create some instances for isinstance()
to analyze:
>>> from balls import AmericanFootBall, Ball, PoolBall
>>> eight_ball = PoolBall("black", 8)
>>> football = AmericanFootBall("brown")
>>> ball = Ball("green", "sphere")
To create instances, you pass the required values to each class. So, PoolBall("black", 8)
will create a new PoolBall
instance, which will be black in color, with a spherical shape and a number eight. Similarly, you create a brown, prolate spheroidal American football and a more general green spherical ball.
Next, you’ll investigate these classes and instances with isinstance()
, starting with eight_ball
:
>>> isinstance(eight_ball, PoolBall)
True
>>> isinstance(eight_ball, Ball)
True
>>> isinstance(eight_ball, AmericanFootBall)
False
Look carefully at the first two isinstance()
calls. Unsurprisingly, eight_ball
has been detected as both a PoolBall
and a Ball
. This second result is True
because any instance of a subclass is also an instance of its superclasses.
Now look at the third call. It returns False
because eight_ball
, being a PoolBall
, isn’t an AmericanFootBall
. In human terms, they share the same parent, so they’re more like siblings.
You’ve just seen that any instance of a subclass is also an instance of its superclass. This could be considered a special case. The wider rule is that any instance of a subclass is also an instance of all its superclasses. This means that an instance belongs not only to the class it was created from but also to its parent class, grandparent class, and so on, all the way up the hierarchy.
Nothing lasts forever, and Python’s inheritance tree is no different. The lineage needs to stop somewhere. That somewhere is the object
superclass, which sits at the top of the hierarchy and is the class from which all other classes are derived.
Take a look at this code:
>>> isinstance(eight_ball, object)
True
>>> isinstance(football, object)
True
>>> isinstance(ball, object)
True
>>> isinstance(object, object)
True
As you can see, everything is an object
instance—even object
itself.
Earlier, you saw how int
, float
, and str
are classes you commonly use when working with basic data types. Another type is bool
, which can hold only True
or False
. Incidentally, this is also the data type returned by isinstance()
.
Like all other types in Python, bool
is a class. However, it’s also a subclass of int
. This means that, in addition to being instances of bool
, both True
and False
are also instances of int
:
>>> isinstance(True, int)
True
>>> isinstance(True, bool)
True
>>> isinstance(False, int)
True
>>> isinstance(False, bool)
True
As you can see, True
and False
are instances of both int
and bool
types. However, while the code int(True)
will return 1
and int(False)
will return 0
, the integers 1
and 0
and the Booleans True
and False
are different. While a bool
type is an integer, an int
type isn’t a Boolean.
Unless you’re careful, this can cause problems when you need to check for non-Boolean integers in situations where bool
values may also be present:
>>> test_data = [10, True, False]
>>> for element in test_data:
... print("int" if isinstance(element, int) else "bool")
int
int
int
As you can see, everything is being flagged as an int
. One solution could be this:
>>> for element in test_data:
... print("bool" if isinstance(element, bool) else "int")
'int'
'bool'
'bool'
This time, the results are accurate. An alternative approach would be to use type()
. As its name suggests, this will tell you the data type of the data passed to it:
>>> for element in test_data:
... print("bool") if type(element) is bool else print("int")
int
bool
bool
While using type()
works in this case, its purpose isn’t the same as isinstance()
. You’ll learn more about this later.
Note: Python classes each have an .mro()
class method. This tells you the method resolution order (MRO), which is the order that Python follows to locate data attributes and methods in a class hierarchy. It’s particularly relevant when you’re dealing with multiple inheritance.
Although it’s not directly related to using isinstance()
, you can use .mro()
to inspect the class hierarchy your class belongs to.
Take a look at the class hierarchy shown below:
>>> class Top:
... pass
>>> class Middle(Top):
... pass
>>> class Bottom(Middle):
... pass
>>> Bottom.mro()
[<class '__main__.Bottom'>,
<class '__main__.Middle'>,
<class '__main__.Top'>,
<class 'object'>]
>>> isinstance(Bottom(), Top)
True
The Top
class has a subclass named Middle
, which has a subclass named Bottom
. When you call the .mro()
method on Bottom
and read the output from left to right, you can see the relationship between Bottom
and its various superclasses. Note that Top
isn’t the end of the hierarchy—the object
class is.
Before you go any further, it’s time to consolidate your learning.
Consolidating Your Learning
To check your understanding of what you’ve learned so far, see if you can answer the following questions:
Answer the following questions based on the Ball
hierarchy used above:
- Is
football
an instance ofAmericanFootBall
? - Is
football
an instance ofPoolBall
? - Is
ball
an instance ofBall
? - Is
ball
an instance ofAmericanFootBall
? - Is
football
an instance ofBall
? - Is
ball
an instance ofPoolBall
? - Is the integer
1
an instance ofbool
? - Is the integer
0
an instancebool
?
You can confirm your answers to each question using isinstance()
:
>>> isinstance(football, AmericanFootBall)
True
>>> isinstance(football, PoolBall)
False
>>> isinstance(ball, Ball)
True
>>> isinstance(ball, AmericanFootBall)
False
>>> isinstance(football, Ball)
True
>>> isinstance(ball, PoolBall)
False
>>> isinstance(1, bool)
False
>>> isinstance(0, bool)
False
Did you get them all correct? Well done if you did!
Now that you’ve had some experience with isinstance()
, next you’ll learn why it’s often better to use isinstance()
instead of type()
to determine an object’s class.
How Does isinstance()
Differ From type()
?
The isinstance()
function is just one example of several introspection functions that allow you to examine Python objects to learn more about them. As you just learned, Python also provides type()
. If you pass it an instance, then you’ll be rewarded with the class to which that instance belongs.
Consider once more the Ball
and PoolBall
classes you created earlier. To begin with, you create a new PoolBall
instance:
>>> from balls import Ball, PoolBall
>>> eight_ball = PoolBall("black", 8)
You can see from the code that eight_ball
is an instance of PoolBall
. You can also use type()
to confirm this:
>>> type(eight_ball)
<class 'balls.PoolBall'>
>>> type(eight_ball) is PoolBall
True
As expected, type()
returns details of the class to which eight_ball
belongs—in this case, a PoolBall
class.
However, you should be careful when using it because it isn’t designed to identify superclass membership. For example, because eight_ball
is a PoolBall
, and PoolBall
is a subclass of Ball
, isinstance()
will confirm that eight_ball
is also a Ball
, but type()
won’t:
>>> isinstance(eight_ball, Ball)
True
>>> type(eight_ball) is Ball
False
While isinstance()
indeed confirms what you know to be true, at first glance, type()
appears to disagree.
The reason type()
returns False
is because it’s not designed to recognize inheritance hierarchies. Unlike isinstance()
, type()
is designed to look at an instance and tell you the class that instance was created from. When you attempt to use type()
to interrogate any further up the class hierarchy, you’re using the wrong tool for the job.
Earlier, you used type()
to check for the bool
type. This is perfectly safe because the bool
class has been designed so that it can’t be subclassed. In other words, there will never be any subclasses of bool
that could be passed to type()
. So, using type()
to check whether a bool
is a subclass of int
would be pointless.
Note: In earlier versions of Python, type()
was also considered slower than isinstance()
. In current versions, though, the difference is negligible. Depending on what you pass to each function, type()
sometimes even slightly outperforms isinstance()
. If you’re in a situation where either could be used, then it might be a good idea to time test both to see which is faster.
Next, you’ll learn how to extend the basic functionality of isinstance()
.
Can You Use isinstance()
to Check for Multiple Types?
Besides being able to tell you if your instance belongs to a single class, you can also use isinstance()
to determine if it belongs to one of several classes. To do this, pass in a tuple of classes. There’s no need to make separate isinstance()
calls.
Suppose you wanted to determine whether data was an integer or a floating-point number. Here’s one way you could do it:
>>> "Number" if isinstance(3.14, (int, float)) else "Not a number"
'Number'
>>> "Number" if isinstance("3.14", (int, float)) else "Not a number"
'Not a number'
By using this code, you’re checking whether a value, first 3.14
, then "3.14"
, is either an integer or a floating-point number. To do this, you pass in a tuple containing both class types you want to check for as the second parameter of isinstance()
. In the first case, 3.14
, because it’s a float
, is shown to be a number. In the second case, "3.14"
is a string, so it’s not considered a number.
You can even pass a nested tuple, meaning a tuple that contains other tuples, as the second argument to isinstance()
:
>>> "Number" if isinstance(3.14, ((int,), (float,))) else "Not a number"
'Number'
>>> "Number" if isinstance(3.14, (int, float)) else "Not a number"
'Number'
In using a nested tuple, you’ve created a semantically equivalent piece of code to your earlier example. The first example contains a nested tuple, while the second contains a flat tuple, as before. The trailing commas surrounding int
and float
in the first example are necessary to ensure tuples are present because each has only one element. Try replacing 3.14
with "3.14"
and you’ll see the same Not a number
result as before.
Using nested tuples is helpful if the nested tuple containing the types to be checked is constructed using existing tuples from different sources. In most cases, you’ll rarely use nested tuples in this way.
Although passing multiple types to isinstance()
is common, you can also use a union type expression. This allows you to group together multiple data types separated by the bitwise OR (|
) operator. When you pass a type to be tested into isinstance()
, the function will return True
if the type matches any of the types defined in your union.
Note: Union types are actually designed for type checking to improve code readability. They also work with isinstance()
.
Instead of passing the tuple (int, float)
to isinstance()
, you could do the following:
>>> "Number" if isinstance(3.14, int | float) else "Not a number"
'Number'
>>> "Number" if isinstance("3.14", int | float) else "Not a number"
'Not a number'
In both examples, you’ve replaced the earlier (int, float)
tuple with the int | float
union type expression. Unsurprisingly, the results are the same.
Note: The only types that isinstance()
supports are single class types, tuples of class types, nested tuples of class types, and union type expressions. If you try using a list, dictionary or anything else, isinstance()
will fail. Other introspection functions, such as issubclass()
, also only accept tuples, so there’s consistency.
Guido van Rossum made a design choice to allow only tuples for a few reasons:
-
Tuples are immutable, meaning they can’t be changed. This assures you that the tuple of types you pass in can’t be mutated by code elsewhere in your program.
-
The implementation of
isinstance()
isn’t designed to take a large number of elements. When you use a tuple, you do so because you have a limited number of elements, whereas lists are designed to grow. Passing a large volume of types intoisinstance()
will mean it’ll perform poorly.
Next, you’ll see how isinstance()
works with abstract base classes.
How Can You Use isinstance()
With Abstract Base Classes?
Earlier, you learned how isinstance()
can tell you if an object is an instance of a class or one of its superclasses. You also know that creating subclasses helps avoid reinventing the wheel when something similar to one of your existing classes becomes necessary. In some cases, you’ll never need to use instances of these superclasses. This is where you might find abstract base classes useful.
Creating an Abstract Base Class
Thinking back to your Ball
example, every ball has a specific use. When you play with a ball in everyday life, you’re really playing with a soccer ball, a pool ball, and so on. Since these real-world balls are not direct instances of Ball
but only instances of its subclasses, the Ball
class is a good candidate for consideration as an abstract base class. Conversely, subclasses designed to be implemented are called concrete classes.
Before you can see how isinstance()
deals with subclasses, you’ll redesign your earlier hierarchy to make the Ball
class abstract:
balls_v2.py
from abc import ABC, abstractmethod
class Ball(ABC):
def __init__(self, color, shape):
self.color = color
self.shape = shape
@abstractmethod
def get_state(self):
pass
class PoolBall(Ball):
def __init__(self, color, number):
super().__init__(color, shape="sphere")
self.number = number
def get_state(self):
print(f"Color {self.color}, Number {self.number}, Shape {self.shape}")
class AmericanFootBall(Ball):
def __init__(self, color):
super().__init__(color, shape="prolate spheroid")
def get_state(self):
print(f"Color {self.color}, Shape {self.shape}")
Here, you use the appropriately named abc
module to help you create abstract base classes. To make your Ball
class an abstract class, your Ball
class must inherit from the ABC
class, which you import from the abc
module in Python’s standard library. Therefore, you can make Ball
an abstract base class using the notation Ball(ABC)
when you define the class.
One of the most prevalent features of abstract classes is abstract methods. Abstract methods act as placeholders for methods that their subclasses will require. They typically don’t contain any implementation details because the implementation is specific to each subclass.
When you create an instantiable subclass of an abstract base class, you usually define an implementation for each abstract method in the subclass. If you don’t, then you’ll raise a TypeError
when you try to create instances of these subclasses because they’ll still be abstract.
To designate a method as abstract, you must decorate it with the @abstractmethod
decorator. This decorator is also provided for you courtesy of abc
.
In your balls_v2.py
file, you first define a new abstract version of your Ball
class. As with the earlier version, its .__init__()
method sets up the initial values of its .color
and .shape
data attributes exactly as .__init__()
did in the original version.
You also include a new abstract method named .get_state()
. The implementation of this method must appear in any subclasses of Ball
. However, in its abstract form, you use the pass
statement instead of an implementation. This appeases the IndentationError
exception you’d see if you tried to create a method without any code in its body.
You also redefine your earlier PoolBall
and AmericanFootBall
classes. This time, you’re forced to provide an implementation of .get_state()
in each. Remember that without this implementation, you wouldn’t be able to create instances of them.
As a quick check to see if the abstraction is working, you try to create a new Ball
instance:
>>> from balls_v2 import Ball
>>> test_ball = Ball("white", "sphere")
Traceback (most recent call last):
...
TypeError: Can't instantiate abstract class Ball without an
⮑ Implementation for abstract method 'get_state'
As you can see, your attempt to instantiate Ball
has failed miserably. However, Ball
is still useful to isinstance()
, as you’ll see next.
Learning How isinstance()
Treats Abstract Base Classes
Although you can no longer create instances of Ball
, there’s nothing to stop you from instantiating the other two classes:
>>> from balls_v2 import AmericanFootBall, Ball, PoolBall
>>> eight_ball = PoolBall("black", 8)
>>> football = AmericanFootBall("brown")
After importing both classes, you manage to instantiate objects from each successfully. You can then use these new objects to see how isinstance()
interprets abstract classes:
>>> isinstance(eight_ball, Ball)
True
>>> isinstance(football, Ball)
True
Although you’ve added abstraction into your infrastructure, isinstance()
treats abstract base classes like any other superclass. Both cases show that PoolBall
and AmericanFootBall
are considered instances of their common abstract superclass. This probably isn’t that surprising—an abstract class is just another class in the hierarchy, after all.
Does isinstance()
Use Duck Typing?
One interesting aspect of using isinstance()
is that it sometimes uses duck typing to decide whether your instance belongs to a class. When you use duck typing, class membership isn’t decided by its true membership, but by its abilities.
Suppose an instance of one of your classes shares similar behavior to another unrelated class. In that case, using duck typing means you’d consider it an instance of that unrelated class. In other words:
If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck. (Source)
However, duck typing can cause isinstance()
to produce surprising results, as you’ll see next.
Introducing collections.abc
To see some examples of duck typing, you’ll use the Iterable
abstract base class from the collections.abc
library, because isinstance()
uses duck typing with many classes from this library.
In Python, an iterable is an object that allows you to iterate over it. In other words, you can work linearly through your iterable and retrieve each of its elements. You use iterables whenever you use common collections such as lists and tuples. It’s because both lists and tuples are iterables that you can use in Python for
loops.
Note: To learn more about iterables, check out Iterators and Iterables in Python: Run Efficient Iterations.
Suppose you decide to write your own iterable to store various pool players. One way of doing this is to subclass the Iterable
abstract base class from the collections.abc
module:
player_iterables.py
from collections.abc import Iterable
class PlayersVersionOne(Iterable):
def __init__(self, players):
self.players = players
def __iter__(self):
return iter(self.players)
To create a PlayersVersionOne
instance, you pass in a Python iterable, such as a list or tuple of pool players. This becomes its .players
data attribute.
The Iterable
abstract base class requires you to implement .__iter__()
in your subclasses. This method is designed to return an iterator object to manage element retrieval. You use the built-in iter()
function within .__iter__()
to return the iterator associated with .players
. It’s the presence of .__iter__()
that makes your PlayersVersionOne
an iterable and allows it to work within a for
loop:
>>> from player_iterables import PlayersVersionOne
>>> for player in PlayersVersionOne(["Fast Ed", "Slow Jo", "Still Su"]):
... print(player)
Fast Ed
Slow Jo
Still Su
First, you import your new PlayersVersionOne
class and create a new instance that contains a list of pool players. By using this instance in a for
loop, you can iterate over it and print each value. Your PlayersVersionOne
instance behaves like an iterable.
You can use isinstance()
to confirm this:
>>> from collections.abc import Iterable
>>> isinstance(
... PlayersVersionOne(["Fast Ed", "Slow Jo", "Still Su"]),
... Iterable
... )
True
You’re probably not surprised to see that your PlayersVersionOne
instance is reported as an instance of Iterable
. After all, PlayersVersionOne
is a subclass of collections.abc.Iterable
. However, things aren’t quite as simple as they seem at first, as you’ll see in the next section.
Understanding How isinstance()
Uses Duck Typing
What may surprise you is that isinstance()
actually uses duck typing. When you use isinstance()
to test for Iterable
, it returns True
because the iteration is done using .__iter__()
. There’s actually no need to subclass Iterable
. Any class with an .__iter__()
method is considered an Iterable
, regardless of what that method does.
Consider the PlayersVersionTwo
class that you add to your player_iterables.py
file:
player_iterables.py
# ...
class PlayersVersionTwo:
def __init__(self, players):
self.players = players
def __iter__(self):
return iter(self.players)
The functionality of your PlayersVersionTwo
class is identical to that of your PlayersVersionOne
class, only this time, PlayersVersionTwo
isn’t a subclass of Iterable
.
Suppose you run the same analysis code against this version:
>>> from collections.abc import Iterable
>>> from player_iterables import PlayersVersionTwo
>>> for player in PlayersVersionTwo(["Fast Ed", "Slow Jo", "Still Su"]):
... print(player)
Fast Ed
Slow Jo
Still Su
>>> isinstance(
... PlayersVersionTwo(["Fast Ed", "Slow Jo", "Still Su"]),
... Iterable
... )
True
As you can see, the output is identical. The isinstance()
function still considers PlayersVersionTwo
an instance of Iterable
, not because it is, but because it implements .__iter__()
. Duck typing is present.
Next, you create a class that’s still capable of being used in for
loops but won’t be detected by isinstance()
as an instance of Iterable
. Consider the PlayersVersionThree
version of your iterable:
player_iterables.py
# ...
class PlayersVersionThree:
def __init__(self, players):
self.players = players
def __getitem__(self, index):
if index >= len(self.players):
raise IndexError("Invalid Index")
return self.players[index]
This time, there’s no .__iter__()
method in sight. This code uses .__getitem__()
to retrieve each element in the .players
data attribute and return it.
Note: Although .__getitem__()
is used here for iteration, this is an older style of iteration in Python.
The main role of the this special method is to allow you to access instances of the class in which it’s defined using the square brackets notation ([]
), similar to how you access elements of a Python list.
You should use .__iter__()
to make an instance of a class iterable in modern Python since this uses Python’s iterator protocol.
You analyze PlayersVersionThree
in the same way as before:
>>> from player_iterables import PlayersVersionThree
>>> for player in PlayersVersionThree(["Fast Ed", "Slow Jo", "Still Su"]):
... print(player)
Fast Ed
Slow Jo
Still Su
>>> isinstance(
... PlayersVersionThree(["Fast Ed", "Slow Jo", "Still Su"]),
... Iterable,
... )
False
You can see that while instances of PlayersVersionThree
are most certainly iterables because they work in for
loops, isinstance()
reveals they’re not instances of Iterable
because it’s looking for .__iter__()
.
Indeed, isinstance()
will give you the same True
value for any instance you pass it that implements .__iter__()
, regardless of what gets returned:
player_iterables.py
# ...
class PlayersVersionFour:
def __init__(self, players):
self.players = players
def __iter__(self):
pass
This time, although PlayersVersionFour
contains .__iter__()
, the method uses pass
to make it do nothing. However, isinstance()
still thinks PlayersVersionFour
is an Iterable
:
>>> from player_iterables import PlayersVersionFour
>>> isinstance(
... PlayersVersionFour(["Fast Ed", "Slow Jo", "Still Su"]),
... Iterable,
... )
True
It’s impossible to use instances of PlayersVersionFour
in a for
loop because it’s not iterable. You might want to try this yourself.
Note: Clearly, you can’t rely on isinstance()
to detect iterables. The best method is to use the iter()
function you saw earlier. If iter()
returns a reference to an iterator object, then an iterable is present. If it returns an error, then it’s not:
>>> iter(PlayersVersionOne(["Fast Ed", "Slow Jo", "Still Su"]))
<list_iterator object at 0x72834fbf00a0>
>>> iter(PlayersVersionTwo(["Fast Ed", "Slow Jo", "Still Su"]))
<list_iterator object at 0x72834fa680a0>
>>> iter(PlayersVersionThree(["Fast Ed", "Slow Jo", "Still Su"]))
<iterator object at 0x72834fbf01f0>
>>> iter(PlayersVersionFour(["Fast Ed", "Slow Jo", "Still Su"]))
Traceback (most recent call last):
...
TypeError: iter() returned non-iterator of type 'NoneType'
Only the first three custom objects you defined are iterable and can be looped over.
You might wonder when isinstance()
decides to use duck typing instead of a class hierarchy search. It only uses duck typing if the class it’s testing contains a special .__instancecheck__()
method. If present, .__instancecheck__()
defines what methods the instance must contain. If this method is absent, then isinstance()
will check the class hierarchy to which the instance belongs.
In the case of the Iterable
class, its .__instancecheck__()
method instructs isinstance()
to ensure the .__iter__()
method is present. If it exists, then the instance is deemed an Iterable
.
Many of the classes within collections.abc
instruct isinstance()
to work in this way. So, just because an instance isn’t an Iterable
, it doesn’t mean it can’t be iterated over. The collections.abc
documentation will tell you what methods isinstance()
looks for.
This is probably a good time to consolidate what you’ve just learned.
Consolidating Your Learning
To wrap up your learning about isinstance()
and how to use it with abstract base classes, why not try answering the following questions?
-
Earlier, you created an abstract
Ball
class with two concrete classes,PoolBall
andAmericanFootBall
. You then proved that instances of both classes are instances ofBall
. Is there any other class mentioned in the code thatisinstance()
would also recognize botheight_ball
andfootball
as instances of? -
The
collections.abc.Callable
is another essential abstract base class. See if you can find out what this abstract class represents. Can you think of any examples ofCallable
instances you’ve used in this tutorial? -
Is there anything inside the
PoolBall
andAmericanFootball
classes that’s alsoCallable
?
-
If you look at the code closely, then you’ll see that the
Ball
class is a subclass ofABC
. Remember thatisinstance()
can recognize class hierarchies:PythonCopied!>>> from abc import ABC >>> from collections.abc import Callable >>> from balls_v2 import AmericanFootBall, PoolBall >>> eight_ball = PoolBall("black", 8) >>> football = AmericanFootBall("brown") >>> isinstance(eight_ball, ABC) True >>> isinstance(football, ABC) True
Instances of
PoolBall
andAmericanFootball
are both instances ofABC
. -
All functions are callable, as are classes. Any of the functions or classes mentioned in this tutorial are instances of
Callable
:PythonCopied!>>> isinstance(isinstance, Callable) True >>> isinstance(PoolBall, Callable) True
As you can see, the
isinstance()
function andPoolBall
areCallable
instances. -
Methods are also instances of the
Callable
class:PythonCopied!>>> isinstance(eight_ball.get_state, Callable) True
As you can see, the
.get_state()
method of aPoolBall
instance—or of any other subclass ofBall
—is indeedCallable
.Note that you passed
eight_ball.get_state
intoisinstance()
, noteight_ball.get_state()
. If you had done the latter, you would have called.get_state()
, which would return the string"sphere"
. Since that’s just a string,"sphere"
isn’t an instance ofCallable.
By getting to this stage, you’ve had a good workout using isinstance()
. However, your learning journey doesn’t have to end here.
What Should You Learn Next?
Congratulations on reaching the end of this tutorial! Hopefully, it’s sparked your interest in using isinstance()
to perform introspection on your objects. This can help you better understand the objects your program uses and the relationships between them.
However, isinstance()
isn’t the only introspection function you can use. You might like to delve deeper and look into the following commonly used functions:
Function | Purpose |
---|---|
dir(object) |
Returns a list of the attributes and methods of the given object. |
hasattr(object, name) |
Checks whether the object has an attribute with the specified name. |
id(object) |
Returns an integer representing the object’s unique identity. |
issubclass(class, classinfo) |
Checks if the class is a subclass of any class in classinfo . |
These aren’t the only built-in functions or introspection tools available. You can find more in Python’s inspect
module.
Conclusion
This tutorial showed you how to use isinstance()
to investigate the type of an object. You should now have a solid understanding of how to use it, along with some of its gotchas.
In this tutorial, you’ve learned how to:
- Use
isinstance()
to determine the class or superclass an object belongs to - Prefer
isinstance()
overtype()
when confirming class membership - Apply
isinstance()
to both abstract base classes and concrete classes - Recognize that when duck typing is involved,
isinstance()
may not return the result you expect - Explore additional introspection functions beyond
isinstance()
While you’ve had a solid overview of what isinstance()
can do, you’re strongly encouraged to practice what you’ve learned and to explore introspection further. Understanding what these functions reveal will help you better grasp how your code works and the objects you’re working with.
Get Your Code: Click here to download the free sample code that you’ll use to learn about isinstance() in Python.
Frequently Asked Questions
Now that you have some experience with isinstance()
in Python, you can use the questions and answers below to check your understanding and recap what you’ve learned.
These FAQs are related to the most important concepts you’ve covered in this tutorial. Click the Show/Hide toggle beside each question to reveal the answer.
You use isinstance()
to determine if an object is an instance of a specified class or its superclass.
You can check if an object is a member of a class by using the isinstance()
function, passing the object and the class as arguments.
The isinstance()
function checks if an object is an instance of a class or any of its superclasses, while type()
only returns the object’s exact class without considering inheritance.
Yes, isinstance()
works with subclasses by returning True
if the object is an instance of the specified class or any of its superclasses.
Take the Quiz: Test your knowledge with our interactive “What Does isinstance() Do in Python?” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
What Does isinstance() Do in Python?Take this quiz to learn how Python's isinstance() introspection function reveals object classes and why it might not always show what you expect.