Pythonでプログラムを書いていると、オブジェクトの「型」を知りたくなる場面はとても多いです。
そのときによく登場するのがisinstanceとtypeですが、両者の違いを正しく理解しておかないと、バグや保守性の低下につながります。
この記事では、isinstanceとtypeの違いと使い分け方を、図解と具体例を使って徹底的に解説します。
isinstanceとtypeの違いとは
isinstanceとtypeの基本的な役割

Pythonでは、どちらも「型」に関係する機能ですが、役割が明確に異なります。
isinstanceの役割は、「あるオブジェクトが、指定したクラス(またはそのサブクラス)のインスタンスかどうかを判定する」ことです。
つまり、継承関係を考慮して「このグループに属しているか」を調べるイメージです。
一方で、typeの役割は、「オブジェクトの実際の型(クラス)オブジェクトそのものを返す」ことです。
こちらは継承を考えず、「このオブジェクトがどのクラスで作られたのか」を正確に知りたいときに使います。
簡単にまとめると、次のような使い分けになります。
- グループ(型階層)として判定したい → isinstance
- そのクラスそのものかどうかを厳密に判定したい → type
Pythonのデータ型とオブジェクト指向の前提知識

isinstanceとtypeを正しく使い分けるために、Pythonの型とオブジェクト指向の考え方を簡単に押さえておきます。
Pythonでは、すべての値がオブジェクトです。
整数も文字列もリストも、そしてクラス自体も、すべてオブジェクトとして扱われます。
また、Pythonはオブジェクト指向の言語なので、クラスを定義し、クラス同士を継承させることができます。
たとえば、標準ライブラリで用意されているlistやdictもクラスであり、それぞれobjectクラスを継承したオブジェクトです。
自分で定義したクラスも、何も書かなくても暗黙的にobjectを継承しています。
この「継承」の仕組みがあるため、親クラスと子クラスのどちらとして扱いたいかを意識した判定が必要になり、そこにisinstanceとtypeの違いが効いてきます。
isinstanceの使い方と具体例
isinstanceの基本構文と戻り値

isinstanceの基本的な構文は次のとおりです。
isinstance(オブジェクト, クラスまたはタプル)
戻り値は真偽値(Boolean)で、条件を満たすとTrue、満たさないとFalseが返ります。
単一クラスを指定する例
# 整数かどうかを判定する例
x = 10
result = isinstance(x, int)
print(result) # True が表示されます
True
複数のクラスを指定する例(タプル)
# intまたはfloatかどうかを判定する例
value1 = 3.14
value2 = "hello"
# 第2引数にタプルで複数の型を渡せます
print(isinstance(value1, (int, float))) # True どちらかに一致すればTrue
print(isinstance(value2, (int, float))) # False どれにも一致しないのでFalse
True
False
このようにisinstanceは、「この値はこのクラス(またはそのサブクラス)のインスタンスか」を簡潔にチェックするための関数です。
典型的な使用シーン

実務や日常のコーディングでisinstanceが使われる典型的なシーンをいくつか挙げます。
1つ目は、関数の引数や外部入力のバリデーションです。
たとえば、数値だけを受け取りたい関数で、間違って文字列が渡されていないかを確認する場合です。
def square(num):
# numがintまたはfloatでなければエラーを出す
if not isinstance(num, (int, float)):
raise TypeError("numは数値型(intまたはfloat)である必要があります")
return num * num
print(square(3)) # 9
print(square(2.5)) # 6.25
# print(square("3")) # コメントを外すとTypeErrorが発生します
9
6.25
2つ目は、処理の分岐を型によって切り替える場合です。
例えば、引数がリストなら全要素を合計し、単一の数値ならそのまま返す、といった柔軟な関数を書くことができます。
def total(value):
# リストなら中身を合計
if isinstance(value, list):
return sum(value)
# 数値ならそのまま返す
elif isinstance(value, (int, float)):
return value
else:
raise TypeError("listまたは数値を渡してください")
print(total([1, 2, 3])) # 6
print(total(10)) # 10
6
10
このように、外部から渡される「よくわからない値」に対して安全なガードをかけるのに非常に向いています。
継承関係とisinstanceの挙動

isinstanceの最大の特徴は、継承関係を考慮して判定する点です。
実際にクラスを定義して試してみます。
# 動物クラス(親クラス)
class Animal:
pass
# 犬クラス(Animalを継承)
class Dog(Animal):
pass
# 猫クラス(Animalを継承)
class Cat(Animal):
pass
dog = Dog()
cat = Cat()
print(isinstance(dog, Dog)) # DogのインスタンスなのでTrue
print(isinstance(dog, Animal)) # Animalを継承しているのでTrue
print(isinstance(dog, Cat)) # Catとは無関係なのでFalse
True
True
False
ここで重要なのは、DogのインスタンスはDogであると同時にAnimalでもあるという点です。
そのため、isinstance(dog, Animal)もTrueになります。
一方で、catはAnimalのサブクラスですがDogとは関係がないので、isinstance(cat, Dog)はFalseになります。
組み込み型とisinstanceの実用例

組み込み型に対してisinstanceを使うことはとてもよくあります。
例えば、シーケンスかどうかをざっくり判定したいときの一例です。
def ensure_list(value):
# すでにリストならそのまま返す
if isinstance(value, list):
return value
# タプルやセットならリストに変換して返す
elif isinstance(value, (tuple, set)):
return list(value)
# それ以外は1要素のリストとして包む
else:
return [value]
print(ensure_list([1, 2, 3])) # [1, 2, 3]
print(ensure_list((4, 5))) # [4, 5]
print(ensure_list(10)) # [10]
print(ensure_list("abc")) # ['abc']
[1, 2, 3]
[4, 5]
[10]
['abc']
この例ではリスト、タプル、セットを「シーケンス的なもの」として同じように扱いたいため、複数の型をタプルで渡して判定しています。
typeの使い方と具体例
typeの基本構文と戻り値

typeの基本構文はとてもシンプルです。
type(オブジェクト)
戻り値はクラスオブジェクトそのものです。
つまり、intやstrなどのクラスそのものが返ってきます。
x = 10
y = "hello"
print(type(x)) # <class 'int'>
print(type(y)) # <class 'str'>
<class 'int'>
<class 'str'>
このように、typeは「このオブジェクトはどのクラスで作られているか」を直接教えてくれる関数です。
オブジェクトの正確な型を知りたい場合の使い方

継承を使っているクラス階層では、typeとisinstanceの違いがはっきり表れます。
class Animal:
pass
class Dog(Animal):
pass
dog = Dog()
print(isinstance(dog, Animal)) # True (DogはAnimalのサブクラス)
print(type(dog) is Animal) # False (実際の型はDog)
print(type(dog) is Dog) # True (Dogで作られたインスタンス)
True
False
True
「Dogかどうか」だけでなく「AnimalではなくDogそのものかどうか」を知りたい場合には、type(obj) is Dogのようにtypeを用いるのが適切です。
また、==で比較することもできますが、型オブジェクト同士の比較では通常isでの比較が一般的です。
print(type(dog) == Dog) # True
print(type(dog) is Dog) # True (こちらを推奨)
クラス定義とtypeの関係

少し進んだ話になりますが、Pythonではクラス自体もオブジェクトであり、そのクラスを作っているのがtypeクラスです。
つまり、ほとんどのクラスのtypeはtypeになります。
class MyClass:
pass
obj = MyClass()
print(type(obj)) # <class '__main__.MyClass'>
print(type(MyClass)) # <class 'type'>
<class '__main__.MyClass'>
<class 'type'>
このように、typeは「オブジェクトのクラスを返す関数」であると同時に、「クラスを作るためのクラス(メタクラス)」でもあります。
日常的なPython入門レベルではそこまで意識する必要はありませんが、クラスもオブジェクトであり、その正体を調べるのにもtypeを使うという点は覚えておくとよいです。
デバッグで役立つtypeの活用例

デバッグ時にprintとtypeを組み合わせて値の中身を確認するのは、とてもよく行われるパターンです。
def debug_print(value, name="value"):
# デバッグ用に値とその型を表示する関数
print(f"{name} = {value!r} (type: {type(value)})")
debug_print(10, "x")
debug_print([1, 2, 3], "numbers")
debug_print({"a": 1}, "data")
x = 10 (type: <class 'int'>)
numbers = [1, 2, 3] (type: <class 'list'>)
data = {'a': 1} (type: <class 'dict'>)
また、辞書などをたくさん扱っているときに「この値、本当にリストだと思っていたけど、実はタプルだった」といったバグが起きることがあります。
そのような場合にtypeで実際の型を確認すると原因究明がスムーズになります。
isinstanceとtypeの使い分け方
継承を考慮するならisinstanceを使う理由

オブジェクト指向では、親クラスをインターフェース(共通の窓口)として扱うことがよくあります。
そのような場面でtypeを使ってしまうと、サブクラスを親クラスとして扱えなくなり、拡張性を損ねます。
class Shape:
pass
class Circle(Shape):
pass
class Square(Shape):
pass
def process_shape(shape):
# Shapeかそのサブクラスなら受け入れる
if isinstance(shape, Shape):
print("図形として処理します")
else:
raise TypeError("Shape系のインスタンスを渡してください")
process_shape(Circle()) # OK
process_shape(Square()) # OK
図形として処理します
図形として処理します
もしここでtype(shape) is Shapeという判定にしてしまうと、CircleやSquareを受け入れられなくなってしまいます。
「この系統のオブジェクトならOK」という柔軟な設計をしたい場合は、必ずisinstanceを使うべきです。
正確な型一致が必要なときにtypeを使うケース

一方で、サブクラスを許可したくない、つまり「まさにこのクラスだけ」を対象にしたい場合にはtypeでの厳密な比較が必要になることがあります。
例えば、簡易的なシリアライズ(特定の型だけJSONに変換するなど)で、想定外のサブクラスが入り込むことを避けたい場面です。
def to_simple_value(value):
# 厳密にintだけ許可したいケース
if type(value) is int:
return value
# 厳密にstrだけ許可したいケース
elif type(value) is str:
return value
else:
raise TypeError("サポートされていない型です")
print(to_simple_value(10)) # 10
print(to_simple_value("test")) # test
# print(to_simple_value(True)) # コメントを外すとTypeError (boolはintのサブクラスだが許可しない)
10
test
ここでisinstance(True, int)はTrueになってしまいます。
なぜなら、Pythonではboolはintのサブクラスだからです。
あえてこれを拒否したい場合、type(value) is intのように厳密に型を比較する必要があります。
isinstanceとtypeのアンチパターンと注意点

isinstanceとtypeは便利ですが、乱用するとコードの柔軟性が下がり、保守が難しくなります。
いくつか代表的なアンチパターンを挙げます。
1. なんでもかんでも型で分岐する
# アンチパターンの例
def process(value):
if isinstance(value, int):
...
elif isinstance(value, str):
...
elif isinstance(value, list):
...
# 型ごとに分岐が延々と続く
このようなコードは、新しい型を追加するたびに関数を書き換えないといけないため、拡張性が低くなります。
可能であれば、ポリモーフィズム(オブジェクトに処理を任せる)を使い、オブジェクト側にメソッドを実装することを検討した方がよいです。
2. 継承を考慮すべき場所でtypeを使う
class Animal:
pass
class Dog(Animal):
pass
def feed(animal):
# 良くない例: typeでぴったり一致を要求してしまっている
if type(animal) is Animal:
print("餌を与えます")
else:
raise TypeError("Animal以外は受け付けません")
feed(Dog()) # TypeError になってしまう
このような場合は、isinstance(animal, Animal)を使うことでDogや他のサブクラスも受け入れられるように設計すべきです。
3. duck typingの文化と過剰なisinstance
Pythonはduck typingと言われる、「その型かどうかではなく、必要なメソッドや属性を持っているかどうか」で判断するスタイルを重視する文化があります。
そのため、本当に必要な場面以外でisinstanceによる型チェックを多用するのは、Pythonらしくない場合もあります。
例えば、「.appendメソッドが呼べればよい」だけなら、わざわざisinstance(x, list)でリストかどうかを確認せず、実際にx.append(...)を呼んでみて、失敗したら例外に任せる、というスタイルもあります。
実務コードでのベストプラクティスとサンプルコード

実務レベルでのisinstanceとtypeの使い分けの方針を簡単にまとめると、次のようになります。
- 継承や「系統」としての判定が絡むなら
isinstanceを使う - 通常の入力バリデーションや引数チェックも
isinstanceが基本 - サブクラスを明確に排除したいときや、セキュリティ・シリアライズなど厳密さが必要な時だけ
typeで比較 - 過度の型チェックは避け、可能な限りduck typingを活かす
以下では、実務でありがちな「設定値の読み込みとバリデーション」の例を通して、両者の使い分けを示します。
from typing import Any, Dict
class ConfigError(Exception):
"""設定値に問題がある場合に送出する例外クラス"""
pass
def validate_timeout(config: Dict[str, Any]) -> int:
"""
設定辞書から'timeout'を検証してint秒として返す関数です。
- intまたはfloatならOK(ただし0以上)
- 数値文字列ならintに変換
- それ以外はエラー
"""
if "timeout" not in config:
raise ConfigError("timeoutが設定されていません")
value = config["timeout"]
# 1. まず数値型(int, float)であるかをisinstanceで緩やかに判定
if isinstance(value, (int, float)):
if value < 0:
raise ConfigError("timeoutは0以上である必要があります")
# ここでは厳密にintを返したいので、typeで厳密比較しつつ変換方針を決める
if type(value) is float:
# floatなら小数点以下を切り捨てる、という仕様にする
return int(value)
# intならそのまま返す
return value
# 2. 文字列ならintに変換を試みる
if isinstance(value, str):
try:
ivalue = int(value)
except ValueError:
raise ConfigError("timeoutは数値文字列である必要があります")
if ivalue < 0:
raise ConfigError("timeoutは0以上である必要があります")
return ivalue
# 3. それ以外の型は受け付けない
raise ConfigError(f"timeoutの型が不正です: {type(value)}")
# サンプル実行
configs = [
{"timeout": 10},
{"timeout": 3.7},
{"timeout": "5"},
{"timeout": -1},
]
for cfg in configs:
try:
result = validate_timeout(cfg)
print(f"config={cfg!r} -> timeout={result}秒")
except ConfigError as e:
print(f"config={cfg!r} -> エラー: {e}")
config={'timeout': 10} -> timeout=10秒
config={'timeout': 3.7} -> timeout=3秒
config={'timeout': '5'} -> timeout=5秒
config={'timeout': -1} -> エラー: timeoutは0以上である必要があります
この例では、次のような使い分けをしています。
- 「数値型かどうか」の判定には
isinstance(value, (int, float))を使用 - 「intかfloatかで処理を分ける」場面では
type(value) is floatのように厳密な比較を使用
このように、基本はisinstanceで緩やかに判定しつつ、必要なところだけtypeで厳密比較するというスタイルが実務では扱いやすいです。
まとめ
isinstanceとtypeの違いは、「継承を考慮するか」「正確な型のみを見るか」に集約できます。
オブジェクト指向の観点からは、親クラスやインターフェースとして扱う場面が多いため、日常的な型チェックにはisinstanceを使うのが基本です。
一方で、サブクラスを明示的に排除したい特別な状況ではtypeによる厳密な比較が有効です。
Pythonのduck typingの文化も踏まえつつ、「柔軟さが必要な場面ではisinstance」「厳密さが必要な場面ではtype」という軸で使い分けると、読みやすく拡張性の高いコードを書けるようになります。
