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
footballan instance ofAmericanFootBall? - Is
footballan instance ofPoolBall? - Is
ballan instance ofBall? - Is
ballan instance ofAmericanFootBall? - Is
footballan instance ofBall? - Is
ballan instance ofPoolBall? - Is the integer
1an instance ofbool? - Is the integer
0an 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
Ballclass with two concrete classes,PoolBallandAmericanFootBall. 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_ballandfootballas instances of? -
The
collections.abc.Callableis another essential abstract base class. See if you can find out what this abstract class represents. Can you think of any examples ofCallableinstances you’ve used in this tutorial? -
Is there anything inside the
PoolBallandAmericanFootballclasses that’s alsoCallable?
-
If you look at the code closely, then you’ll see that the
Ballclass is a subclass ofABC. Remember thatisinstance()can recognize class hierarchies:Python>>> 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) TrueInstances of
PoolBallandAmericanFootballare both instances ofABC. -
All functions are callable, as are classes. Any of the functions or classes mentioned in this tutorial are instances of
Callable:Python>>> isinstance(isinstance, Callable) True >>> isinstance(PoolBall, Callable) TrueAs you can see, the
isinstance()function andPoolBallareCallableinstances. -
Methods are also instances of the
Callableclass:Python>>> isinstance(eight_ball.get_state, Callable) TrueAs you can see, the
.get_state()method of aPoolBallinstance—or of any other subclass ofBall—is indeedCallable.Note that you passed
eight_ball.get_stateintoisinstance(), 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.



