- Python
Python型判定はisinstanceとtypeどちら?違いと使い分け早見表
Pythonの型判定は、基本的に isinstance(x, 型) を使います。isinstance は継承したクラス(サブクラス)も「その型」として認めるのに対し、type(x) は「その値を作ったクラスそのもの」を返すので、type(x) is 型 は完全一致だけを調べたいときに使う、という使い分けです。 型の名前を画面に出して確認したいだけなら type(x) や type(x).__name__ で十分です。
この記事では、同じ値に対して type() と isinstance() を実行した結果を横に並べた早見表を最初に示し、そのあとで継承、bool と int の関係、None、複数の型をまとめて調べる書き方、collections.abc や numbers を使った「性質」での判定、NumPy の型、match 文、そのまま貼って使える判定関数集までを順に説明します。掲載した出力はすべて Python 3.11.15 で実行したもので、同じスクリプトを Python 3.14.7(NumPy 2.4.4)でも動かし、注記した1か所を除いて同じ結果になることを確認しています。
型判定の早見表(type と isinstance の実行結果を横並び)
まず「どの書き方を選ぶか」の一覧です。
| やりたいこと | 書き方 | ポイント |
|---|---|---|
| 型を画面に出して確認したい | print(type(x)) / type(x).__name__ |
デバッグ向け。判定には使わなくてよい |
| ある型か(サブクラスも含めて)調べたい | isinstance(x, int) |
ふだんはこれ |
| 複数の型のどれかか調べたい | isinstance(x, (int, float)) / isinstance(x, int | float) |
タプルは全バージョン、| は 3.10 以降 |
| その型ちょうど(サブクラスを除く)か調べたい | type(x) is int |
bool を int から除きたいときなど |
| None か調べたい | x is None |
isinstance より is が定番 |
| ループで回せるか調べたい | isinstance(x, collections.abc.Iterable) |
文字列も True になる点に注意 |
| 辞書のように使えるか調べたい | isinstance(x, collections.abc.Mapping) |
dict 以外の辞書風オブジェクトも拾える |
| 数値か調べたい | isinstance(x, numbers.Number) |
Decimal や NumPy の数値も True |
| クラス同士の親子関係を調べたい | issubclass(Dog, Animal) |
第1引数はクラス |
| 型ごとに処理を分けたい | match x: と case int(): |
3.10 以降。中身の取り出しも同時にできる |
次に、同じ値を type() と isinstance() に渡した実行結果です。T は True、F は False を表します。
| 値 | type(x).__name__ |
type(x) == int |
isinstance(x, int) |
isinstance(x, (int, float)) |
numbers.Number |
abc.Iterable |
abc.Sequence |
abc.Mapping |
|---|---|---|---|---|---|---|---|---|
1 |
int | T | T | T | T | F | F | F |
True |
bool | F | T | T | T | F | F | F |
1.5 |
float | F | F | T | T | F | F | F |
'a' |
str | F | F | F | F | T | T | F |
None |
NoneType | F | F | F | F | F | F | F |
[1] |
list | F | F | F | F | T | T | F |
(1,) |
tuple | F | F | F | F | T | T | F |
{'a': 1} |
dict | F | F | F | F | T | F | T |
Dog()(自作クラス) |
Dog | F | F | F | F | F | F | F |
OrderedDict() |
OrderedDict | F | F | F | F | T | F | T |
Decimal('1') |
Decimal | F | F | F | T | F | F | F |
np.int64(1) |
int64 | F | F | F | T | F | F | F |
np.float64(1) |
float64 | F | F | T | T | F | F | F |
np.array([1]) |
ndarray | F | F | F | F | T | F | F |
Color.RED(IntEnum) |
Color | F | T | T | T | F | F | F |
太字のセルが、つまずきやすいところです。True は isinstance(True, int) が True になる、np.int64(1) は int ではない、文字列は Iterable であり Sequence でもある、NumPy 配列は Sequence ではない、の4点を押さえておくと、判定の書き間違いが大きく減ります。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Python型判定はisinstanceとtypeどちら?違いと使い分け早見表」
type と isinstance の違いは「継承を見るかどうか」
type は作ったクラスそのもの、isinstance は親クラスも認める
type(x) は、その値を作ったクラスを1つだけ返します。isinstance(x, C) は、x のクラスが C であるか、C を継承したクラスであれば True を返します。
class Animal: pass
class Dog(Animal): pass
d = Dog()
print(type(d)) # 作ったクラス
print(type(d).__name__) # クラス名の文字列
print(type(d) == Dog, type(d) == Animal)
print(isinstance(d, Dog), isinstance(d, Animal), isinstance(d, object))
print(type(d).__mro__) # 継承の並び
<class '__main__.Dog'>
Dog
True False
True True True
(<class '__main__.Dog'>, <class '__main__.Animal'>, <class 'object'>)
Dog は Animal を継承しているので、「Animal として扱えるか」という問いには isinstance(d, Animal) が True と答えます。一方 type(d) == Animal は False です。「この関数は Animal を受け取る」という書き方をした場合、サブクラスの Dog が渡されても動いてほしいことがほとんどなので、判定には isinstance が合います。
isinstance が見ているのは __mro__(メソッド解決順序。そのクラスと親クラスを順に並べたもの)です。この並びのどこかに指定した型があれば True になります。
判断表:どちらを使うか
| 場面 | 選ぶもの | 理由 |
|---|---|---|
| 引数の型で処理を分ける | isinstance |
サブクラスが渡されても同じ分岐に入る |
| 受け取った値が想定の型か確かめて例外を出す | isinstance |
同上 |
| 型名をログやエラーメッセージに出す | type(x).__name__ |
名前を取り出す用途 |
bool を整数扱いしたくない |
type(x) is int、または isinstance(x, int) and not isinstance(x, bool) |
bool は int のサブクラスのため |
| シリアライズなどで型ごとに厳密に書式を変える | type(x) is 型 |
サブクラスを別扱いにしたい |
| 2つの値が同じ型か比べる | type(a) is type(b) |
型同士の比較 |
type(x) == と type(x) is の違い
型はプログラムの中で1つしか存在しないオブジェクトなので、完全一致の判定は is で書けます。== はクラス(正確にはメタクラス)が __eq__ を定義していると結果が変わりうるため、完全一致を調べるなら is のほうが意図どおりになります。
class Meta(type):
def __eq__(cls, other): return True # 何と比べても等しいと答える
def __hash__(cls): return 1
class Odd(metaclass=Meta): pass
print(type(Odd()) == int, type(Odd()) is int)
print(type(1) == int, type(1) is int)
True False
True True
通常のクラスでは == と is の結果は同じです。違いが出るのは上のような特殊なクラスだけですが、is は「同じオブジェクトか」を調べるだけなので速度面でも読みやすさの面でも素直です。静的解析ツール(Ruff や flake8 の E721)も、type(x) == int と書くと is か isinstance への書き換えを提案します。
bool は int のサブクラス:True が整数として通る
bool は int を継承して作られています。そのため isinstance(True, int) は True です。
print(isinstance(True, int), type(True) == int, True + True)
print(bool.__mro__)
print(issubclass(bool, int))
True False 2
(<class 'bool'>, <class 'int'>, <class 'object'>)
True
「数量は整数で受け取る」という関数に True が渡されると、isinstance(x, int) の検査を通り、1 として計算が進みます。bool を整数として受け取りたくないときは、先に bool を除きます。
def set_quantity(n):
# 直す前: if not isinstance(n, int):
if isinstance(n, bool) or not isinstance(n, int):
raise TypeError(f"整数を渡してください: {type(n).__name__}")
return n
print(set_quantity(3))
try:
set_quantity(True)
except TypeError as e:
print("TypeError:", e)
3
TypeError: 整数を渡してください: bool
type(n) is int と書いても bool を除けますが、その場合は IntEnum など int を継承したほかのクラスも除かれます。どこまで受け入れたいかで選びます。
None の判定は is None
None の型は NoneType で、3.10 以降は types.NoneType として参照できます。ただし判定は x is None と書くのが定番です。
import types
x = None
print(type(x), x is None)
print(isinstance(x, type(None)), isinstance(x, types.NoneType))
print(isinstance(x, int | None)) # 3.10 以降。Union の中では None と書ける
<class 'NoneType'> True
True True
True
None は1つしか存在しない値なので、is で比べるのが最も短く正確です。if not x: と書くと、0・空文字・空リストも同じ分岐に入るため、「None かどうか」を調べたいときは is None を使います。
複数の型をまとめて判定する(タプルと int | str)
isinstance の第2引数には、型のタプルを渡せます。Python 3.10 以降は int | str のように | でつないだ書き方も使えます。
print(isinstance(1, (int, str))) # どのバージョンでも使える
print(isinstance(1, int | str)) # 3.10 以降
print(isinstance(1.5, int | str))
print(type(int | str))
True
True
False
<class 'types.UnionType'>
最後の行は Python 3.11 での表示です。Python 3.14 では <class 'typing.Union'> と表示されました。表示される型の名前が変わっただけで、判定の結果は同じです。
リストや文字列を渡すとエラーになります。複数指定はタプルか | で書きます。
isinstance(1, [int, str])
isinstance(1, "int")
TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union
| 書き方 | 使えるバージョン | メモ |
|---|---|---|
isinstance(x, (int, str)) |
すべて | 入れ子のタプルも可 |
isinstance(x, int | str) |
3.10 以降 | 型ヒントと同じ見た目で書ける |
isinstance(x, typing.Union[int, str]) |
3.10 以降 | typing.Optional[int] も同様に True/False を返す |
isinstance(x, [int, str]) |
使えない | TypeError になる |
issubclass:クラス同士の親子関係を調べる
isinstance が「値とクラス」の関係を調べるのに対し、issubclass は「クラスとクラス」の関係を調べます。
print(issubclass(Dog, Animal), issubclass(bool, int))
print(issubclass(Dog, Dog)) # 自分自身も True
print(issubclass(int, (str, int))) # タプルも渡せる
issubclass(Dog(), Animal) # 値を渡すとエラー
True True
True
True
TypeError: issubclass() arg 1 must be a class
isinstance(x, C) は、おおよそ issubclass(type(x), C) と同じ意味です。クラスそのものを引数で受け取る関数(プラグインの登録、設定クラスの検査など)では issubclass を使います。
collections.abc で「性質」を判定する
「リストか」ではなく「ループで回せるか」「添字で取り出せるか」「辞書のように使えるか」を調べたいときは、collections.abc の抽象基底クラス(Abstract Base Class。特定のメソッドを持つことを表す型)を使います。
主な型の対応表(実行結果)
import collections.abc as abc
| 値の型 | Iterable | Iterator | Sized | Sequence | MutableSequence | Mapping | Set | Hashable |
|---|---|---|---|---|---|---|---|---|
| str | T | F | T | T | F | F | F | T |
| bytes | T | F | T | T | F | F | F | T |
| list | T | F | T | T | T | F | F | F |
| tuple | T | F | T | T | F | F | F | T |
| range | T | F | T | T | F | F | F | T |
| dict | T | F | T | F | F | T | F | F |
| set | T | F | T | F | F | F | T | F |
| ジェネレータ | T | T | F | F | F | F | F | T |
| dict_keys | T | F | T | F | F | F | T | F |
| np.ndarray | T | F | T | F | F | F | F | F |
| deque | T | F | T | T | T | F | F | F |
| 抽象基底クラス | 意味 | 判定の根拠 |
|---|---|---|
Iterable |
for で回せる | __iter__ を持つ |
Iterator |
next() で1つずつ取り出せる |
__iter__ と __next__ を持つ |
Sized |
len() が使える |
__len__ を持つ |
Sequence |
順番があり添字で取り出せる | 登録されたクラス、または継承したクラス |
Mapping |
キーで値を引ける | 同上 |
Callable |
呼び出せる | __call__ を持つ(callable(x) でも可) |
文字列は Iterable であり Sequence でもあります。 「リストやタプルなら1件ずつ処理し、文字列なら1件として扱う」という分岐では、文字列を先に除く必要があります(後述の判定関数集に関数を用意しました)。
Iterable の判定は __iter__ の有無で決まる
Iterable は、クラスが継承していなくても __iter__ を持っていれば True になります。一方、__getitem__ だけで反復できるクラスは isinstance では False になります。
class MyIter:
def __iter__(self): return iter([1])
class OldSeq: # __getitem__ だけを持つ
def __getitem__(self, i):
if i > 2: raise IndexError
return i
print(isinstance(MyIter(), abc.Iterable))
print(isinstance(OldSeq(), abc.Iterable), list(OldSeq()))
True
False [0, 1, 2]
OldSeq は list() に渡すと反復できますが、isinstance(..., Iterable) は False です。反復できるかを確実に知るには iter(obj) を実際に呼んでみる方法があります。この点は「ダックタイピング」の節で扱います。
numbers.Number で数値かどうかを判定する
int と float だけなら isinstance(x, (int, float)) で足りますが、Decimal、Fraction、NumPy の数値まで含めて「数値か」を調べるなら numbers モジュールが便利です。
| 値 | Number | Real | Integral | isinstance(x, int) |
isinstance(x, float) |
|---|---|---|---|---|---|
1 |
T | T | T | T | F |
True |
T | T | T | T | F |
1.5 |
T | T | F | F | T |
1+2j |
T | F | F | F | F |
Fraction(1, 2) |
T | T | F | F | F |
Decimal('1.5') |
T | F | F | F | F |
np.int64(1) |
T | T | T | F | F |
np.float32(1) |
T | T | F | F | F |
'1' |
F | F | F | F | F |
numbers.Number:数値全般(複素数も含む)numbers.Real:実数。大小比較や四捨五入ができる数値numbers.Integral:整数
Decimal は Number ですが Real ではありません。 float と混ぜた計算ができない設計のため、実数の階層には登録されていません。Decimal も受け入れたい場合は isinstance(x, (numbers.Real, Decimal)) と明示します。
NumPy の型を判定する
NumPy の配列から取り出した1要素は、Python の int ではなく np.int64 などの NumPy スカラーです。
import numpy as np
a = np.array([1, 2, 3]); f = np.array([1.5])
print(type(a), a.dtype, type(a[0]))
print(isinstance(a[0], int), isinstance(a[0], np.integer))
print(isinstance(f[0], float), isinstance(f[0], np.floating), isinstance(np.float32(1), float))
print(np.issubdtype(a.dtype, np.integer), np.issubdtype(f.dtype, np.floating), np.issubdtype(a.dtype, np.number))
print(isinstance(np.bool_(True), bool), isinstance(np.str_('a'), str))
print(type(a.tolist()[0]), type(a[0].item()))
<class 'numpy.ndarray'> int64 <class 'numpy.int64'>
False True
True True False
True True True
False True
<class 'int'> <class 'int'>
| 調べたいこと | 書き方 |
|---|---|
| 配列か | isinstance(x, np.ndarray) |
| NumPy の整数スカラーか | isinstance(x, np.integer) |
| NumPy の浮動小数点スカラーか | isinstance(x, np.floating) |
| 配列の要素型が整数か | np.issubdtype(arr.dtype, np.integer) |
| Python の数値と NumPy の数値をまとめて | isinstance(x, numbers.Number) |
| Python の型に戻したい | x.item() / arr.tolist() |
np.float64 は Python の float を継承しているので isinstance(x, float) が True ですが、np.float32 と np.int64 は継承していません。NumPy の値が混ざる可能性があるなら、numbers.Integral や numbers.Real で判定するか、.item() で Python の型に戻してから扱うと統一できます。 JSON に書き出すときに np.int64 がそのままでは変換できない、という場面でも .item() や .tolist() が役立ちます。
match 文のクラスパターンで型ごとに分岐する
Python 3.10 以降では、match 文の case int(): という書き方(クラスパターン)で型による分岐を書けます。内部では isinstance と同じ判定が行われます。
def describe(x):
match x:
case bool(): # int より先に書く
return "bool"
case int() | float():
return "数値"
case str() as s:
return f"文字列({len(s)}文字)"
case [int(), int()]:
return "intが2つのシーケンス"
case list() | tuple():
return "リストかタプル"
case {"id": int(i)}:
return f"idつきの辞書 id={i}"
case Dog():
return "Dog"
case None:
return "None"
case _:
return "その他"
for v in [True, 1, 1.5, "abc", [1, 2], [1, "a"], (1, 2, 3), {"id": 7, "x": 1}, None, {1}]:
print(repr(v), "->", describe(v))
True -> bool
1 -> 数値
1.5 -> 数値
'abc' -> 文字列(3文字)
[1, 2] -> intが2つのシーケンス
[1, 'a'] -> リストかタプル
(1, 2, 3) -> リストかタプル
{'id': 7, 'x': 1} -> idつきの辞書 id=7
None -> None
{1} -> その他
押さえておきたい点は3つです。
case int():のかっこは必須です。case int:と書くと「何にでも一致してintという名前の変数に代入する」という意味になります。case bool():はcase int():より先に書きます。 順番を逆にすると、Trueは先にあるint()に一致します(下の実行結果)。case [int(), int()]のようなシーケンスパターンは、リストとタプルに一致し、文字列には一致しません。文字列を誤って1文字ずつ分解する心配がありません。
def m2(x):
match x:
case int(): return "int"
case bool(): return "bool"
print(m2(True))
int
if isinstance(...) / elif isinstance(...) を3つ以上並べる場面では、match に書き換えると「型の判定」と「中身の取り出し」を1行で書けて見通しがよくなります。
コピペできる判定関数集
よく必要になる判定を関数にまとめました。import も含めてそのまま貼り付けて使えます。
import collections.abc as abc
import numbers
def is_number(x, *, allow_bool=False):
"""数値か(int / float / complex / Decimal / Fraction / NumPy の数値)。bool は既定で除く"""
if isinstance(x, bool):
return allow_bool
return isinstance(x, numbers.Number)
def is_real_number(x):
"""大小比較できる実数か(bool・複素数・Decimal は除く)"""
return isinstance(x, numbers.Real) and not isinstance(x, bool)
def is_iterable_not_str(x):
"""文字列・バイト列を除いた、for で回せるものか"""
return isinstance(x, abc.Iterable) and not isinstance(x, (str, bytes, bytearray))
def is_list_like(x):
"""リストのように順番と添字を持つか(list / tuple / range / deque など。文字列は除く)"""
return isinstance(x, abc.Sequence) and not isinstance(x, (str, bytes, bytearray))
def is_int_string(s):
"""整数として解釈できる文字列か"""
if not isinstance(s, str):
return False
try:
int(s)
except ValueError:
return False
return True
def is_float_string(s):
"""小数として解釈できる文字列か"""
if not isinstance(s, str):
return False
try:
float(s)
except ValueError:
return False
return True
def ensure_list(x):
"""None は空リスト、単独の値は1要素のリスト、反復できるものはリストにそろえる"""
if x is None:
return []
if is_iterable_not_str(x):
return list(x)
return [x]
実行して確かめた結果です。
| 関数 | 入力 | 結果 |
|---|---|---|
is_number |
1, 1.5, True, "1", Decimal("1"), np.int64(1), None |
T, T, F, F, T, T, F |
is_real_number |
1, 1.5, True, 1+2j, Fraction(1, 2), Decimal("1") |
T, T, F, F, T, F |
is_iterable_not_str |
[1], (1,), {1}, {"a": 1}, range(3), "abc", b"ab", 1, None, np.array([1]) |
T, T, T, T, T, F, F, F, F, T |
is_list_like |
[1], (1,), range(3), {1}, {"a": 1}, "abc", np.array([1]), deque() |
T, T, T, F, F, F, F, T |
is_int_string |
"12", "-3", " 7 ", "12", "1_000", "1.5", "abc", "", 12, "0x1f" |
T, T, T, T, T, F, F, F, F, F |
is_float_string |
"1.5", "1e3", "nan", "inf", "abc", "1,000" |
T, T, T, T, F, F |
ensure_list |
None, "abc", (1, 2), 5 |
[], ['abc'], [1, 2], [5] |
「整数として解釈できる文字列か」に str.isdigit を使うときの注意
str.isdigit() は手軽ですが、int() が受け付ける範囲と一致しません。
| 文字列 | int(s) できるか |
s.isdigit() |
|---|---|---|
"12" |
できる | True |
"-3" |
できる | False |
" 7 "(前後に空白) |
できる | False |
"12"(全角数字) |
できる | True |
"1_000" |
できる | False |
"²"(上付きの2) |
できない | True |
int("²")
ValueError: invalid literal for int() with base 10: '²'
「int() に渡せるか」を知りたいなら、実際に int() を呼んで ValueError を捕まえる is_int_string の形が最も正確です。 半角数字だけを受け付けたい入力欄なら、正規表現 re.fullmatch(r"-?[0-9]+", s) のように受け付ける範囲を明示します。is_float_string は "nan" や "inf" も True になるので、受け付けたくない場合は math.isfinite(float(s)) を追加します。
型ヒントは実行時チェックではない
型ヒント(def add(a: int, b: int) -> int のような注釈)は、エディタや mypy・Pyright といった型チェッカーが読むための情報で、Python 本体は実行時に検査しません。
def add(a: int, b: int) -> int:
return a + b
print(add("1", "2"))
print(add.__annotations__)
from dataclasses import dataclass
@dataclass
class P:
x: int
print(P("abc"))
12
{'a': <class 'int'>, 'b': <class 'int'>, 'return': <class 'int'>}
P(x='abc')
int と注釈した引数に文字列を渡しても、そのまま文字列の連結として実行されます。dataclass も同じで、注釈と異なる型の値をそのまま受け取ります。実行時に型を保証したい入り口(外部から届いた JSON、設定ファイル、ユーザー入力)では、isinstance による検査や、検証用ライブラリを別に用意します。 型ヒントの書き方そのものは、別記事「Pythonの型ヒントの書き方|3.12以降の新しい書き方まで」で扱っています。
list[int] を isinstance に渡すと TypeError になる
型ヒントで使う list[int] や dict[str, int] を、そのまま isinstance に渡すことはできません。
| 式 | 結果 |
|---|---|
isinstance([1, 2], list[int]) |
TypeError: isinstance() argument 2 cannot be a parameterized generic |
isinstance({'a': 1}, dict[str, int]) |
TypeError: isinstance() argument 2 cannot be a parameterized generic |
isinstance([1, 2], typing.List[int]) |
TypeError: Subscripted generics cannot be used with class and instance checks |
isinstance('a', typing.Literal['a']) |
TypeError: Subscripted generics cannot be used with class and instance checks |
isinstance(1, typing.Any) |
TypeError: typing.Any cannot be used with isinstance() |
isinstance([1, 2], typing.List) |
True(添字なしなら動く) |
isinstance(1, typing.Union[int, str]) |
True |
isinstance(1, typing.Optional[int]) |
True |
isinstance が調べるのは「入れ物の型」までで、要素の型までは見ません。要素の型も確かめたいときは、入れ物と中身を分けて書きます。
def is_list_of_int(x):
return isinstance(x, list) and all(type(v) is int for v in x)
print(is_list_of_int([1, 2]), is_list_of_int([1, "a"]), is_list_of_int([True]))
True False False
type(v) is int としているのは、True を整数として数えないためです。bool も整数として認めるなら isinstance(v, int) にします。
ダックタイピングと try/except:型を調べずに使ってみる
Python には「必要な操作ができるなら、型は問わない」という考え方(ダックタイピング)があります。事前に型を調べる代わりに、使ってみて、できなかったら例外で受け止める書き方です。
def total_len(x):
try:
return len(x)
except TypeError as e:
return f"TypeError: {e}"
print(total_len([1, 2]), total_len("abc"), total_len(5))
try:
iter(5)
except TypeError as e:
print("TypeError:", e)
print(hasattr([], "__iter__"), hasattr(5, "__iter__"), callable(len), callable(1))
2 3 TypeError: object of type 'int' has no len()
TypeError: 'int' object is not iterable
True False True False
| 方法 | 向いている場面 | 気をつける点 |
|---|---|---|
isinstance(x, 型) |
型ごとに処理の内容が変わる。入り口で早めに知らせたい | 想定外の互換クラスを締め出すことがある |
isinstance(x, abc.Iterable) など |
「性質」で分けたい | 文字列も Iterable |
try / except |
操作できるかだけが重要 | except の範囲を狭く保つ(処理の中の別の TypeError も拾うため) |
hasattr(x, "close") |
特定のメソッドの有無を見たい | 属性があっても呼び出せるとは限らない |
callable(x) |
関数として呼べるか | クラスも True になる |
型を調べる書き方と、使ってみる書き方は、どちらか一方に決めるものではありません。外部から入ってくるデータの入り口では isinstance で早めに確認し、内部の関数同士では型ヒントとダックタイピングに任せる、という組み合わせが読みやすく保ちやすい形です。
Protocol と runtime_checkable:メソッドの有無で isinstance する
typing.Protocol は「このメソッドを持っていれば、この型として扱う」という約束を型ヒントで表す仕組みです。そのままでは isinstance に渡せませんが、@runtime_checkable を付けると実行時の判定にも使えます。
from typing import Protocol, runtime_checkable
class Closable(Protocol):
def close(self) -> None: ...
@runtime_checkable
class Closable2(Protocol):
def close(self) -> None: ...
class Res:
def close(self): return 1
class Bad:
close = 5 # メソッドではなくただの値
try:
isinstance(Res(), Closable)
except TypeError as e:
print("TypeError:", e)
print(isinstance(Res(), Closable2), isinstance(1, Closable2), isinstance(Bad(), Closable2))
TypeError: Instance and class checks can only be used with @runtime_checkable protocols
True False True
Bad は close がメソッドではなく数値ですが、True になっています。runtime_checkable の判定は「その名前の属性があるか」だけを見ており、引数や戻り値の型までは確かめません。 細かい検査は型チェッカーに任せ、実行時の isinstance は大まかな確認として使うのが合っています。Protocol と抽象クラスの選び方は、別記事「Python 抽象クラスとProtocol|ABCとの違い・使い分け早見表」で詳しく扱っています。
判定の速度を測る
500万回の判定にかかった時間を timeit で測りました(Python 3.11.15、この記事の検証用コンテナでの値。環境で変わります)。
| 式 | 500万回の所要時間 |
|---|---|
type(x) is int |
0.085 秒 |
isinstance(x, int) |
0.082 秒 |
isinstance(x, (str, float, int)) |
0.361 秒 |
isinstance(x, collections.abc.Iterable) |
0.485 秒 |
type(x) is int と isinstance(x, int) はほぼ同じ速さです。タプルや抽象基底クラスは少し時間がかかりますが、1回あたり 0.1 マイクロ秒未満です。速度を理由に type を選ぶ必要はなく、意味(サブクラスを含めるかどうか)で選んで問題ありません。
つまずきやすい点のチェックリスト
| 症状 | 原因 | 直し方 |
|---|---|---|
True が整数として通る |
bool は int のサブクラス | isinstance(x, bool) を先に除く |
| 文字列が1文字ずつ処理される | str は Iterable かつ Sequence | is_iterable_not_str のように文字列を除く |
NumPy の値が isinstance(x, int) で False |
np.int64 は int を継承していない |
numbers.Integral か np.integer で判定 |
isinstance(x, list[int]) で TypeError |
添字つきの型は渡せない | isinstance(x, list) と要素の検査を分ける |
isinstance(x, [int, str]) で TypeError |
リストは渡せない | タプル (int, str) か int | str |
case int: が何にでも一致する |
かっこがないと変数への代入になる | case int(): と書く |
サブクラスを渡すと type(x) == 親 が False |
type は完全一致 | isinstance を使う |
| 型ヒントを書いたのに別の型が通る | 型ヒントは実行時に検査されない | 入り口で isinstance を使う |
Decimal が numbers.Real で False |
Decimal は Real に登録されていない | (numbers.Real, Decimal) と明示 |
よくある質問
Q. 結局、isinstance と type のどちらを使えばよいですか?
A. 判定には isinstance を使います。サブクラスも同じ型として扱えるので、あとからクラスを継承で拡張しても分岐が保たれます。type は、型名を表示したいとき(type(x).__name__)と、サブクラスを含めず完全一致で調べたいとき(type(x) is int)に使います。
Q. 変数の型を確認するだけなら何を書けばよいですか?
A. print(type(x)) で <class 'int'> のように表示されます。クラス名だけが欲しいときは type(x).__name__ です。NumPy 配列なら arr.dtype、pandas の DataFrame なら df.dtypes で要素の型を確認できます(pandas は今回の検証対象外で、一般に知られた使い方です)。
Q. isinstance(x, int | str) はどのバージョンから使えますか?
A. Python 3.10 以降です。3.9 以前でも動かす必要があるなら、タプルの isinstance(x, (int, str)) を使います。結果は同じです。
Q. リストの中身がすべて int かどうかを isinstance で調べられますか?
A. isinstance(x, list[int]) は TypeError になります。isinstance(x, list) and all(isinstance(v, int) for v in x) のように、入れ物と中身を分けて書きます。データの構造が複雑な場合は、検証用のライブラリを使うと定義をまとめて書けます。
まとめ
- 型の判定は
isinstance(x, 型)が基本。サブクラスも True になる type(x) is 型は完全一致を調べたいときだけ使う。型名の表示はtype(x).__name__- bool は int のサブクラス。整数だけを受け取りたいときは bool を先に除く
- 複数の型はタプル
(int, str)か、3.10 以降のint | strで指定する - 「回せるか」「数値か」は
collections.abcとnumbersで判定する。文字列が Iterable である点と、NumPy の整数が int ではない点に注意 - 型ヒントは実行時に検査されない。
list[int]を isinstance に渡すと TypeError になる - 型ごとの分岐が増えたら
match文のクラスパターンが読みやすい
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Python型判定はisinstanceとtypeどちら?違いと使い分け早見表」
株式会社RJCでは、Pythonの型判定の使い分けを仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。