#C1 Python文法解説
機械学習を対象としたプログラムの場合、一言でPythonのコードといっても、実はこの後で紹介するNumPyやpandasといった、標準的に利用されているライブラリの機能を使っていることが多いです。当記事では、その中でも元のPythonそのものの文法について説明します。[目次]
Pythonには様々なデータ型がありますが、その大部分は基本型と呼ばれるデータ型が構成要素です。そこで基本型について説明します。よく用いられる4つの基本型は次のとおりです。
整数型: 整数を表現するのに用いられます。
浮動小数点数型: 浮動小数点数を表現するのに用いられます。
文字列型: 文字列を表現するのに用いられます。
ブーリアン型: True または Falseの2種類の値(論理値)のみを取る変数型です。
それでは、コードC1-1-1で個々の変数型定義を見ていきましょう。
コードC1-1-1 基本型変数の定義
[In]
# 整数型
# 数値表現が整数の場合、代入先変数は自動的に整数型になります。
a = 1
# 浮動小数点数型
# 数値表現に小数点が含まれると、代入先変数は自動的に浮動小数点型になります。
b = 2.0
# 文字列型
# 文字列はシングルクオート(')で囲みます。
# あるいはダブルクオート(")でもいいです。
c = 'abc'
# ブーリアン型
# True または False と取る変数の型です。
d = True
Javaなどの言語では、変数は宣言するときに型も明示的に指定します。Pythonでは、そのようなことはなく、どの値が代入されたかにより、型が自動的に定まります。上のプログラムにより、変数aは整数型に、変数bは浮動小数点型に、変数cは文字列型に、変数dはブーリアン型に設定されることになります。
今、定義したaからdまでの変数にどのような値が入っているか、またどの型が設定されているかを確認します。変数の内容を見るためにはprint関数を、型を調べるためにはtype関数を利用します。まずは、整数型の変数aについて調べてみましょう。実装はコードC1-1-2になります。
コードC1-1-2 整数型の変数の値と型表示
[In]
# 整数型の変数aの値と型表示
print(a)
print(type(a))
[Out]
1
<class 'int'>
値は1、型は「class 'int'」であることがわかりました。
次に浮動小数点数型の変数の値と型を表示します。実装はコードC1-1-3です。
コードC1-1-3 浮動小数点数型の変数の値と型
[In]
# 浮動小数点数型の変数bの値と型表示
print(b)
print(type(b))
[Out]
2.0
<class 'float'>
浮動小数点数型の型は「class 'float'」となっていることがわかりました。
次は文字列型で、実装はC1-1-4です。
コードC1-1-4 文字列型の変数の値と型表示
[In]
# 文字列型の変数cの値と型表示
print(c)
print(type(c))
[Out]
abc
<class 'str'>
文字列型の変数の型は「class 'str'」でした。
最後がブーリアン型で、実装はコードC1-1-5になります。
コードC1-1-5 ブーリアン型の変数の値と型表示
[In]
# ブーリアン型の変数dの値と型表示
print(d)
print(type(d))
[Out]
True
<class 'bool'>
ブーリアン型の場合は、TrueかFalseのいずれかの値を取ります。この二つの単語はPythonでは予約語 となっています。型は「class 'bool'」です。
リストは、複数の値を順番に並べて、全体を一つのまとまりとして扱うデータ構造 のことです。Javaなどのプログラミング言語などの「配列」に近いものです。
リストは、Pythonで複雑な処理をする際、最もよく利用されます。Python固有の機能もいくつかあります。これらについて実習を進めていきましょう。
最初に、リストを定義します。リストは、カンマ区切りで複数のデータを並べて、[]で囲んで定義します。コードC1-1-6で確かめてみましょう。
コードC1-1-6 リストの定義
[In]
# リストの定義
l = [1, 2, 3, 5, 8, 13]
# リストの値とtype
print(l)
print(type(l))
[Out]
[1, 2, 3, 5, 8, 13]
<class 'list'>
このコードでは、リストを定義した後で、リスト全体をprint関数に渡しています。Pythonでは、このようなprint関数の使い方が可能で、リストの全要素を表示してくれます。type関数の出力結果は「class 'list'」となっています。
プログラムでリストを扱っていると、そのリストの要素数を知りたい場合がよくあります。その方法を示すのが、次のコードC1-1-7です。
コード C1-1-7 リストの要素数
[In]
# リストの要素数
print(len(l))
[Out]
6
Pythonではこのような目的でlen関数が用意されています。この関数を呼び出すことで、リストの要素数が6だとがわかりました。
リストに対する操作でよくあるのが、リストの特定の要素にアクセスする処理です。次のコードC1-1-8にその方法を示します。
コード C1-1-8 リストの要素参照
[In]
# リストの要素参照
# 最初の要素
print(l[0])
# 3番目の要素
print(l[2])
# 最後の要素 (こういう指定方法も可能)
print(l[-1])
[Out]
1
3
13
特定の要素にアクセスするには、l[2]
のように記述します。最初の要素のインデックスは0です。なので、l[2]
は3番目の要素を意味します。
Python固有の参照方法もあります。それは三つめのコーディング例のl[-1]
という書き方です。-1
とは「リストの最後」を意味します。同様にl[-2]
、l[-3]
といった書き方も可能です。それぞれどの要素を指すのか、自分で考えた上で実際に試してみてください。
リストの参照方法にはPython独特の文法がいくつかあります。その典型的な例がこれから説明する部分リスト参照です。これはl[(インデックス1):(インデックス2)]
のように、コロンを挟んで二つのインデックス値を指定し、その範囲内の要素をすべて参照する方法です。
言葉の説明だけではわかりにくいので、次のコード C1-1-9で確認しましょう。
コードC1-1-9 部分リスト参照1
[In]
# リストの部分参照1
# 部分リスト インデックス:2以上 インデックス: 5未満
print(l[2:5])
# 部分リスト インデックス:0以上 インデックス: 3未満
print(l[0:3])
# 開始インデックスが0の場合は省略可
print(l[:3])
[Out]
[3, 5, 8]
[1, 2, 3]
[1, 2, 3]
このコードの最初はl[2:5]
で、3番目の要素であるl[2]
から始まり、6番目の要素であるl[5]
の一つ手前、つまりl[4]
までの部分リストが参照されます。
次はl[0:3]
で、l[0]
から始まり、l[3]
の一つ手前、つまりl[2]
までの部分リストになります。最後の例では開始要素の値を省略しています。この場合、開始要素は先頭要素、つまりl[0]
を意味するルールです。なので、l[0:3]
とl[:3]
は同じ結果を意味します。
部分リストの参照パターンをもう少しいろいろ見てみましょう。コードC1-1-10がその実装です。
コードC1-1-10 部分リスト参照2
[In]
# リストの部分参照2
# 部分リスト インデックス:4以上最後まで
# リストの長さを求める
n = len(l)
print(l[4:n])
# 最終インデックスが最終要素の場合は省略可
print(l[4:])
# 後ろから二つ
print(l[-2:])
#最初も最後も省略する通りスト全体になる
print(l[:])
[Out]
[8, 13]
[8, 13]
[8, 13]
[1, 2, 3, 5, 8, 13]
今回は「5番目の要素から最後の要素」までを参照対象にしたいとします。今まで習った話で、先頭のインデックスを4にすることはすぐわかりますが、問題は「最後」の要素の指定方法です。このコードの最初では、配列の長さを返すlen関数を使って、最後の要素にあたるインデックスを調べています。
l[(インデックス1):(インデックス2)]
の書き方では、先頭の(インデックス1)同様、後ろの(インデックス2)も省略可能です。省略した場合は「最後の要素まで」という意味になります。2番目のl[4:]
は、この性質を利用したものです。確かに同じ結果が返ってきていて、実は、長さnを調べる必要はなかったです。
次の実装は、二つめの実装にもう一工夫加えたものです。このリストの5番目の要素は、後ろから数えると2番目です。後ろから2番目の要素は、インデックス値として-2
でも表せます。それで、l[-2:]
という書き方をすると、「配列の後ろから二つ目から最後まで」という意味になるのです。この参照法は本書の5章の実習でも出てくので、しっかり覚えてください。
最後の例のl[:]
では、先頭(インデックス1)も終了(インデックス2)も両方省略しています。この場合、もう想像がついたと思いますが、元のリスト全体を意味します。リストのインデックスの場合、この書き方は(元の変数と同じなので)まったく意味がないのですが、本書で解説しているNumPyでは重要な意味を持つ場合が出てきます。
リストとよく似たデータ構造として「タプル」と呼ばれるものがあります。コードC1-1-11を見てください。ここにタプルの定義方法と典型的な利用方法を示しました。
コードC1-1-11 タプルの定義方法と典型的な利用方法
[In]
# タプルの定義
t = (1, 2, 3, 5, 8, 13)
# タプルの値表示
print(t)
# タプルの型表示
print(type(t))
# タプルの要素数
print(len(t))
# タプルの要素参照
print(t[1])
[Out]
(1, 2, 3, 5, 8, 13)
<class 'tuple'>
6
2
このコードをみればわかるように、タプルとリストで異なるのはその定義方法です。リストは[]で囲んだのに対して、タプルでは()を使います。type関数で型を表示するとclass 'tuple'
と返ってきます。要素数をlen関数で調べたり、t[1]
のような形で要素を参照したりする点はリストとまったく同じです。
しかし、リストとタプルの振る舞いで決定的に異なる点が一つあります。それは、次のコードC1-1-12でわかります。
コードC1-1-12 タプルへの代入
[In]
t[1] = 1
[Out]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-12-d6b0ce29b2aa> in <module>
----> 1 t[1] = 1
TypeError: 'tuple' object does not support item assignment
このコードは、事前に定義したタプルであるtの特定の要素t[1]
を、別の値で書き換えようとしたものです。すると、上記のようなエラーになってしまいました。このように一度定義したタプルは、書き換えができないのです 。試してもらえばわかりますが、リスト変数の場合は、一度定義した内容を自由に書き換えられます。
Pythonでは、実はいろいろなところで、意識することなくタプルが使われています。その一例をコードC1-1-13とコートC1-1-14で示します。
コードC1-1-13 二つの変数の組を新しい変数に代入
[In]
x = 1
y = 2
z = (x, y)
print(type(z))
[Out]
<class 'tuple'>
このコードは変数x
と変数y
を(x, y)
という形に表現して全体をまとめて新しい変数zに代入しています。新しい変数z
の型はclass 'tuple'
です。これだけ見ると、なんの変哲もないコードに見えます。
次のコードC1-1-14は、先ほどのコードで定義した新しい変数z
を利用した実装例です。
コードC1-1-14 二つの変数に同時に値を代入
[In]
a, b = z
print(a)
print(b)
[Out]
1
2
なんと、a
とb
という変数に対して同時に代入できました。なぜこのようなことができたのでしょうか?
実はa, b
と書くとこの式もタプルになっています。Pythonでは、代入を意味する等式=
の右辺と左辺が要素数の等しいタプル同士の場合、複数の変数へ同時に代入できるのです 。
リストと並んで重要なデータ構造に辞書(プログラミング言語によってはハッシュテーブルと呼ばれる場合もあります)があります。Pythonでの辞書の扱い方について簡単に説明しましょう。
最初は辞書の定義方法です。コードC1-1-15に具体例を示しました。
コードC1-1-15 辞書の定義
[In]
# 辞書の定義
my_dict = {'yes': 1, 'no': 0}
# print文の結果
print(my_dict)
# type関数の結果
print(type(my_dict))
[Out]
{'yes': 1, 'no': 0}
<class 'dict'>
このコードの一番上が辞書の定義方法です。Webシステムで使われる「JSON」の書式と同じで、{}の囲みの中に、「キー値: 値」の形式の並びをカンマ区切りで記述します。
print文の結果は、定義の内容がそのままです。type関数で型を調べるとclass 'dict'
でした。
次のコードC1-1-16で定義した辞書の参照方法を示します。
コードC1-1-16 辞書の参照方法
[In]
# キーから値を参照
# key= 'yes'で検索
value1 = my_dict['yes']
print(value1)
# key='no'で検索
value2 = my_dict['no']
print(value2)
[Out]
1
0
このコードの通り、辞書をキーで検索するには、my_dict['yes']
のように記述します。リストの参照と似ていてややこしいのですが、[]の内部が整数でなく文字列(かタプル)の場合、辞書参照を意味することになります。
一度作った辞書に新しい項目を追加する方法を示します。実装はコードC1-1-17になります。
コード C1-1-17 辞書に新しい項目の追加
[In]
# 項目追加
my_dict['neutral'] = 2
# 結果確認
print(my_dict)
[Out]
{'yes': 1, 'no': 0, 'neutral': 2}
今作ったmy_dict
という辞書に新しいキー'neutral'
で項目を追加したい場合、my_dict['neutral'] = 2
のような書き方をすればよいです。再度、my_dict
全体をprint関数にかけると新しい項目が追加されていることが確認できます。
今まで、「基本データ型」「リスト型」「辞書型」というデータ型を中心にPythonの文法を見てきました。プログラミング言語としては、データ型と並んで制御構造が重要です。代表的な制御構造である「ループ処理」「条件分岐(if文)」「関数定義」について、順に説明します。
Pythonでは、ループ処理の方法が何通りかありますが、一番典型的なのは、リストを引数として、その要素一つひとつを処理するパターンです。その実装例をコードC1-1-18で示します。
コード C1-1-18 ループ処理
[In]
# ループ処理
# リストの定義
list4 = ['One', 'Two', 'Three', 'Four']
# ループ処理
for item in list4:
print(item)
[Out]
One
Two
Three
Four
事前に用意したlist4
というリスト変数に対して for item in list4:
という書き方をすると、そこから先の制御構造の内部では、リストの各要素をitem
という変数名で参照できます。コードC1-1-18では一番単純に各要素をprint文で表示しただけですが、もっと複雑な処理もこの中で行えます。
ここで非常に重要な文法上の決まりがあります。それは、Pythonでは制御構造の内部は行のインデント(字下げ) により規定されるというルールです。Javaなど他の言語でも、人間がプログラムを組むときは、見やすさ、わかりやすさから制御構造の内部をインデントすることはよく行われます。Pythonでは、この慣用的なルールを言語の文法として規定しているのです。こうするメリットは、他の言語で必要な制御構造の内部を示す括弧が不要になることです。この性質があるので、Pythonは他の言語よりシンプルにコードが書けます。一方で慣れないと、この文法を忘れてエラーを起こすので、この点は注意してください。
コードC1-1-18の中でprint文だけ中途半端な(インデントした)場所から始まっているのは、このような深い意味があったのでした。
コードC1-1-18のように、ループ処理対象のリストを使う場合の方法はわかったと思います。では、Javaなどでよく行われる (for i =0, i < N, i++)のような、整数値インデックスでループ処理を回す場合はどうしたらよいのでしょうか? ここでよく使われるのがrange関数です。まずは、その実装をコードC1-1-19で見ていきましょう。
コードC1-1-19 range関数を使ったループ処理
[In]
# range関数と組み合わせ
for item in range(4):
print(item)
[Out]
0
1
2
3
このコードを見ると、range(4)の結果が[0, 1, 2, 3]というリストになっていることがわかると思います。range関数とは、まさにこのようなリストを自動生成する関数です。
range関数の引数を二つ、または三つ取ることもできます。それぞれどのような結果になるか、コードC1-1-20とコードC1-1-21で確認しましょう。
コードC1-1-20 引数二つのrange関数
[In]
# 引数二つのrange関数
for item in range(1, 5):
print(item)
[Out]
1
2
3
4
このコードの結果から、引数を二つ取るrange関数では、最初の引数は開始地点の整数値であることがわかります。
コード C1-1-21 引数三つのrange関数
[In]
# 引数三つのrange関数
for item in range(1, 9, 2):
print(item)
[Out]
1
3
5
7
このコードの結果から、引数を三つ取るrange関数の三つめの引数は、整数の増分値であることも確認できます。
ループ処理と並んで重要な制御構造はif文による条件分岐です。Pythonでどのような実装形式になるのか、次のコードC1-1-22で確認してみましょう。
コードC1-1-22 if文の実装例
[In]
for i in range(1, 5):
if i % 2 == 0:
print(i, 'は偶数です')
else:
print(i, 'は奇数です')
[Out]
1 は奇数です
2 は偶数です
3 は奇数です
4 は偶数です
このコードでは、先ほど説明したrange関数を使ったループ処理の内部に、ifによる分岐構造を入れて、二重に入れ子になった処理構造を持たせています。
今まで説明していなかった文法としてi % 2
があります。「これは整数iを2で割った余り」を意味しています。余りが0ならiは偶数、1なら奇数なので、そのように分岐してメッセージを表示するプログラムになっています。
ifによる分岐の場合も、処理構造の内部はインデントで示します。このように制御構造が入れ子になったような場合は特に、Javaなどと比べて、Pythonはよりシンプルに書けることがわかると思います。
プログラミング言語の制御構造として、ループ処理、条件分岐と並んで重要なものに関数があります。次のコードC1-1-23では、Pythonでの関数の定義方法と、呼び出しサンプルを示します。
コードC1-1-23 関数定義と関数呼び出し
[In]
# 関数の定義例
def square(x):
p2 = x * x
return p2
# 関数の呼出し例
x1 = 13
r1 = square(x1)
print(x1, r1)
[Out]
13 169
Pythonで関数を定義する場合、行の頭にdef と書いてその後、<関数名>(<引数の並び>):
という書き方をします。引数が一つもない関数もあり得て、その場合がdef func():
のような書き方になります。
関数の場合も、内部の処理はインデントします。また、値を戻すときは、return <値>
と記述します。
関数の呼び出し方法に関しては、特に難しい点はないので、解説は不要と思います。上のサンプルで、squareという関数は、数値の引数一つを入力として、その2乗の結果を返します。実際に13を引数として呼び出すと、その2乗の169が戻ってきています。
Pythonでは、前に説明したタプルの仕組みをうまく使うことで、複数の値を同時に返す関数もれます。次のコードC1-1-24では、その実例をお見せします。
コードC1-1-24 複数の値を戻す関数
[In]
# 関数の定義例2
def squares(x):
p2 = x * x
p3= x * x * x
return (p2, p3)
# 関数の呼出し例
x1 = 13
p2, p3 = squares(x1)
print(x1, p2, p3)
[Out]
13 169 2197
これが、その関数squaresです。数値の引数一つを受け取って、2乗を計算するところまでは前の関数と同じですが、もう一つ追加で3乗の値も計算しています。そして、return文の中で(p2, p3)
と要素が二つのタプルを戻しています。こういう形の関数の場合、受け取る方もp2, p3
と二つの変数を用意しておくと、結果的に二つの値を同時に受け取れます。Pythonならではの便利な関数の定義の仕方といえます。
ここまで、Python独自の機能については、できるだけ詳しく説明しながら、Pythonの文法の基本的な部分をかけ足で説明してきました。ここでは、本書のサンプルコードに出て来る、Pythonのそれ以外の利用法について、簡単に説明します。
Pythonが機械学習に便利な理由の一つは、様々なライブラリが利用できる点にあります。本書の実行環境である、Google ColaboratoryなどJupyter Notebookが動く環境でライブラリを利用する場合、2段階の準備が必要なことがあります。
最初の段階は、ライブラリをJupyter Notebookが稼働しているOS上にソフトウェアとして導入する段階です。NumPyやpandasといった、機械学習で非常によく利用されるライブラリはすでにOS上に導入ずみです。ところがあまり利用されないライブラリは、そのような状態になっていないことがあります。その場合、次のコードC1-1-25のように、pipコマンドを使って導入します。
コードC1-1-25 ライブラリの導入
[In]
# 日本語化ライブラリ導入
!pip install japanize-matplotlib | tail -n 1
[Out]
Successfully installed japanize-matplotlib-1.1.2
!pip
と行頭に!
がついているのは、Notebook内からOSコマンドを発行する場合のルールです。このコードで導入しているjapanize-matplotlibとは、matplotlibというグラフ用ライブラリを日本語対応するためのもので、本書のサンプルコードでは標準的に利用しています。
OS上にライプラリが導入済みであっても、すぐに利用可能なわけではありません。Notebook上で利用するためには、もう一段階「インポート」という操作が必要です。その実装例を示しているのが、次のサンプルコードC1-1-26です。ちなみに、このコードは、本書のサンプルアプリで共通に定義している処理の一部です。
コード C1-1-26 ライプラリのインポート
[In]
# 必要ライブラリのimport
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# matplotlib日本語化対応
import japanize_matplotlib
# データフレーム表示用関数
from IPython.display import display
このコードを見ると importで始まっている行と、fromで始まっている行の2通りがあります。
その違いですが、importで始めた場合は、importしたモジュールに含まれている関数がすべて利用可能になります。
逆に from x import y
の形式では、「yという名前のライブラリのうちxという関数だけを利用する」という宣言になります。このコードの一番下の行の宣言により、IPython.display
ライブラリの「display関数」が利用可能ということになります。
また、import pandas as pd
は、「pandasという名前のライブラリをpdという別名で利用可能にする」ということを意味します。冒頭3行のimport文は、機械学習のプログラミングで慣用的に利用されている別名定義です。なので、いつもこの別名を宣言するものと理解してください。
もう一つ、本書の共通処理の実装を説明します。コードC1-1-27がその実装です。
コード C1-1-27 余分なワーニングの非表示
[In]
# 余分なワーニングを非表示にする
import warnings
warnings.filterwarnings('ignore')
ここでは、warningsという名前のライブラリをインポートし、その中でfilterwarningsという関数を呼び出しています。Pythonでは、ライブラリが日々バージョンアップをするなかで、「この関数が次のバージョンで使えなくなる予定です」という意味の警告メッセージを表示することがあります。このような警告メッセージを表示させないための設定がコードC1-1-27になります。
本節の最後に数値の整形出力方法について説明します。Pythonでは、今までの実習で見てきた通り、対象変数の型がリスト型や辞書型でも、print関数に単に渡すだけで内容を表示できます。一方で浮動小数点数などの基本型に関しては、小数点以下の桁数指定など、より細かい単位で整形出力をしたい場合があります。そのような方法は、従来もいくつかあったのですが、Python 3.6で新たに可能になった方式があり、短いコードで出力可能なので、本書の実習では標準的にこの方式を用いることにします。 具体的な実装例をコードC1-1-28に示しました。
コードC1-1-28
[In]
# f文字列の表示
a1 = 1.0/7.0
a2 = 123
str1 = f'a1 = {a1} a2 = {a2}'
print(str1)
[Out]
a1 = 0.14285714285714285 a2 = 123
f'xxx'
とfで始まる文字列がPython 3.6から利用可能になった「f文字列」と呼ばれる特殊な書式です。その特徴は、第一に変数の値を{変数名}の形で直接埋め込め点です。このコードのstr1
の式の中では{a1}
と{a2}
の形で2カ所の埋め込み定義があります。str1
をprint関数にかけて埋め込み結果を確認しています。
f文字列の二つ目の特徴は、個々の埋め込み変数に対して、細かく書式を指定できる点です。その具体例をコードC1-1-29で示しました。
コードC1-1-29 f文字列の詳細書式設定
[In]
# f文字列の詳細オプション
# .4f 小数点以下4桁の固定小数点表示
# 04 整数を0詰め4桁表示
str2 = f'a1 = {a1:.4f} a2 = {a2:04}'
print(str2)
# 04e 小数点以下4桁の浮動小数点表示
# #x 整数の16進数表示
str3 = f'a1 = {a1:.04e} a2 = {a2:#x}'
print(str3)
[Out]
a1 = 0.1429 a2 = 0123
a1 = 1.4286e-01 a2 = 0x7b
最初の例では、浮動小数点型のデータに対して、「固定小数点表示 小数点以下4桁」の指定で表示してみました。また整数型に対しては、「4桁表示0詰め」を指定しています。
2番目の例では、浮動小数点型のデータに対して、「浮動小数点表示 小数点以下4桁」の指定を、また整数型に対しては「16進数表示」と指定しています。いずれも、想定通りの結果が得られています。
書式指定のより細かい文法のついては、下記のリンク先などの情報を参考にしてください。