3.9 基本データ型と型変換

(1/1)
さまざまなデータのイメージ
これまでのサンプル・プログラムを通じ、Pythonでは、整数、小数、文字列の扱いが異なることがわかった。Pythonに限らず、プログラミング言語には扱うデータの種類に応じたデータ型という概念があり、異なるデータ型の間でのデータ変換処理が用意されている。
Pythonは、データ型を自動認識・変換する動的型付け言語で、内部的にはきめ細かなデータ型をもっている。これに対し、プログラマがデータ型を指定し、指定した以外のデータ型にするには変換指定をしなければならないプログラミング言語を静的型付け言語と呼ぶ。Python, JavaScript, PHP, Rubyは動的型付け言語、C/C++, C#, Java, Swift, Goは静的型付け言語だ。
今回は、初級~中級者プログラミングで登場するデータ型について学ぶ。

(2024年7月20日)docstringをGoogleスタイルに変更.

目次

サンプル・プログラム

組込み型

最初に、Python に標準で用意されているデータ型(組込み型)を一覧表に整理しておこう。
組込み型
分類データ型種類ミュータブルイテラブルシーケンス
数値 int整数型 ×××
float浮動小数型 ×××
complex複素数型 ×××
データ str文字列型 ×
bytesバイナリ型 ×
コンテナ listリスト型
tupleリスト型(変更不可) ×
dict辞書型 ×
set集合型 ×
その他 bool論理型 ×××
NoneType値がない型 ×××
ミュータブルとは、「3.5 代入と代入演算子」で学んだとおり、再代入したときに内容が変更可能であること。ミュータブルの項目が×のデータ型はイミュータブルであることを意味する。
イテラブルとは、繰り返し可能なことを意味する。たとえば for文のイテレータになるデータ型はイテラブルだ。
シーケンスとは、そのデータ型の内部要素が順序を持つ、つまりインデックスを用いてアクセスができるデータ型を意味する。

こうしてみると、Python には他のプログラミング言語にある配列型がなく、代わりにコンテナとして、リスト型辞書型集合型に分かれている。コンテナについては、「第5章 関数とコンテナ」で説明する。

整数型と小数型

Python では、type関数を使って、データ型を取得することができる。
a = -123		# 整数表記
print("a = " + str(a) + " " + str(type(a)))
a = 12.3		# 小数表記
print("a = " + str(a) + " " + str(type(a)))
a = 12.0		# 小数表記
print("a = " + str(a) + " " + str(type(a)))
a = 1.23e+4		# 指数表記
print("a = " + str(a) + " " + str(type(a)))
a = 4.56e-3		# 指数表記
print("a = " + str(a) + " " + str(type(a)))
a = 0xABCD		# 16進数表記(整数)
print("a = " + str(a) + " " + str(type(a)))
a = 0o567		# 8進数表記(整数)
print("a = " + str(a) + " " + str(type(a)))
a = 0b01011		# 2進数表記(整数)
print("a = " + str(a) + " " + str(type(a)))
a = 1_234_567	# セパレータ表記(整数)
print("a = " + str(a) + " " + str(type(a)))
a = 3.141_592_653_49	# セパレータ表記(小数)
print("a = " + str(a) + " " + str(type(a)))

-123 は整数なので、それを代入した変数 a のデータ型は int(整数型)と表示する。
12.3 は小数なので、それを変数 a に再代入すると、データ型は float(浮動小数型)に変化する。このように、動的型付け言語である Python は、データの値に応じて自動的に変数のデータ型を決める。
なお、12.0 は整数なのだが、小数表記していることからデータ型は floatだ。
1.23e+4 は指数表記で \( \displaystyle 1.23 \times 10^{+4} \) を意味し、整数になるのだが、データ型は floatだ。
4.56e-3 は指数表記で \( \displaystyle 4.56 \times 10^{-4} \) を意味し、データ型は floatだ。
0xABCD は ABCD という16進数を意味し、データ型は intになる。
0o567 は 567 という8進数を意味し、データ型は intになる。
0o01011 は 01011 という2進数を意味し、データ型は intになる。
1_234_567 は桁区切り記号のカンマ , の代わりにアンダースコア _ を用いる Python 独特の表記方法で、アンダースコアをセパレータと呼ぶ。3桁区切りでなくても、4桁でも2桁でもよい。データ型は intになる。
3.141_592_653_49 はセパレータを小数に用いた例で、データ型は floatになる。
異なるデータ型同士を計算した結果のデータ型はどうなるだろうか。
# 整数÷整数
a = 5		# 整数
b = 2		# 整数
c = a / b
print("a = " + str(a) + " " + str(type(a)))
print("b = " + str(b) + " " + str(type(b)))
print("c = " + str(c) + " " + str(type(c)))

# 逆算する(小数×整数)
a = c * b
print("a = " + str(a) + " " + str(type(a)))
print("b = " + str(b) + " " + str(type(b)))
print("c = " + str(c) + " " + str(type(c)))
\( 5 \div 2 \) を計算してみる。被除数、除数は int だが、商は float になる。
では、商に除数を掛けて逆算するとどうなるか。被除数に戻るのだが、intではなくfloatに変化している。

なお、ここで紹介したint(整数型)やfloat(浮動小数型)は、「3.7 数値型の誤差と範囲」で紹介した decimalモジュールとは別物であることに留意されたい。decimalモジュール組込み型ではなくモジュールである。
よって、動的型付けは作用せず、decimal.Decimal("1.0") のように decimalモジュールを使うことを明示する必要がある。

複素数型

Python は、標準で複素数を扱うことができる。複素数は数学で習うが、Wi-Fiで使う電磁波や、マイクやイヤフォンが扱う音波に関わる機器を設計するときに欠かせない概念であり、その意味では、われわれの日常生活に欠かせないデータ型と言えよう。
Python では、虚数部はを\( i \) ではなく \( j \) で表し、\( x + aj \) のように表記する。データ型は複素数型(complex)になる。
# 複素数+複素数
c1 = 1 + 2j		# 複素数
c2 = 3 + 4j		# 複素数
c3 = c1 + c2
print("c3 = " + str(c3) + " " + str(type(c2)))
このプログラムの前半で complex 同士の足し算を行う。結果は complex である。
後半の計算は、結果として虚数部が打ち消されるが、結果は complex のままだ。

論理型

Python など多くのプログラミング言語では、条件が成り立つかどうかを判断するのに論理型(bool)を用意している。Python では boolTrue または False のいずれか2値をとる。
# bool型
a = 1
b = 2
c = (a == b)	# 等しい
print("c = " + str(c) + " " + str(type(c)))
d = (a < b)		# より小さい(未満)
print("d = " + str(d) + " " + str(type(d)))
変数 a と 変数 b が等しいかどうかを計算する \( a == b \) の結果は偽(False)となり、データ型は bool だ。大小比較 \( a > b \) の結果は真(True)となり、データ型は bool だ。
ここで計算に使った比較演算子 ==< は、「3.6 比較演算子と論理演算子」で解説した。
他のデータ型をboolに変換する組み込み関数の bool関数を使ってみよう。
# boolに変換
a = 123		# 整数
print("bool(" + str(a) + ") = " + str(bool(a)))
a = 0		# 整数(ゼロ)
print("bool(" + str(a) + ") = " + str(bool(a)))
a = -123	# 整数
print("bool(" + str(a) + ") = " + str(bool(a)))
a = 0.0		# 小数(ゼロ)
print("bool(" + str(a) + ") = " + str(bool(a)))
a = 0.0 + 0.0j	# 複素数(ゼロ)
print("bool(" + str(a) + ") = " + str(bool(a)))
a = "abc"	# 文字列
print("bool(" + str(a) + ") = " + str(bool(a)))
a = ""		# 文字列(空文字)
print("bool(" + str(a) + ") = " + str(bool(a)))
a = {}		# 空リスト
print("bool(" + str(a) + ") = " + str(bool(a)))
a = None	# 空の値
print("bool(" + str(a) + ") = " + str(bool(a)))
123や-123を bool に変換すると True だが、0に限っては False となる。小数や複素数の0の場合も False だ。また、空文字列や空リスト、Noneも False となる。
この変換結果は正しいかどうかではなく、Python ではこういう変換仕様なのだと承知しておいてほしい。他のプログラミング言語では違う結果になるかもしれないので、boolへの変換は避けた方がいい――後述する比較演算子や論理演算子で代替しよう。
ここで、0 は False に変換されることに気をつけよう。
# 数値と論理の関係
a = False
b = 0.0
print(a == b)
前述のboolへの変換結果の通り、等式 False == 0.0 は成立してしまう。
このことから、「2.4 数学関数とユーザー定義関数、入力バリデーション」で作った入力バリデーション関数 str2int は不完全であることが分かる。
def str2int(str):
	# 整数変換してみる
	try:
		int(str)
	# 失敗したらFalseを返す.
	except ValueError:
		return False
	# 成功したら変換結果を返す
	else:
		return int(str)
プログラム "strInt1.py" で0を入力すると、数字として認識されない。str2int は0を返しているのだが、メインプログラム側でFalseと認識されてしまうためだ。

この問題を回避するために、str2int を改良し、変換正否と変換結果の整数の2つの値を別々に返すようにしたのが "strInt2.py" である。
def str2int(str):
	"""引数を整数に変換する(改良版)
	
	Args:
		str(str): 変換したい数字(文字列)
	
	Returns:
		Bool: True 整数である/False 整数ではない, int: 変換結果の整数
	"""
	# 整数変換してみる
	try:
		int(str)
	# 失敗したらFalseを返す.
	except ValueError:
		return False, 0
	# 成功したら変換結果を返す
	else:
		return True, int(str)

文字列型

3.8 文字列」で説明したように、文字列型(str)の基本は、ダブルクォーテーション "..." またはシングルクォーテーション '...' で囲む。
a = "abcde"
print(a[2])
冒頭の一覧表にあるとおり、strはシーケンスに対応しており、"a[2]" と表記すると、文字列 a の先頭文字を0文字目として、2番目の文字を表示する。
a = "あいうえお"
print(a[2])

a = "0あいう12"
print(a[2])
文字列 a が全角のみである場合や、全角・半角混在の場合も、同様である。ただし、動作保証は UTF-8 に限られていることに注意されたい。
a = "あいうえお"
print(a[2:4])
文字列 a の2番目から3番目を取り出したい場合は、"a[2:4]" と表記する。
a = "あいうえお"
print("うえ" in a)
print("か" in a)
文字列 a の中から部分文字列があるかどうかを探すには、in演算子を使ってできる。
a = "あいうえお"
for c in a:
	print(c)
文字列 a はイテラブルなので、for文を使って 1文字ずつ分解処理することもできる。

バイナリ型

バイナリ型(bytes)は、画像や音声といったバイナリデータに対して用いることができるが、ここでは、strのエンコード変換に着目してみよう。
s1 = "あいうえお"
b1 = s1.encode("utf8")	# UTF-8
print(b1)
文字列 s1 に対して encodeメソッドを使うことで bytes に変換できる。ここでは、 UTF-8 でエンコードする。
b2 = s1.encode("sjis")	# SJIS
print(b2)
次にシフトJIS(SJIS)にエンコードしてみる。文字コード表を引いたり、ネットで検索してほしいのだが、シフトJISに変換されているはずである。
s2 = b2.decode("sjis")	# SJIS
print(s2)
最後に、シフトJISに変換したバイナリ型を、decodeメソッドを使ってstrに戻してみる。

文字コード変換は、たとえばExcelのCSVファイルのように、シフトJISでないと受け取れない場合に役に立つ。

練習問題

次回予告

Python など多くのプログラミング言語には制御という仕組みが備わっており、条件によって計算式を切り換えたり、同じ計算式を繰り返し実行することができる。
制御を活用することで、Excel関数だけでは実現できなかった、さまざまな処理が実現できるようになる。
次回は、条件によって制御を切り替える if文の使い方を学ぶ。Python のブロックについて説明する。プログラムの内容も、少しずつ実用的なものにステップアップしてゆく。

コラム:電話番号、ID番号

スマートフォンを使うペンギンのイラスト
0~9の数字が常に数値化というと、そんなことはない。たとえば電話番号――'0'から始まる数字だが、そのままExcelに入力すると、頭の '0' が失われ、8.E+10 のような指数表記になってしまうこともある。これは、文字列型から数値型への暗黙の型変換が起きたためだ。また、Python では、これを8進数と認識してしまう。
電話番号やID番号のように単位や助数詞が付かない数のことを無名数と呼ぶが、無名数に対して数値演算を行うことはあり得ないので、str で扱わなくてはならない。

コラム:動的型付けと静的型付け

LISPロゴ
Python をはじめ、JavaScript、PHP、Ruby、awkといったスクリプト言語は動的型付けだ。一方、C、C++、Javaなどのコンパイラ言語は静的型付けといって、変数宣言時にデータ型を明示してやる必要がある。
動的型付けのルーツは古く、1950年代後半に設計された LISP に遡る。当時、人工知能研究に用いられた言語だ。同じ頃に開発された FORTRAN静的型付けであった。こちらは科学計算向きの言語である。
この2つのルーツから分かることだが、われわれ人間が自然にデータを扱おうとしたら、動的型付けの方が書きやすい。しかし、純粋に科学計算をしようというときは、静的型付けが便利である。
上記の例で分かるとおり、動的型付けでは予期しないデータ型で計算しているバグを見つけるのに時間がかかる。一方、今時の動的型付け言語に慣れていると、静的型付け言語のコンパイル時のデータ型エラーを頻発させるだろう。

個人的には、動的型付け言語の場合も、変数のデータ型が動的に変化しないよう意識的にプログラミングを心がけることだと思うのだが、皆さんはどうお考えだろうか。
(この項おわり)
header