4.

Python の型|動的型付けと型ヒント・組み込み型一覧・isinstance の使い方

編集
この記事の要点
  • Python は動的型付け。型は変数ではなく値(オブジェクト)が持つ
  • プリミティブ型という区別は無く、intstr もすべてオブジェクト
  • 型の確認は type(x)、判定は isinstance(x, int)継承を考慮する isinstance を使う
  • boolint のサブクラス。isinstance(True, int)True になる
  • 型ヒントは実行時に検査されない。効くのはエディタと mypy などの静的解析

型は値が持っている

x = 10
print(type(x))       # <class 'int'>

x = "十"
print(type(x))       # <class 'str'>   同じ名前に別の型を入れられる

print(type(10).__name__)   # int   名前だけ取り出す

変数に型を宣言しないため、代入するたびに扱う型が変わり得ます。「この変数は必ず int」という保証は、書き手の規律か型ヒント+静的解析で担保します。

組み込み型の一覧

分類リテラル例変更可否
整数int10 0xff 1_000_000不可
小数float1.5 2e-3不可
複素数complex1 + 2j不可
真偽boolTrue False不可
文字列str"abc"不可
バイト列bytesb"abc"不可
リストlist[1, 2]
タプルtuple(1, 2)不可
辞書dict{"a": 1}
集合set{1, 2}
なしNoneTypeNone

整数に上限はありません。2 ** 200 もそのまま計算でき、桁あふれは起きません。

int と float の落とし穴

print(10 / 3)        # 3.3333333333333335   / は必ず float を返す
print(10 // 3)       # 3                    切り捨て除算
print(type(10 / 5))  # <class 'float'>   割り切れても float

print(0.1 + 0.2)             # 0.30000000000000004
print(0.1 + 0.2 == 0.3)      # False   浮動小数点の誤差

from decimal import Decimal
print(Decimal("0.1") + Decimal("0.2") == Decimal("0.3"))   # True

import math
print(math.isclose(0.1 + 0.2, 0.3))    # True   誤差を許した比較

金額計算のように誤差が許されない場面では Decimal を使い、文字列から生成します(Decimal(0.1) だと誤差ごと取り込んでしまいます)。

bool は int の一種

print(True + True)                 # 2
print(isinstance(True, int))       # True    ← bool は int のサブクラス
print(sum([True, False, True]))    # 2       条件に合う件数を数えられる

# 「int だけ受け付けたい」なら bool を弾く必要がある
def need_int(v):
    if isinstance(v, bool) or not isinstance(v, int):
        raise TypeError("int を渡してください")
    return v

型の確認と判定

x = 10

print(type(x) is int)          # True   厳密に一致するかだけ見る
print(isinstance(x, int))      # True   サブクラスも True になる
print(isinstance(x, (int, float)))     # True   複数候補は タプル で渡す

class Animal: pass
class Dog(Animal): pass

d = Dog()
print(type(d) is Animal)       # False  ← 継承を見てくれない
print(isinstance(d, Animal))   # True   こちらが通常の判定

判定には isinstance() を使いますtype(x) == int はサブクラスを弾いてしまい、意図しない分岐になります。詳しくは 型の確認 を参照してください。

型変換(キャスト)

print(int("42"))         # 42
print(int("0x1f", 16))   # 31    基数を指定
print(int(3.9))          # 3     切り捨て(四捨五入ではない)
print(float("1.5"))      # 1.5
print(str(42))           # '42'
print(list("abc"))       # ['a', 'b', 'c']
print(bool([]))          # False 空のものは偽

int("42abc")             # ValueError: invalid literal for int() with base 10
int(None)                # TypeError

Python は暗黙の型変換をほとんど行いません。"1" + 1TypeError になります。int() は失敗すると例外を投げるので、外部入力を変換するときは try で囲みます(strからintへの型変換)。

型ヒントの書き方

def total(prices: list[int], rate: float = 0.1) -> int:
    return int(sum(prices) * (1 + rate))

name: str = "田中"
scores: dict[str, int] = {"math": 80}
maybe: int | None = None            # Python 3.10 以降の書き方

from typing import Any, Optional
old_style: Optional[int] = None     # int | None と同じ
anything: Any = "何でも入る"

total(["100"])          # 実行できてしまう。エラーにはならない

型ヒントは注釈であって強制ではありません。実際に検査するには次を走らせます。

pip install mypy
mypy app.py            # 型の不一致を指摘してくれる

文字列とバイト列の区別

s = "あ"
b = s.encode("utf-8")

print(type(s), len(s))       # <class 'str'> 1     文字数
print(type(b), len(b))       # <class 'bytes'> 3   バイト数
print(b)                     # b'\xe3\x81\x82'
print(b.decode("utf-8"))     # あ

s + b                        # TypeError: can only concatenate str (not "bytes") to str

ファイルやネットワークから読んだ生データは bytes です。文字として扱う前に必ず decode() するのが原則で、混ぜると TypeError になります。文字化けの多くは、ここでエンコーディングを指定していないことが原因です。

None は型ではなく唯一の値

x = None
print(type(x))               # <class 'NoneType'>
print(x is None)             # True    判定はこれ
print(x == None)             # True だが is None を使う

def f():
    pass
print(f())                   # None    return を書かない関数は None を返す

lst = [3, 1, 2]
print(lst.sort())            # None    ← その場で並べ替えて None を返す
print(sorted(lst))           # [1, 2, 3]   新しいリストを返す

「メソッドを呼んだのに None が入る」のは、その場で書き換える種類のメソッド(sort() append() reverse())を代入に使ったときの定番の失敗です。

コレクションの中身の型

mixed = [1, "a", None, 3.5]        # 型が混ざっていても入る
print({type(v).__name__ for v in mixed})   # {'int', 'str', 'NoneType', 'float'}

# 型ヒントで中身まで表す
scores: dict[str, list[int]] = {"math": [80, 90]}
pair: tuple[str, int] = ("田中", 30)
either: str | int = 1

# 辞書のキーになれるのはイミュータブルなものだけ
{(1, 2): "ok"}                     # タプルは可
{[1, 2]: "ng"}                     # TypeError: unhashable type: 'list'

辞書のキーや集合の要素にはハッシュ可能な(=変更できない)値しか使えません。リストをキーにしたい場合はタプルに変換します。

型を調べる実務的な手順

value = get_something()

print(type(value))          # クラス名
print(repr(value))          # 見た目で紛れる型を区別できる("1" と 1 など)
print(dir(value))           # 使えるメソッド一覧
print(vars(value))          # インスタンスの属性(__dict__)

import inspect
print(inspect.getmro(type(value)))   # 継承のたどり方

print(x) だと 1"1" がどちらも 1 と表示されます。調査中は repr() を使うとクォートの有無で型が分かります。

関連

編集
Post Share
子ページ
  1. 型の確認
  2. strからintへの型変換
  3. 辞書型を文字列に変換する方法
同階層のページ
  1. 基本的なルール
  2. 変数
  3. 演算子
  4. 標準ライブラリ
  5. 外部ライブラリ
  6. 制御構文
  7. リスト(配列)
  8. タプル
  9. セット
  10. 辞書(dict)
  11. クラスとメソッド
  12. 継承の概念と必要性
  13. 継承の構文
  14. コンストラクタ
  15. cookieの値の設定と取得
  16. 例外処理
  17. 例外を文字列で出力する方法
  18. httpリクエスト(curl)をする方法
  19. Responseオブジェクトの中身の確認
  20. 変数が空かどうか判定する方法
  21. タイムゾーンの設定と現在日時の取得と文字列化
  22. シングルクォーテーションとダブルクォーテーションの違い

最近更新/作成されたページ