Pythonで型を比較するには大きく2つの方法があります。type関数を使う方法とisinstance関数を使う方法はどこでどんな違いがあるのか、このクイズで確認しましょう。そして、第3の方法もあるって知ってましたか?
以下はAnimalクラスから派生するDogクラスとCatクラスのオブジェクトを生成し、その型をtype関数とisinstance関数で比較するコードだ。
class Animal:
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
dog = Dog()
cat = Cat()
print(type(dog) is Dog) # True
print(type(cat) is Cat) # True
print(type(dog) is Animal) # A
print(type(cat) is Animal) # B
print(isinstance(dog, Dog)) # True
print(isinstance(cat, Cat)) # True
print(isinstance(dog, Animal)) # C
print(isinstance(cat, Animal)) # D
このコードを実行すると、AからDの値がどうなるかを以下の選択肢から答えよ。
| 選択肢 | A | B | C | D |
|---|---|---|---|---|
| 1 | True | True | True | True |
| 2 | True | True | False | False |
| 3 | False | False | True | True |
| 4 | False | False | False | False |
| A、B、C、Dの値はどうなるかな? | ||||
問題文では「type(dog) is Dog」のようにtype関数の戻り値とDogクラスをis演算子で比較しています。でも、「type(dog) == Dog」とも書けますよね。実際に、筆者もよく書くことがあります。どっちの方がよいのか、ChatGPT(GPT-5)とGemini(2.5 Flash)、Claude(Opus 4.1)の3つの大規模言語モデル(LLM)に聞いてみました。その結果は最後に!
どうもHPかわさきです。
今回の問題はPythonの世界はよく言及されているので、そんなに難しくはないかもしれません。それだけじゃあナニなので、最後にはおまけの話題もちょびっと付け足しています。そこまで読んでくれるとうれしいです(棒読み)。今回、ここが短いのは書くネタを思いつけなかったからでーす。じゃ、先いってくださいネ。
答えは選択肢3のA:False、B:False、C:True、D:Trueです。
type関数は引数として与えられたオブジェクトの型を調べるもので、それをそのオブジェクトの親クラスとis演算子や==演算子で比較するとFalseになります。そのため、AとBの値はFalseです。
一方、isinstance関数は第1引数として与えたオブジェクトが、第2引数として与えたクラスかどうかを継承関係を考慮して判定します。そのため、CとDはTrueになります。
以下では、これらについてもう少し詳しく説明します。
既に述べましたが、type関数はオブジェクトの型を調べる(取得する)ためのものです。
dog_type = type(dog)
cat_type = type(cat)
print(dog_type) # <class '__main__.Dog'>
print(cat_type) # <class '__main__.Cat'>
この例ではdog_typeは__main__モジュール(Pythonの対話環境など)で定義されているDogクラスです。同様に、cat_typeはCatクラスです。これらをis演算子でその親クラスであるAnimalクラスと比較しているのが、問題文にあるAとBの部分です。
class Animal:
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
dog = Dog()
cat = Cat()
print(type(dog) is Dog) # True
print(type(cat) is Cat) # True
print(type(dog) is Animal) # A
print(type(cat) is Animal) # B
4つあるprint関数呼び出しの最初の2つはDogクラスとDogクラス、CatクラスとCatクラスをis演算子で比較しているので、その結果はもちろんTrueです。後の2つはDogクラスとAnimalクラス、CatクラスとAnimalクラスをis演算子で比較しているので、その結果はもちろんFalseになるというわけです。
対して、isinstance関数は継承関係を考慮して、第1引数として与えたオブジェクトが第2引数として与えたクラスであるかどうか、つまり「is-a」の関係が成立しているかどうかを調べるものです。これが問題文のCとDの部分です。
class Animal:
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
dog = Dog()
cat = Cat()
print(isinstance(dog, Dog)) # True
print(isinstance(cat, Cat)) # True
print(isinstance(dog, Animal)) # C
print(isinstance(cat, Animal)) # D
isinstance関数のドキュメントには「object引数がclassinfo引数に指定した型、またはその(直接、間接、または仮想の)サブクラスのインスタンスである場合にTrueを返します。objectが与えられた型のオブジェクトでない場合、この関数は常にFalseを返します」とあります(ここでobject引数とは第1引数のことで、classinfo引数とは第2引数のこと)。
そして、DogクラスとCatクラスはどちらもAnimalクラスを継承しています(DogクラスとCatクラスはAnimalクラスのサブクラス)。つまり、「Dog is an Animal」「Cat is an Animal」の関係が成り立っています。そのため、DogクラスとCatクラスのオブジェクトはどちらもAnimalクラスのオブジェクトでもあるということです。よって、CとDはTrueになります。
オブジェクトの型を知りたいだけであれば、type関数を使えばよいでしょう。実際、筆者も「print(type(some_obj))」のような使い方はよくしますし、得られたクラスをhelp関数に渡してドキュメントを参照したり、dir関数に渡してどんな属性やメソッドがあるかを確認したりしています。
そうではなく、オブジェクトの型を何らかの型と比較したい場合、特に継承関係を考慮して比較したい場合には、type関数で得た型をis演算子や==演算子で比較するのではなく、isinstance関数を使って判定することをオススメします。
あるいはmatch文のクラスパターンを使う方法も考えられます(Python 3.10以降)。
class Animal:
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
class Bird(Animal):
pass
dog = Dog()
cat = Cat()
bird = Bird()
objs = [dog, cat, bird]
for obj in objs:
match obj:
case Dog():
print('dog')
case Cat():
print('cat')
case Animal():
print('animal')
case _:
print('none')
この例ではAnimalを継承するBirdクラスも追加して、dogとcatとbirdの3つのオブジェクトを生成して、その型に応じて処理を切り替えるようにmatch文でパターンマッチしています。ここでは各クラスに属性がないので「case Dog()」のような書き方になっていますが、オブジェクトに属性があり、その属性の値ごとに処理を切り分けることも可能です(説明は省略)。
重要なのはクラスパターンではisinstance関数と同様に継承関係を考慮したパターンマッチが行われます。特に「case Animal():」は「isinstance(obj, Animal)」のような判定が行われると考えてください。そのため、上のサンプルコードではdogオブジェクトは「case Dog():」に、catオブジェクトは「case Cat():」に、birdオブジェクトは「case Animal():」にマッチします。
match文とクラスパターンを頭の中に入れておくと、if文とisinstance関数の組み合わせよりもシンプルにコードを書けるかもしれません。ただし、サブクラスのcase節をベースクラスのcase節よりも先に置く必要がある点には注意してください。この例では「case Animal():」が他のcase節よりも先にあると、dogオブジェクトもcatオブジェクトもそこでマッチしてしまいます。
では、type関数の戻り値を他の型と比較するときにはis演算子と==演算子のどちらを使うべきなのかを3つのLLMに聞いた結果です。
全員一致でis演算子でした! その理由も共通しています。その前に2つの演算子の違いを簡単にまとめると次のようになります。
そして、ある型が別の型と同じかどうかを調べるには、同値性ではなく同一性を比較すべきだからということになります。承知しました。次からはちゃんとis演算子を使うようにします(笑)。
Copyright© Digital Advantage Corp. All Rights Reserved.