Lesson1

Python基本文法

1. 学習の目標

ここでは、本カリキュラムの学習を進める上で最低限必要なPython文法を学びます。

まずは、前のレッスンで学習したとおりにJupyterLabで新規ノートブックを作成し Lesson1 という名前をつけてください。サンプルコードを入力して実行する際は、こちらのノートブックを使いましょう。

2. 文字の表示

まずは、1行だけ命令を記述してみましょう。指定した文字列を出力(表示)する命令です。そのためには print() という命令(関数ともいいます)を使います。

ノートブックの最初のセルに print("Hello, world!") と入力したら実行ボタン(もしくは Shift + Enter )を押してください。

上のように Hello, world! と表示されればOKです。これで晴れてPythonの世界に足を踏み入れました。おめでとうございます!

補足:前のレッスンの「JupyterLabの使い方」で計算式を表示したときのように、JupyterLabでは、データを1つ表示するだけの場合、最終行に該当のデータを記述すれば表示されます。

3. データ型

Pythonでは主に「数値」と「文字列」のデータを扱います。

3.1 数値

「コンピュータがする処理」として思い浮かびやすいのは数値計算です。レッスン0の「JupyterLabの使い方」でも、たし算を行いました。

3 + 2

実行すると 5 と出力されます。

3+2のようにスペースを空けなくても正しく実行されますが、コードが読みやすくなるので「+」の前後はスペースで空けることをオススメします。以降の内容では、スペースを空けることにします。

四則計算

Pythonでは四則演算をはじめ、さまざまな数値演算が可能です。以下の計算式を1つのセルに1つずつ記述し、実行確認してみてください。

種類 計算式 出力結果
たし算 3 + 2 5
ひき算 6 - 4 2
かけ算 8 * 4 32
わり算(小数点以下も計算) 21 / 8 2.625
わり算(小数点以下は切り捨て) 21 // 8 2
わり算の余りを求める計算 21 % 8 5
を計算(右の例は26 2 ** 6 64

なお、Pythonでは += といった計算の記号のことを 演算子 といいます。覚えておいてください。

計算誤差

一部の計算において、正しい答えにならない場合があることを覚えておいてください。

3 * 4.6

出力結果: 13.799999999999999

3 × 4.6 = 13.8 を出してほしいのに、13.799999…(途中で打ち止め)となりました。

0.1 + 0.1 + 0.1

出力結果: 0.30000000000000004

こちらも 0.300000...(中略)...4 と変な結果になりました。これは「2進数に直して計算する」というコンピュータ内部の計算のしくみによるもので、どうしても近似値になってしまいます。正確さを求める場合、注意が必要です。

Pythonの数値には「整数」「実数(小数)」「複素数(12 + 34jのような形式のデータ:j はPython上で虚数を扱うための文字)」があります。本カリキュラムでは、複素数についてはほとんど扱いません。

3.2 文字列

このレッスンの最初に “Hello, world!” という文字列を表示させました。この “Hello, world!” のようなデータは、Pythonでは 文字列 という言い方をします。

文字列は半角英数の他、全角文字も処理できます。

"こんにちは、世界!"

実行しても、文字化けやエラーが発生しないことを確認してください。

なお、文字列を扱うときは、文字列をシングルクォーテーションかダブルクォーテーションで囲む必要があります。シングルクォーテーションとダブルクォーテーションに違いはありませんので、どちらでも大丈夫です。

'こんにちは、世界!'

シングルクォーテーションの場合でも、実行結果がエラーや文字化けにならないことを確認しましょう。

クォーテーションを文字列として表示する

クォーテーションは「文字列を囲むための記号」となるため、クォーテーションそのものを文字列として表示したい場合は注意が必要です。

"Hello, "Python" world!"

出力結果:

  File "<ipython-input-13-af96e338e20a>", line 1
    "Hello, "Python" world!"
                  ^
SyntaxError: invalid syntax

これは、Python の手前の " が、文字列の最初の " に対応したもの(閉じる用途としてのクォーテーション)と判断されてしまうためです。

対処法としては2つあります。まずは、ダブルクォーテーションを文字列で表示したいときはシングルクォーテーションで囲み、逆にシングルクォーテーションを文字列で出したいときはダブルクォーテーションで囲むという方法です。

'Hello, "Python" world!'

もうひとつは、\ (Windowsの場合は ¥)をクォーテーションの手前にくっつける方法です。これにより、そのクォーテーションが「文字列のクォーテーション」として扱われます。このときの \エスケープシーケンス と呼んでいます。

"Hello, \"Python\" world!"

上記2つのコードは、どちらもエラーにならないことを確認してください。

改行して表示する

文字列の途中で改行したものを表示したい場合、print() を使うとともに、下記のような工夫が必要です。

もっとも簡単な方法は、Pythonの命令は上から下に向かって1行ずつ実行されるという性質を利用して、複数行の print() を記述するだけです。print() は、とくに指定のない限り、文末で改行されます。

print("Hello,")
print("Python")
print("world!")

ここでは、よりスマートな方法を説明します。

ひとつは、エスケープシーケンスを使う方法です。\n (Windowsの場合は ¥n) という入力が改行として扱われます。

print("Hello,\nPython\nworld!")

もうひとつは、クォーテーションを3つ連続で囲む方法です。先ほどのような改行記号を意識しなくてもよくなります。このような使い方を ヒアドキュメント といいます。

print("""Hello,
Python
world!""")

どちらも出力結果は、下記のとおりになります。

Hello,
Python
world!

文字列の連結と繰り返し

数値において + は、たし算の演算子でした。

123 + 456

出力結果: 579

文字列に + を用いると、文字列の連結になります。

"123" + "456"

出力結果: '123456'

文字列の "123" と文字列の "456" を連結して、"123456" という文字列ができました。

数値の 123 と文字列の "123" は別ものです。気をつけましょう。

同じく、* の演算子で掛け算のような書き方をすると、文字列を繰り返して表示します。

"abc" * 3

出力結果: 'abcabcabc'

3.3 数値と文字列の相互変換

数値は普通に数値として扱いたいかもしれません。しかし、数値を文字列として扱った方が楽になる処理もあります。たとえば、数値の桁数を何らかの処理方法で知りたい場合、文字列にして len() という命令(関数)を利用すれば一発でわかります。

len("1234567890")

出力結果: 10

つまり、必要なときに数値を文字列にしたり、逆に文字列化していたものを数値にしたりすればよいのです。

数値を文字列に変換する

数値を文字列に変換するには str() という命令(関数)を使います。カッコの中には、文字列にしたい数値を指定します。

len(123)

len() に数値を入れて実行するとエラーになります。

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-21-a95e54e61f62> in <module>
----> 1 len(123)

TypeError: object of type 'int' has no len()

そこで、str() を使います。 str() は、カッコに指定した数値を文字列化してくれる命令です。len() のカッコの中に str(123) と記述することで先に str(123) が実行されるので、その処理結果、つまり文字列の "123"len() に指定した場合(str("123"))と同じ結果が得られます。

len(str(123))

出力結果: 3

文字列を数値に変換する

逆に文字列化したものを数値に戻したい場合は、int() という命令(関数)を使います。

int("123") + int("456")

上記の例は、文字列の "123""456"int() で数値化して、たし算を行っています。 579 という結果が表示されることを確認してください。

4. 変数

以降のPythonプログラミングで重要になる「変数」について説明します。

4.1 変数とは

ここまでは単一行のプログラムを実行しているのみでしたが、今後、複数行に渡るプログラムを記述することになります。その際に「今実行した処理結果のデータを、いったんどこかに置いておき、あとで使いたい」といったことが必要です。その置き場所となるのが 変数 です。「データを格納する 」にたとえられています。

4.2 変数を使ってみる

Pythonでは変数をどのように使うか、以下のサンプルコードで確認してみましょう。

a = 2
b = 3
c = a + b
c

出力結果: 5

1行ずつ見てみましょう。

a = 2

ここでは a という変数を用意しました。変数を用意することを 定義宣言 といいます。用意した変数 a に整数の2を入れました。変数にデータを入れることを 代入するといいます。

variable_01.png

数学では = は「左辺と右辺が同値」であることを示す記号ですが、Pythonで = は「右辺の内容を左辺へ代入する」という意味になります。特定の変数にデータを代入したいときは 変数名 = データ と記述すればOKです。変数名の前に何か単語をつける必要はありません。

なお、a = 2 という処理は、変数へ数値を代入しただけですので「すぐに何か結果が表示される」というものではありません。

コラム:変数名に関して
変数名は基本的に自分で好きなように設定できます。以降のサンプルのように abc といった1文字のみの変数名でも構いませんが、格納するデータの内容がわかる変数名が望ましいです。たとえば、住所を入れる変数なら address、年齢を入れる変数なら age です。

また、2つ以上の単語で変数名をつけたい場合、Pythonではアンダーバー (_) で単語を結びつける方法が推奨されています。苗字(last name)を入れる変数の場合は last_name、下の名前(first name)を入れる変数の場合は first_name です(このような表記方法を スネークケース といいます)。

さらに、 予約語 に注意しましょう。ifwhile など、Pythonの処理において特別な意味をもつキーワードを変数名として指定することはできません。本カリキュラムや他の書籍などのサンプルコードで、変数名を変えた以外はそのまま記述しているのにエラーになる場合は、予約語を使っているかどうか調べてください。
参考資料:予約語 - Python 3.7 ドキュメント

さて、サンプルコードの話に戻ります。

b = 3

b という変数を定義して、整数の3を代入しました。こちらも単なる代入処理ですので、結果は何も表示されません。

c = a + b

2つ前で定義した変数 a と、1つ前で定義した変数 b を使って、たし算をしています。a + b は「変数 a の中身と変数 b の中身をたし算する」という処理内容です。c = a + b と書いているので、a + b のたし算の結果を変数 c へ代入したことになります。

計算処理をしてはいますが、計算結果を変数に代入しているので、この処理についても結果がすぐに表示されるわけではありません。

c

最後に c とだけ記述し、cの中身をJupyterLabに表示します。

変数の中身を表示したい場合、変数名をクォーテーションで囲む必要はありません。たとえば以下のように変数 c をクォーテーションで囲ってしまうと「文字列のc」という扱いになります。

"c"

なお、上記までの例は変数と変数の計算処理でしたが「変数と数値のたし算」といったことも可能です。

d = 10
e = d + 1
e

出力結果: 11

上記の例では、変数 d に10を代入した状態で e = d + 1 という計算をしました。変数 d の中身(整数の10)に1を足した結果を e に代入しています。そのため変数 e には11が格納されています。

同じ変数に上書きする

すでにデータの入った変数に新しいデータを上書きすることも可能です。

f = 100
f = f + 100
f

出力結果: 200

上記の例は、まず変数 f に100を代入しました。その次に「変数 f の中身(整数の100)に100を足したもの(要するに200)を f に上書きする」処理をしたことになります。そのため、最終的に変数 f の中身は200に変わります。

variable_03.png

変数を破棄する

なお、ノートブック内で一度定義した変数は、ノートブックを終了するまで何回でも中身を確認する(参照するといいます)ことができます。「不要なので破棄したい」という場合は del(変数名) という命令(関数)を実行してください。

del(f)
f

出力結果:

---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-27-e87019636d6e> in <module>
      1 del(f)
----> 2 f

NameError: name 'f' is not defined

上記のように、「定義されていない」もしくは「del() で破棄された」変数を参照しようとすると NameError: name '変数名' is not defined というエラーになります。

なおJupyterLabでは、画面上部のメニューから「Kernel」→「Restart Kernel」を実施することで、すべての変数を破棄できます。

変数のデータ型

「変数の中に格納されているデータの種類」を「変数のデータ型」と呼びます。具体的にデータ型を知りたいときは type() という命令(関数)を使います。たとえば、変数 a から d を以下のように定義したとします。

a = 123
b = 456.789
c = "abcdefg"
d = 3 + 4j

type(変数名) と記述して実行した結果は以下のとおりです。

記述内容 出力結果
type(a) <class 'int'>
type(b) <class 'float'>
type(c) <class 'str'>
type(d) <class 'complex'>

上記では、変数 a に整数の123、bに実数の456.789、c に文字列の”abc”、d に複素数の (3 + 4j) を代入しました。それぞれを type() で調べた結果、aint(整数)、bfloat(実数)、cstr(文字列)、dcomplex(複素数)であると表示されました。

variable_02.png

4.3 文字列の中に変数の中身を埋め込む

文字列の中に変数の値を埋め込んで表示したいときは、以下のように、文字列の中に{ }を使って変数を埋め込む方法があります。

station1 = "東京"
station2 = "新橋"
price = 140
str1 = f"{station1}から{station2}までの運賃は{price}円です。"
str1

出力結果: "東京から新橋までの運賃は140円です。"

{ }を使って変数を埋め込む場合、文字列のクォーテーションのすぐ前にfの1文字を追記する必要があります。

5. Pythonの基本的な文法要素

コメントや演算子など、基本的な文法要素について、まとめます。

5.1 コメント

文字列データと変数のどちらでもない「ただの文字」をPythonのファイルの中に記述すると、エラーになります。

これは普通の日本語のコメントです。

出力結果:

  File "<ipython-input-37-50e0f0f0da5e>", line 1
    これは普通の日本語のコメントです。
                    ^
SyntaxError: invalid character in identifier

しかしプログラミングをしていると「処理の内容について説明をメモ書きしておきたい」「後でこの命令を使いたいけど今は使わない。でも使うときに改めて記述するのは面倒くさい」という思いも出てきます。そのときに使うのが コメント です。

Pythonでコメント機能を使う場合は # の記号やヒアドキュメントを使います。

# これは普通の日本語のコメントです。

a = 10  # 変数aに10を代入
b = 20  # 変数bに20を代入

# 以下はaを上書きしているが、コメントにしているので処理は行われない
#a = 50

# たし算
c = a + b

"""
このように記述することで
複数行のコメントの記述も可能です。
最後にcの中身を表示します。
"""
c

実行すると 30 とだけ表示されます。コメントはただのメモですので、処理の上では無視されます。# を使う場合は、# の記号の後ろから行末までがすべてコメントという扱いです。次の行の先頭からは普通にPythonの命令を記述できます。また、#a = 50 の行のように、特定の命令の行頭に # をつけることで処理が無効化されます。いったん不要な処理がある場合は、このようにコメント化しておくと、再度その処理が必要となった場合に # を削除するのみで済むので便利です。

また、ヒアドキュメント(3個連続のクォーテーションで文字列を囲う仕組み)を使うことで、複数行のコメントの記述が可能です。変数へ代入していないので、単純に「活用できない文字列データがPythonファイル内に存在している」だけの状態です。それを利用して、コメントと同じ扱いにできます。

自分の作ったコードを人に見せる場合、または以前自分で作ったコードを、忘れた頃に見返す場合、コメントが記述されていると「どういう意図でその命令を記述したか」が理解しやすくなります。変数名の名づけ方と併せて効果的に活用し、人が見てもわかりやすいコードの書き方を意識してください。

5.2 命令文の区切り

Pythonでは他のプログラミング言語のように行末にセミコロン(;)をつける必要はありません。ただ、命令文の区切りとして使うことは可能です。セミコロンを命令文の最後に記述することで、続けて次の命令を記述することができるので、1行の中に複数の命令を記述してもエラーになりません。

a = 10; b = 20; c = 30
a

出力結果: 10

続くセルで bc も確認してみます。

b

bの出力結果: 20

c

cの出力結果: 30

ただし、どういった場合でも1行に複数の命令を記述して良いというものではありません。1つの命令が長いのに続けて次の命令を記述すると、コード全体が読みにくくなるかもしれません。注意しましょう。

5.3 演算子

ここまででも +- といった数値計算用の演算子や文字列を連結する演算子、変数にデータを代入する演算子(=)がありました。その他の演算子をまとめます。

代入演算子

Pythonでは、= の他にも += という代入演算子が用意されています。

a = 1
a += 1
a

出力結果: 2

a = a + 1a += 1 は同じ処理をします。つまり、+= という演算子は「変数 a の中身に1をたして a に入れ直す」という処理をする演算子です。このような処理をする演算子は、すべての数値演算で用意されています。

演算子の種類 計算の一例 演算の内容
+= a += b a の中身に b をたした数値を a に代入しなおす
-= a -= b a の中身から b をひいた数値を a に代入しなおす
*= a *= b a の中身に b をかけた数値を a に代入しなおす
/= a /= b a の中身を b でわった数値(実数値)を a に代入しなおす
//= a //= b a の中身を b でわり、小数点以下を切り捨てた数値(整数値)を a に代入しなおす
%= a %= b a の中身を b で割ったときの余りを a に代入しなおす
**= a **= b a の中身の b 乗を a に代入しなおす

比較演算子

Pythonのデータ型のひとつに 論理値(boolean) と呼ばれるものがあります。これは TrueFalse の2種類しかないデータ型です。文字列のように見えますが文字列ではありません。クォーテーションは囲まずに TrueFalseと記述します。この論理値は int() を使うことで数値に変換することが可能で、 True1False0 となります。

このTrueFalse は「特定の条件を満たしているか否か」を調べた結果として出力されるデータです。指定した条件を満たしていれば True、満たしていないなら False になります。たとえば、「年齢が20歳を超えているか」を調べたときに20歳を超えていれば True、20歳以下なら False です。

age = 25
age > 20  # 出力結果:True
age = 18
age > 20  # 出力結果:False

上記の1つ目の例では、最初に変数 age の中身を25にしているので age > 20age が20よりも大きい)は条件を満たしているので True です。次の例は age の中身を18に変更したため、age > 20 は条件を満たさず False になります。>(「より大きい」を調べる演算子)のような演算子のことを 比較演算子 と呼んでいます。

Pythonで用意されている比較演算子をまとめます。

演算子の種類 使い方の例 内容
> a > b a の中身は b の中身より大きいかどうか
< a < b a の中身は b の中身より小さいかどうか
>= a >= b a の中身は b の中身以上かどうか
<= a <= b a の中身は b の中身以下かどうか
== a == b a の中身と b の中身が同値かどうか
!= a != b a の中身は b の中身と違う値かどうか

論理演算子

年齢が「18歳以上 かつ 65歳以下」のように複数の条件を設けたい場合は 論理演算子 を用います。論理演算子は以下の3つしかありません。

演算子の種類 使い方の例 内容
and (age >= 18) and (age <= 65) 論理積。すべての条件式を満たす場合にのみ Trueになる
or (age >= 18) or (age <= 65) 論理和。ひとつの条件式さえ満たしていれば True になる
not not(a >= 18) 否定。指定された条件式を満たせば False、満たさないなら True になる

ひとつ例を示します。以下のように変数 age を初期化します。

age = 70

その上で and or not を使ってみます。

(age >= 18) and (age <= 65)  # 出力結果:False
(age >= 18) or (age <= 65)  # 出力結果:True
not(age >= 18)  # 出力結果:False

上記の1つ目の例は and で「age の中身が18歳以上 かつ 65歳以下」かどうかを調べています。age >= 18 は満たしますが age <= 65 は満たさないため、全体としては False になります。

2つ目の例は or で「age の中身が18歳以上 または 65歳以下」かどうかを調べています。age >= 18 は満たしているので、全体としては True になります。

最後の例は not で「age の中身が18歳以上 ではない」かどうかを調べています。age >= 18 という条件式は満たしているので、全体としては逆転して False になります。

and&or| という記号に置き換えが可能です。
論理積の例:(age >= 18) & (age <= 65)
論理和の例:(age >= 18) | (age <= 65)

6. 条件分岐

Pythonに限らずプログラミング言語では、基本的に上から記述した順に命令が実行されます。

flow_01.png

このような処理を 逐次処理 と言います。(補足:上の図は「アクティビティ図」と呼ばれる形式で描いた、処理の流れを表す図です。)

しかし「特定の条件によって処理を分けたい」ときは、少し異なった記述の仕方が必要です。

6.1 if文

テストの点数が60点以上であれば「合格」と表示するプログラムを考えます。

flow_02.png

こういったプログラムでは、テストの点数が「60点以上か、それ未満か」で処理の内容が変わります。上の流れ図では ◇ の絵のところで処理が分かれています。テストの点数(score)が60点以上なら「合格です」と表示し、score が60点未満なら「合格です」と表示する部分を飛ばして次の処理に向かう、という流れです。

条件によって分岐する処理を記述する場合、if文というものを使います。

score = int(input("点数を入力してください:"))

if score >= 60:
    print("合格です")

print("処理を終了します")

score = 80 のときの出力結果:

合格です
処理を終了します

score = 40 のときの出力結果:

処理を終了します
input() は処理の途中で一時停止し、キーボードからデータ入力を求めるために使う命令(関数)です。表示されたテキストボックスにデータを入力後、Enter(Return)キーを押すと、処理が再開します。なお input() で受け取ったデータは、すべて文字列のデータ型となるため、int() で数値化しています。

60点以上の数値を入力したときに限り「合格です」と表示することが確認できました。if x >= 60: という部分が if文 と呼ばれている、条件分岐の処理のために使う文です。if には「もしも」という意味があるとおり、if (条件式): と記述することで「もし (条件式) を満たしていれば」という判定をしてくれます。条件式の後ろのコロン(:)は必須です。

上記のコードでは条件式を x >= 60 としました。この条件式を満たす場合のみ「合格です」が表示されることになります。

if文の通用範囲とインデント

さきほどのコードを以下のように修正してください。

score = int(input("点数を入力してください:"))

if score >= 60:
    print("合格です")
    print("おめでとうございます!")

print("処理を終了します")

同じように実行確認してみてください。

score = 80 のときの出力結果:

合格です
おめでとうございます!
処理を終了します

score = 40 のときの出力結果:

処理を終了します

60点以上のときは「合格です」に続けて「おめでとうございます!」と表示されます。60点未満の場合は、2つの文のどちらも表示されません。しかし「処理を終了します」の表示は、点数によらず毎回必ず表示されます。

print("合格です")print("おめでとうございます!") だけが60点以上のときに表示されるのは、これらの2文の行頭にスペースを追加したからです(行頭のスペースのことを インデント といいます)。Pythonではスペース4個分がインデントの設定として推奨されています。なお、Cloud9では、自分でスペースキーを4回押すだけでなく、Tabキーを1回押す形でも構いません。

一般的なプログラミング言語では、インデントはプログラムのソースコードを見やすくするために追記します。Pythonではコードを見やすくすること以外に if をはじめとした各種制御の通用範囲を示すためにも使われています。if x >= 60: と記述した後の行でインデントをしている命令のみが条件分岐の対象となります。

flow_03.png

色分けしてみました。インデントがされていないピンクの部分が必ず実行される部分で、インデントされている黄緑色の部分が x >= 60 の条件式を満たす場合のみ実行される部分です。

最初のうちはインデントで通用範囲を示すことに戸惑うかもしれませんが、繰り返しの場合などでもインデントで通用範囲を示すことになるので、徐々に慣れていってください。

複数の条件式を指定する

andor といった論理演算子も条件式に利用できます。

age = int(input("年齢を入力してください:"))

if (age >= 18) and (age <= 65):
    print("どうぞお乗りください")

print("処理を終了します")

実行確認してください。

age = 25 のときの出力結果:

どうぞお乗りください
処理を終了します

age = 70 のときの出力結果:

処理を終了します

年齢(age)が18歳以上 かつ 65歳以下であれば「どうぞお乗りください」が表示されます。

if文の中にif文を記述する(ネスト)

複数の条件を指定する方法として、もうひとつ、if文の中にif文を入れるという方法があります。(ネスト、もしくは入れ子と呼ばれます)

age = int(input("年齢を入力してください:"))

if age >= 18:
    if age <= 65:
        print("どうぞお乗りください")

print("処理を終了します")

実行確認しましょう。

age = 25 のときの出力結果:

どうぞお乗りください
処理を終了します

age = 70 のときの出力結果:

処理を終了します

最初に age >= 18 の条件式を調べ、次に age <= 65 の条件式を調べるという流れは and を使った場合と同じです。ただ「段階を踏んでいる」という意図や意味合いは、ネストの書き方の方が伝わりやすいのではないでしょうか。

flow_03_01.png

外側のif文と内側のif文の通用範囲もインデントの数によります。内側のifの処理を記述する場合は、外側の2倍(スペース8個分)、インデントを追加してください。

6.2 if-else文

「条件を満たさない場合も別の処理をしたい」とき、たとえば、60点以上が「合格です」と表示するのに対して60点未満は「不合格です」を表示したいとします。

flow_04.png

その場合は else という句を追加して if-else 文にします。

score = int(input("点数を入力してください:"))

if score >= 60:
    print("合格です")
    print("おめでとうございます!")
else:
    print("不合格です")
    print("もっとがんばりましょう")

print("処理を終了します")

実行確認してみてください。

score = 80 のときの出力結果:

合格です
おめでとうございます!
処理を終了します

score = 40 のときの出力結果:

不合格です
もっとがんばりましょう
処理を終了します

点数(score)が60点以上なら「合格です」「おめでとうございます!」と表示され、60点未満なら「不合格です」「もっとがんばりましょう」という表示になります。条件式を満たすときはifの中の処理が行われるのは変わりありませんが、if-else で記述したとき、条件式を満たさない場合にはelseの中の処理が実行されるのです。

flow_05.png

else にはインデントを入れていない点に注意してください。if のキーワードと同じ位置です。else にインデントを入れてしまうと if 内の処理のひとつとして判断されてしまうので気をつけましょう。ただし、else のときに実行したい処理にはインデントを入れてください。

6.3 if-elif-else文

特定の条件式を「満たすとき」「満たさないとき」の二択だけではなく、「ある条件を満たすとき」「ある条件は満たさないけど、この条件なら満たすとき」「どれも満たさないとき」のように3つ以上に分岐する場合を考えます。たとえば、今までは60点以上で「合格です」、60点未満なら「不合格です」としていましたが、90点以上なら「優秀です」と表示する仕様を追加したいとします。

flow_06.png

これを実現する一例は、else の中に if を作る方法です。

score = int(input("点数を入力してください:"))

if score >= 90:
    print("優秀です")
    print("大変よくできました!!")
else:
    if score >= 60:
        print("合格です")
        print("おめでとうございます!")
    else:
        print("不合格です")
        print("もっとがんばりましょう")

print("処理を終了します")

score = 95 のときの出力結果:

優秀です
大変よくできました!!
処理を終了します

score = 75 のときの出力結果:

合格です
おめでとうございます!
処理を終了します

score = 55 のときの出力結果:

不合格です
もっとがんばりましょう
処理を終了します

最初 if score >= 90 で「点数が90点以上かどうか」を調べます。90点以上ではなかった場合、else の中に記述した if の条件である「点数が60点以上かどうか」を判定します。この条件を満たすかどうかで「合格です」か「不合格です」が表示されます。

flow_07.png

もうひとつの方法は、ifelse の間に elif という句を追記する方法です。

score = int(input("点数を入力してください:"))

if score >= 90:
    print("優秀です")
    print("大変よくできました!!")

elif score >= 60:
    print("合格です")
    print("おめでとうございます!")

else:
    print("不合格です")
    print("もっとがんばりましょう")

print("処理を終了します")

score = 95 のときの出力結果:

優秀です
大変よくできました!!
処理を終了します

score = 75 のときの出力結果:

合格です
おめでとうございます!
処理を終了します

score = 55 のときの出力結果:

不合格です
もっとがんばりましょう
処理を終了します

流れは基本的に一緒です。最初の条件(score >= 90)が満たされていれば、その下にある elifelse は無視されます。score >= 90 ではなかった場合に、 elif に記述した score >= 60 を満たしているかを確認します。それに応じて処理が実行されます。

flow_08.png

条件が4つ以上でも elif は利用できます。

season = input("季節を入力してください:")

if season == "":
    print("春はあけぼの")
elif season == "":
    print("夏は夜")
elif season == "":
    print("秋は夕暮れ")
elif season == "":
    print("冬はつとめて")
else:
    print("エラー")

print("処理を終了します")

season = "春" のときの出力結果:

春はあけぼの
処理を終了します

season = "夏" のときの出力結果:

夏は夜
処理を終了します

season = "秋" のときの出力結果:

秋は夕暮れ
処理を終了します

season = "冬" のときの出力結果:

冬はつとめて
処理を終了します

season = "季節" のときの出力結果:

エラー
処理を終了します

これくらいでしたら else の中に if を記述するネストの方法でもOKですが、幾重にもネストすることになり、ソースコードが見づらくなるので、オススメしません。分岐が多い場合は elif を使いましょう。

7. 繰り返し

「同じ処理を○回繰り返したい」ときの書き方を学びます。

たとえば、整数の1から10までを連続して表示したいとします。このとき、

print(1)
print(2)
print(3)
print(4)
print(5)
print(6)
print(7)
print(8)
print(9)
print(10)

このように記述しても良いかもしれませんが、かなり面倒です。「特定の条件式を満たす間だけ、同じ処理を繰り返す」「10回だけ、同じ処理を繰り返す」というように記述した方が、楽に記述できます。

flow_09.png

こういった記述をするには whilefor という文を使います。

7.1 while文

最初に while 文について学習しましょう。while 文は if と同様に「とある条件式を満たす間だけ」処理を繰り返し実行します。

i = 0

while i < 10:
    i += 1
    print(i)

print("処理を終了します")

出力結果:

1
2
3
4
5
6
7
8
9
10
処理を終了します

このとおり、if と記述の仕方がさほど変わりません。while (条件式): と記述して「条件式を満たす間」という指定をします。そして、繰り返したい命令にインデントをつけてください。これも if と同じです。

flow_10.png

無限ループに注意

while の条件式を、間違えて次のように記述してしまったとします。(すぐには実行しないでください!!)

i = 0

while i >= 0:
    i += 1
    print(i)

print("処理を終了します")

条件式が i >= 0 となっていますが、このプログラムにおいて変数 i の中身が0を下回ることがないため、永遠と繰り返しの処理が実行されてしまい、いつまでもプログラムが終了しません。このような状態を 無限ループ と呼びます。繰り返し処理を実装する際は無限ループにならないよう、気をつけてください。

なお、不注意で無限ループの状態で実行してしまった、もしくは上記を実行してみた、という場合は「■」ボタンを押すことで処理が止まります。覚えておいてください。

break

while True: というように条件式の部分を True とだけ記述することで、意図的に無限ループにすることもあります。その場合は、繰り返し処理の中で if を記述して「特定の条件を満たすときに繰り返し処理を終了」させる必要があります。そのために使うのが break です。

i = 0

while True:
    i += 1

    if i > 10:
        break

    print(i)

print("処理を終了します")

実行結果は最初の例と同じです。ループの中で、条件式を i >= 10 とする if を記述し、その条件式が満たされるときに break が実行されます。break繰り返し処理を強制終了するという命令です。break が実行されると、その下にあって、なおかつ while の通用範囲にある print(i) は実行されません。while の通用範囲の下にある print() で「処理を終了します」を表示させます。

flow_11.png

continue

break に似ているのですが continue という文も用意されています。continue繰り返し処理の最初に戻すという命令です。

i = 0

while True:
    i += 1

    if i > 10:
        break

    if i % 2 == 1:
        continue

    print(i)

print("処理を終了します")

出力結果:

2
4
6
8
10
処理を終了します

break を使ったコードに、もうひとつ if を追加しました。2つ目の if では i % 2 == 1i の中身を2で割った余りが1、つまり i の中身が奇数かどうか)を調べています。これを満たす場合に continue が実行され、その下にある print(i) を無視して繰り返し処理の最初に戻り、i += 1 を再び実行します。その結果、画面には1から10までの「偶数」だけが表示されることになります。

flow_12.png

7.2 for文

while の他に、もうひとつ、繰り返し処理を実現する方法が for です。

for は最初から繰り返す回数が決まっている場合に使うと便利です。繰り返す回数が動的に変化する(プログラムの実行時に毎回変わる)場合は while を使います。

forwhile の間で大きく異なる点は記述方法のみです。同じ「繰り返し処理」ですので breakcontinuefor でも利用できます。

さっそく for を使った繰り返し処理を記述してみましょう。

for i in range(11):
    print(i)

出力結果:

0
1
2
3
4
5
6
7
8
9
10

たった2行だけで0から10までの連続する整数値を表示できました。しかし for i in range(11): だけ見ると意味不明に思われたかもしれません。分解して見てみましょう。

for は繰り返し処理のキーワードです。

i は繰り返し処理の中だけで使える変数でカウンタ変数とも呼ばれています。処理が繰り返される度にカウンタ変数iの中身が上書きされます。上書きされる仕組みは、これに続く要素が関係します。

in は、その後ろに続く要素から1件ずつ取り出してカウンタ変数に格納するという意味のキーワードです。

その in の後ろに続く要素は、後のレッスンで学ぶ「リスト」などが該当しますが、ここでは range() という関数を使いました。range()連続する数値を順番どおり並べたもの(正体はリスト)を取得するための関数です。range() で取得した数の並びを、先頭から1件ずつ取得してカウンタ変数(i)に格納します。最後の数について一連の繰り返し処理が終了したタイミングで for のループが終了します。なお、range(11) と書きましたが、引数を 11 とすることで「0以上11未満」の連続する数値を取得することになります。なお、whileif と同じく、最後のコロン(:)は忘れずにつけてください。

flow_13.png

range()関数について補足

基本的に range() は0以上となりますが、1以上としたい(0は不要)としたい場合は、引数を2つにして、最初の引数を 開始の数値, 次の引数を 終了の数値 としてください。

for i in range(1, 11):
    print(i)

出力結果:

1
2
3
4
5
6
7
8
9
10

これにより「1以上11未満(1から10まで)」となります。

また「偶数のみ」としたいときは、3つ目の引数を追加します。1番目と2番目は、さきほどの 開始の数値 終了の数値 と同じですが、3つ目は 増分 です。増分を 2 とすることで +2 ずつの数値のならびにできます。

for i in range(2, 11, 2):
    print(i)

出力結果:

2
4
6
8
10

数値の大きさを 開始の数値 > 終了の数値にし、なおかつ 増分 をマイナスの数値にすることで数値の大きい順で1件ずつカウンタ変数に格納することもできます。

for i in range(10, 0, -2):
    print(i)

出力結果:

10
8
6
4
2

繰り返し処理の中に繰り返し処理を記述することもできます。その場合、内側の繰り返し処理がひととおり完了すると外側の繰り返し処理に戻り、再度内側の繰り返し処理が実行される、という流れになります。

for i in range(11):
    for j in range(11):
        print(f"{i} + {j} = {i + j}")

出力結果:

0 + 0 = 0
0 + 1 = 1
0 + 2 = 2
...(中略)...
0 + 9 = 9
0 + 10 = 10
1 + 0 = 1
1 + 1 = 2
...(中略)...
10 + 8 = 18
10 + 9 = 19
10 + 10 = 20

カウンタ変数の使い方に注意

上記の ijfor の処理のために用意した変数です。for の外側で ij は参照すべきでありません。カウンタ変数に限らず、また ifwhile でも同様ですが、それらの中で用意した変数は、外部からは参照しないようにすることをオススメします。

8. 関数

ここまでの中で print()len() などのものを「命令(関数)」という書き方をしてきました。関数(function)という言葉は学校の数学で習いました。Pythonにおける関数も、数学の関数とほぼ同じ意味という理解で問題ありません。

たとえば、

y = f(x)

このような形の関数を数学で学びました。x という変数で定義された計算式があり、x に何らかの数値を代入して計算すると1つの答え(y)が得られるというものです。

f(x) = 2x + 3

f(x)が上記のように定義されているとすると、x に1を代入して計算したら5に、x に2を代入して計算したら7になります。

Pythonの関数もほぼ同じです。何らかのデータを関数に与えると、それが処理されて、何らかの結果が得られます。Pythonの関数では、関数に与えるデータのことを 引数(ひきすう)、関数が処理して得られた結果のことを 戻り値(返り値) といいます。引数は1つの場合もあれば2つ以上、もしくは何も必要ない場合もあります。戻り値も、何らかのデータが得られる関数もあれば、画面に表示するだけで別に何かデータが得られるものではない関数もあります。

たとえば、本カリキュラムで最初に使ったPythonの関数は print() でした。print() はカッコの中に「画面へ表示したいデータ」を記述します。それを実行することで指定したデータが画面に表示されますが、とくに何かデータが加工されて戻り値として得られる関数ではありません。

print("Hello, world!")

8.1 関数とメソッド

Pythonの命令は、関数と メソッド(method) にわけられます。メソッドの命令も今後登場しますので、「関数」と「メソッド」の違いについて触れておきます。

コラム:オブジェクトとは何か
それを説明する前に オブジェクト について説明します。Pythonでは数値も文字列も、データはすべてオブジェクトです。オブジェクト(object)を直訳すると「物体」という意味です。プログラム上のデータの実体を目で見ることができませんが、「物体がある」ものとして考えてください。「物体」ではわかりにくい場合、「動物」や「人」に擬態化して考えてもよいでしょう。

print()input() などの「関数」は、以下のように記述するものでした。

関数名(引数)

それに対し「メソッド」は、以下のように記述します。

データ.メソッド名(引数)

「引数を持つ」点は共通しています。違いは ドット(. の有無です。関数は最初から用意されているものなので、単純に関数名のみを指定すれば大丈夫でした。しかし メソッドはオブジェクトが持っているもの なので、「○○が持つ△△を実行する」という書き方をしなければなりません。

「データ」と「メソッド名」の間にあるドット(.)は助詞の「の」だと思ってください。「○○ △△を実行する」です。

8.2 自分で関数を作って使う

一連の処理をひとまとめにして、独自の関数として定義できます。

関数の基本(引数も戻り値もない関数)

さっそく関数を作ってみましょう。

先に、ひとつだけ。忘れてはいけないのは、複数の処理を関数という形でひとまとめにしておくと 後から再度、その処理を実行しやすくなる という利点です。このようなことを、よく 再利用 と呼ぶことがあります。自分で関数を作る理由は再利用のためだと思ってください。使用目的を見失うと学習する意味がわからなくなり、モチベーションの低下に繋がります。ぜひ心に留めておいてください。

では、話を戻して、関数の作り方を学びましょう。関数を作るには def というキーワードを使います。まずは話を簡単にするため、引数も戻り値もない関数を作りましょう。Pythonファイルを作成して、以下のコードを書いてください。

def hello():
    print("Hello!")
    print("It is a fine today!!")

def good_morning():
    print("Good morning!")
    print("Have a nice today!!")

hello()
good_morning()

出力結果:

Hello!
It is a fine today!!
Good morning!
Have a nice today!!

def と書かれた行の最後に : がついていて、次の行にインデントが設定されていることから気づいたかもしれませんが、ifwhilefor と同じで def もインデントが通用範囲となります。インデントの設定されている部分が、その関数の通用範囲となります。

def_01.png

なお、関数を利用する際は 先に関数の定義を読み込む必要があります ので注意してください。

引数がある関数の定義

引数をもつ関数を作りたい場合は、def 関数名(引数1, 引数2, ...) のように記述してください。

def introduce(name, age):
    print("Hello!")
    print(f"My name is {name}.")
    print(f"I'm {age} years old.")

introduce("tanaka", 25)
introduce("suzuki", 30)

出力結果:

Hello!
My name is tanaka.
I'm 25 years old.
Hello!
My name is suzuki.
I'm 30 years old.

上の例では nameage という2つの変数を定義しました。気をつけたいのは、引数には 順序 があるという点です。つまり introduce() を実行する際、最初に指定したデータが name に、2番目に指定したデータが age に入ります。"tanaka"25 の指定を逆にすると意図しない動作になってしまいます。

def_02.png

キーワード引数を使う

Pythonでは、上記のように順序(位置)で引数にデータを指定する他に キーワード引数 という仕組みが導入されています。関数を実行する記述の中でキーワード引数を使って キーワード引数名 = データ と記述することで、順序に関係なく、引数にデータを指定することが可能です。

def introduce(name, age):
    print("Hello!")
    print(f"My name is {name}.")
    print(f"I'm {age} years old.")

introduce("tanaka", 25)
introduce("suzuki", 30)
introduce(name = "sato", age = 20)  # 追加
introduce(age = 35, name = "takahashi")  # 追加

出力結果:

...(一部省略)...
Hello!
My name is sato.
I'm 20 years old.
Hello!
My name is takahashi.
I'm 35 years old.

"takahashi" さんのデータ(順序を入れ替えて指定したもの)が正常に実行されていることを確認してください。

引数のデフォルト値

関数の定義の中で引数にデフォルト値を指定すると、関数を実行する記述を書く際にデータの指定を省略できます。引数にデータを指定しなかった場合、定義で記述したデフォルト値が採用されます。もちろん、今までどおり引数にデータを記述すれば、そのデータが関数内で使われることになります。

def introduce(name = "nanashi", age = 18):
    print("Hello!")
    print(f"My name is {name}.")
    print(f"I'm {age} years old.")

introduce("tanaka", 25)  # 引数を全て指定
introduce("suzuki")      # age を省略
introduce(age = 30)      # name を省略
introduce()              # name も age も省略

出力結果:

Hello!
My name is tanaka.
I'm 25 years old.
Hello!
My name is suzuki.
I'm 18 years old.
Hello!
My name is nanashi.
I'm 30 years old.
Hello!
My name is nanashi.
I'm 18 years old.

戻り値がある関数の定義

ここまで作った関数は print() しかしていないため、戻り値はありませんでした。一連の処理をした結果として何らかのデータを戻したいときは return というキーワードを使います。

def calc_bmi(height, weight):
    ret = weight / height ** 2
    return ret

height = float(input("身長(m)を入力:"))
weight = float(input("体重(kg)を入力:"))

bmi = calc_bmi(height, weight)
print(f"BMI値:{bmi:.1f}")

出力結果:

身長(m)を入力:1.7
体重(kg)を入力:60.1
BMI値:20.8

上記の例では、身長と体重のデータを基にBMIの数値を計算する calc_bmi() を作りました。計算で求まったBMIの値を return で戻しています。return には計算式を指定することもできるので bmi = weight / height ** 2return bmi を1行にまとめて return weight / height ** 2 と記述しても構いません。

関数を実行する部分では、戻り値を変数 bmi で受け取っています。なお print() の中で指定している .1f は「小数第一位まで表示する」という意味の書式指定です。

変数の通用範囲に注意

calc_bmi() の中で用意した変数 ret は、関数定義の中でのみ利用できる変数で、外からは参照できませんので注意してください。外から参照しようとしても「ret が何なのかわからない」という旨のエラーになります。このような変数は「ローカル変数」と呼ばれています。

途中で関数から抜ける方法

繰り返し処理における break のように関数でも「途中で処理を終了する」ことができます。その際に使うのが None と呼ばれるオブジェクトです。None何もない ことを意味しています。「何もない」のに「オブジェクト」というのは理解しにくいかもしれませんが、とりあえず「何もないという意味である」と覚えてもらえれば大丈夫です。

関数定義の中で、「途中で抜ける」には return None と記述してください。

def sum_nums():
    max_num = int(input("数値を入力してください:"))

    if max_num < 0:
        return None

    ret = 0
    for i in range(max_num + 1):
        ret += i

    return ret

print(sum_nums())

出力結果:

数値を入力してください:10
55
数値を入力してください:-1
None

上記のプログラムは、1から「キーボード入力された数値」までの自然数の和を求める sum_nums() 関数を作ったプログラムです。ただし負の数が入力された場合は None を戻し、和を求める繰り返し処理は実施しません。

return None の部分は単に return のみでも構いませんが、正しくない入力値だった場合でも何らかの戻り値を設定した方が「この関数には戻り値がある」という仕様に一貫性を持たせられるので、上記の例では return None としています。

9. シーケンス1:文字列

ここから シーケンス と呼ばれる要素について学びます。Pythonでは以下のものがシーケンスとして用意されています。

  • リスト(list)
  • タプル(tuple)
  • レンジ(range() で取得できる数値の一覧)
  • 文字列(string)

次の2つは厳密に言うとシーケンスに属しませんが、同列で説明されることが多い要素です。

  • セット・集合(set)
  • 辞書(dict)

9.1 シーケンスとは何か

変数をひとつの箱と例えるなら、シーケンスは箱が繋がったもの、すなわちロッカーみたいなものだと思ってください。他のプログラミング言語の経験者でしたら「配列」と言えば理解いただけるかと思います。

ひとつのロッカーの入れ物には、ひとつのオブジェクト(データ)しか格納できませんが、同じようなデータを1か所にまとめて管理できます。

また、一般的にロッカーには番号が振られています。場所を忘れても番号を覚えておけば自分の預けた荷物は取り出せます。

1か所で複数のオブジェクトを管理できること、番号が振られていること。これがシーケンスの特徴です。

9.2 文字列のデータ構造

文字列もシーケンスの一種です。

今までは、たとえば、

s = "TellusxData"

このような1行の命令があったとしたら「変数 s"TellusxData" という11文字の塊が格納されている」という認識でプログラミングをしてきました。しかし厳密に言うと、この場合「変数 s要素数11個のシーケンス で、先頭から順に T, e, l, l, u, s, x, D, a, t, a が1文字ずつ格納されている」のです。文字列の場合、下記のように横一列に連なっているロッカーをイメージしていただければOKです。

seq_str_01.png

ロッカーの上に記載した赤い数字がロッカーの番号です。注意してほしいのは、番号は0からはじまる 点です。range(11) が「0以上11未満の連続する数」であったのと同じです。「なぜ1ではなく0からスタートなのか」と思うかもしれませんが、仕様なので覚えておきましょう。

また、ロッカーの下に記載した青いマイナスの数字も利用可能です。マイナスの数字を利用することで、最後から逆順で辿ることができます。こちらも、ぜひ覚えておいてください。

9.3 文字列のスライス

文字列に限らずリストなどのシーケンスでは、部分的に要素を抜き出すことができます。この操作を スライス と呼んでいます。

ここでは、以下のように小文字のaからzの26文字で構成される文字列を用意します。

s = "abcdefghijklmnopqrstuvwxyz"

特定の1要素を取得する

まずは特定の1要素を取得する方法です。文字列変数名[番号]と入力してください。

記述内容 出力結果
s[0] 'a'
s[1] 'b'
s[2] 'c'
s[24] 'y'
s[25] 'z'

なお、s は要素数が26個なので番号としては0から25までです。26番目以降は文字が存在しません。存在しない場所を指定すると IndexError になりますので、注意してください。

# IndexErrorになる例
s[26]

出力結果:

---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-45-110a7b63557e> in <module>
----> 1 s[26]

IndexError: string index out of range

マイナスの数値を指定する手段も確認しておきましょう。

記述内容 出力結果
s[-1] 'z'
s[-2] 'y'
s[-3] 'x'
s[-25] 'b'
s[-26] 'a'

マイナスで指定する場合も、範囲に気をつけてください。

# IndexErrorになる例
s[-27]

出力結果:

---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-47-f0ae0de6c75e> in <module>
----> 1 s[-27]

IndexError: string index out of range

部分的に文字列を取り出す

文字列の一部分(複数文字)を取り出したいときは 文字列変数名[開始位置:終了位置] と記述します。こうすることで、開始位置の場所から 終了位置 - 1 の場所までの文字列を取得できます。終了位置には気をつけましょう。

記述内容 出力結果
s[0:7] 'abcdefg'
s[7:14] 'hijklmn'
s[14:21] 'opqrstu'
s[21:26] 'vwxyz'
s[-5:-1] 'vwxy'

開始位置 および 終了位置は省略可能です。省略した場合、それぞれ左端と右端の位置が格納されたのと同じ動作になります。

記述内容 出力結果
s[:10] 'abcdefghij'
s[10:] 'klmnopqrstuvwxyz'
s[-5:] 'vwxyz'
s[:-20] 'abcdef'
s[:] 'abcdefghijklmnopqrstuvwxyz'

最後の例のように、両方を省略することも可能です。

飛び飛びで文字列を取得する

部分的に文字列を ○個おきで 取得したい場合は、コロン(:)区切りを1つ増やし、3つ目のところに「飛び飛びの数(増分)」の数値を指定してください。文字列変数名[開始位置:終了位置:増分] となります。

記述内容 出力結果
s[0:10:2] 'acegi'
s[1:20:3] 'behknqt'
s[-1:-6:-1] 'zyxwv'
s[-1:-10:-2] 'zxvtr'
s[::-2] 'zyxwvutsrqponmlkjihgfedcba'
s[::] 'abcdefghijklmnopqrstuvwxyz'

増分 をマイナスの数値にすると取得した文字列が逆順になり、省略すると1が入力されたのと同じ動作になります。

10. シーケンス2:リストとタプル

さきほどは、わかりやすい例として文字列でシーケンスの特徴を説明しました。次は代表的なシーケンスである「リスト」と「タプル」についてです。

10.1 リストを作る

リストは、汎用的に利用できるロッカーです。各要素に2文字以上の文字列を格納することや、バラバラな数値を格納できます。

まずはリストを作ってみましょう。

[]でリストを作る

リストは、[ ] のカッコの中に要素を記述すれば作れます。

リストの変数名 = [ 要素0, 要素1, 要素2, ...]

以下は一例です。

a = [2, 5, 3, 6, 1, 4]
b = ["tanaka", "suzuki", "takahashi"]
c = []
d = [3, "test", 7, "hoge", "fuga", 10]

エラーが出なければ、リストの作成が問題なく完了していると考えて構いません。c のように要素がない空のリストも作れます。また d のように文字列と数値が混在したリストも作れます。ただし、複数のデータ型が混在するリストは、あとで要素を取得してデータ処理したい場合に想定外の実行時エラーが発生するかもしれないので、控えた方が無難です。基本的にリストを作る際、明確な意図がない限りは 数値だけ、文字列だけ、のように1種類のみで要素を構成させる ことをオススメします。

list()でリストを作る

もうひとつ、list() という関数を使うことでもリストを作成できます。ただし、list() は、文字列(テキストシーケンス型)や range() による数値のシーケンスなど、他の型のシーケンスをリストへ変換したい場合に使います。引数には文字列や range() を指定してください。

list() の例
e = list("TellusxData")
e

出力結果: ['T', 'e', 'l', 'l', 'u', 's', 'x', 'D', 'a', 't', 'a']

range() の例
f = list(range(1, 11, 2))
f

出力結果: [1, 3, 5, 7, 9]

e の動作について最初は戸惑うかもしれませんが、テキストシーケンス型の特徴を考えれば、納得できるのではないでしょうか。また、ただ単に list() と記述することで、要素が0の空のリストが作れます。

g = list()
g

出力結果: []

10.2 リストの要素を管理する

要素の参照

リストにある要素を参照したいときは、文字列のところで紹介した「スライス」を使います。

リスト変数名[番号]
リスト変数名[開始位置:終了位置]
リスト変数名[開始位置:終了位置:増分]

さきほど作成した a から g までのリストについて、スライスして遊んでみてください。

要素の追加

リストに要素を追加するには、リストがもつメソッドのひとつ append() を使います。

fruits = []
fruits.append("apple")
fruits.append("banana")
fruits.append("orange")
fruits

出力結果: ['apple', 'banana', 'orange']

append() はリストの最後尾に要素を追加します。最後尾以外の特定の場所に要素を追加したい場合は insert() メソッドを使います。insert(挿入位置, 要素) と記述することで、指定した挿入位置に要素が追加されます。先ほどの fruits のリストを引き続き使って確認してみましょう。

fruits.insert(1, "peach")
fruits

出力結果: ['apple', 'peach', 'banana', 'orange']

要素の更新

特定の要素の中身を変更したい場合は「スライス」を使ってデータを上書きすればOKです。専用のメソッドはありません。

fruits[2] = "strawberry"
fruits

出力結果: ['apple', 'peach', 'strawberry', 'orange']

なお、存在しない番号の要素には適用できません。それをするとエラーになります。

要素の削除

リストから特定の要素を削除する(抜き取る)には pop() というメソッドを使います。pop() の引数を省略するとリストの最後尾の要素を削除します。

fruits.pop()
fruits.pop()
fruits

出力結果: ['apple', 'peach']

pop() には戻り値があります。リストから削除された要素が戻ってきます。そのため、a = fruits.pop() というような記述方法により「各要素を表示する」ような使い方もできます。

なお、pop(1) のように数値を指定すると、その場所の要素が削除となります。マイナスの数値(逆順)を引数に指定することも可能です。また、リストに何件入っていようが全件削除して空のリストにしたい場合は clear() メソッドを使ってください。

10.3 2次元リスト

今までは「横一列のロッカー」という例えでリストを使ってきましたが、縦と横に連なっている 2次元リスト も作れます。

list_01.png

リスト変数名[縦の番号][横の番号] で該当の要素にアクセスできます。上図のリスト a でいえば2がある場所は a[0][1]、 12がある場所は a[3][2] です。2次元リストは「縦と横の表」で例えるのが一般的ですが、下図のように「リストの要素がリストになっているネスト(入れ子)構造」という理解でも構いません。

list_02.png

実際に2次元リストを作ってみましょう。以下のようにネスト構造を作ります。

list_2d = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]
list_2d

出力結果: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]

補足:多次元リスト

今回は2次元までとしましたが、さらにネストを深くして3次元、4次元、5次元…というように多次元のリストを作ることもできます。ネストが深く(次元の数が多く)なりすぎると、ソースコードが読みにくくなる上に、処理完了まで時間がかかることになるので、安易な利用は控えた方が良いでしょう。

10.4 タプルとリストの違い

続いてはタプルです。

タプルもリストと同じで、複数の要素を管理できるロッカーみたいなものです。タプルとリストの決定的な違いは、リストが要素を変更可能であるのに対して タプルは要素の変更が不可 なシーケンスです。つまり、タプルは一度作成すると、要素の上書きや追加・削除ができません。

これだけ見ると、使いどころが限られてしまい「なぜタプルというものが存在するのか」疑問に思われるかもしれません。タプルがPythonで用意されている理由は、以下の3点にあると思ってください。

  • リストよりも高速に動作する

一般的なプログラミング言語では、便利に使える関数・メソッド・オブジェクトほど処理が遅くなる傾向にあります。タプルの方がリストよりも機能が少ない分、処理が高速になります。ただし、現在のコンピュータの性能を踏まえると、その恩恵はさほど感じにくいかもしれません。

  • 定数値として使える

変数について紹介したときに「どういう目的で使うものかが把握しやすい名前をつける」ことを説明しました。たとえば「繰り返しの回数」のような 不変な数値や文字列 であっても、変数に格納する(名前をつけて管理する)ことで目的や意図が把握しやすくなります。このようにした変数のことを 定数 と呼んでいます。タプルも同じで、複数のデータを管理したいけど変更する予定がないときにタプルを使います。

  • 辞書のキーでタプルを使える

これについては辞書のレッスンで解説します。今は「そうなんだ」とだけ思ってください。

10.5 タプルを作る

() を使ってタプルを作る

a = (1, 2, 3, 4, 5)
b = ("apple", "banana", "orange", "peach")
c = ()
d = (1, "hoge", "fuga", 5, 7, "aaa")

タプルに新しい要素は追加できませんが、タプルを格納する変数を上書きすること自体は可能です。また +* の演算子も利用できます。

num1 = (1, 2, 3)
num2 = (4, 5, 6)

# (1, 2, 3, 4, 5, 6) のタプル num3 として初期化
num3 = num1 + num2
要素が1つのタプルを作る場合

タプルに格納するデータが1つだけのときは注意が必要です。

a = (1)
type(a)

出力結果: <class 'int'>

上記のように a = (1) という書き方だと a は整数値 1 を格納する変数になります。これはタプルが、四則演算でも使う ( ) の括弧を使っているためであり、仕様です。これを防ぐには 1 のあとに , を追加します。

a = (1,)
type(a)

出力結果: <class 'tuple'>

タプルでは type() で型を調べると tuple と表示されます(補足:リストを type() で調べた場合の出力結果は list です)。

tuple() を使ってタプルを作る

list() と同様、タプルを作る関数として tuple() が用意されています。こちらも別のシーケンスから変換する用途で使うのが一般的です。

# ('T', 'e', 'l', 'l', 'u', 's', 'x', 'D', 'a', 't', 'a') のタプル e
e = tuple("TellusxData")

# (1, 3, 5, 7, 9) のタプル f
f = tuple(range(1, 11, 2))

# 空のタプル g
g = tuple()

10.6 タプルを使う

参照の方法はリストと同じく「スライス」を使います。ただし、タプルでは要素の新規登録・更新・削除ができません。できることは参照と検索のみですので、注意してください。

10.7 その他のトピックス

アンパック

リストやタプルで格納していた各要素を、ひとつひとつ変数を用意して格納したい場合、「アンパック」という手法が使えます。

nums = [10, 20, 30]
a, b, c = nums

リストの並び順で a , b , c に代入されます。その結果 a = 10b = 20c = 30 となります。

zip()

Pythonの標準関数 zip() を用いると、複数のリストを1つにまとめることができます。

a0 = [ 1,  2,  3]
a1 = [ 4,  5,  6]
a2 = [ 7,  8,  9]
a3 = [10, 11, 12]
temp_a = zip(a0, a1, a2, a3)
a = list(temp_a)

この結果できたリスト a[(1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)] という構成になります。

注意点が2つ。まず、新しいリストにできた各要素はタプルになります。また、[(1, 2, 3), (4, 5, 6), ...] とはならず、縦に並ぶ要素でリストの1要素(タプル)が作られます。

tuple_01.png

リスト a の各要素を確認してみてください。

記述内容 出力結果
a[0] (1, 4, 7, 10)
a[1] (2, 5, 8, 11)
a[2] (3, 6, 9, 12)

[(1, 2, 3), (4, 5, 6), ...] など zip() でできるものとは別の構造のリストを作りたい場合は、for文を駆使して意図した構造のリストを作成するか、次のレッスンで学ぶNumPyを活用してください。

enumerate()

同じくPythonの標準関数 enumerate() を使うと、(カウンタの数値, リストの要素) というペアでできたタプルを格納するリストを生成します。

sports = ["baseball", "soccer", "volleyball", "golf", "tennis"]
temp_sports = enumerate(sports, 1)
sports_tl = list(temp_sports)

この結果できたリスト sports_tl[(1, 'baseball'), (2, 'soccer'), (3, 'volleyball'), (4, 'golf'), (5, 'tennis')] という構成になります。

なお、2番目の引数の整数値は省略可能です。その場合、カウンタの数値は0からスタートします。増分の指定はできません。

11. シーケンス番外編:セットと辞書

ここまでシーケンス(sequence)というものを学びました。sequenceには「順序」という意味があります。今まで見てきた「文字列」「 range() で得られる数値の一覧」「リスト」「タプル」ともに番号が振られていて、スライスで番号を指定することで指定の要素を取得することができました。

しかし、次にあげるPythonの機能は、シーケンスと同様にロッカーに例えることができますが「順序」は持っていないので、厳密にはシーケンスではありません。

  • セット(集合)
  • 辞書

機械学習では主にリストと、次のレッスンで学ぶNumPy形式の配列のデータを扱います。とはいえセットや辞書の概要だけでも知っておくと役に立ちます。

そこで、ここではセットと辞書の簡単な使い方を学習します。

11.1 セットとは何か

セット(set) は日本語で言うと「集合」です。「集合」は、単なるグループだと思ってもらって構いません。「電車で通勤する人」「タバコを吸う人」などのような枠組みです。

あるデータが特定のグループに「あるかないか」、それだけです。つまり、セットでは同じ内容のデータを重複して登録することができません。グループの中に全く同じデータは2つ以上ありません。1つだけです。また、セットはシーケンスではないので、順序は保証されません

簡単に言えば上記のとおりですが、厳密にはPythonでの「集合」は、数学で学ぶ「集合」を指しています。そのため、「和集合」「積集合」など、集合に関する演算がPythonでは簡単に実行できます。数学の「集合」について忘れてしまった(習っていない)方は「そういうものなのか」という理解のみで構いません。

11.2 セットを作る

セットは { } の括弧で作ります。リスト([ ])やタプル(( ))とはまた違う括弧なので、注意してください。

a = {2, 5, 7, 1, 3, 6, 4}
b = {"apple", "banana", "orange", "peach"}
c = {}
d = {1, "hoge", 4, "fuga", "bar", 7}

c = {} で作ったものは空のセットだと思うかもしれませんが、実は、このあと学ぶ「辞書」となります。

記述内容 出力結果
type(a) <class 'set'>
type(b) <class 'set'>
type(c) <class 'dict'>

空のセットを用意したい場合は、set() を利用してください。list()tuple() と同様、シーケンスから変換する用途で使います。

# 順序が保証されないため、たとえば {'D', 'T', 'a', 'e', 'l', 's', 't', 'u', 'x'} のような順序になる
e = set("TellusxData")

# 空のセットを宣言する
f = set()

また、同じデータを複数登録することはできません。エラーも発生しません。

# 以下のセット fruits は {'banana', 'apple', 'orange', 'peach'} となる
# ('banana' を3つ格納しようとしているが実際には1つしか格納されない)
fruits = {"apple", "banana", "banana", "orange", "banana", "peach"}

11.3 セットを使う

順序が保証されていないセットではスライスが使えません。

既存のセットに要素を新規登録する場合は add() 、要素を削除するときは remove() のメソッドを利用します。

# 空のセットを宣言
sports = set()

# 要素を4つ追加して {'volleyball', 'baseball', 'golf', 'soccer'} とする
sports.add("baseball")
sports.add("soccer")
sports.add("golf")
sports.add("volleyball")

# 'golf' を削除する
sports.remove("golf")

# セットの内容を確認
sports

出力結果: {'volleyball', 'baseball', 'soccer'}

in を使うことで、セットの中に特定のデータが存在するかを調べることができます。

"baseball" in sports  # 出力結果:True
"tennis" in sports  # 出力結果:False

11.4 辞書とは何か

辞書もリストやタプルと同じロッカーみたいなものですが、番号ではなく「名前つきのロッカー」だと思ってください。「田中さんのロッカー」「鈴木さんのロッカー」みたいなものです。各要素につける名前のことを キー と呼びます。辞書にデータ(値)を新しい要素として登録する際は、値とキーをペアにしなければなりません。格納された特定の要素を参照する場合は、キーを指定して値を取得することになります。

下の図は、contact という辞書を作り、first_name, last_name, age, is_male, tel_no という5つのキーを用意して、first_name には "taro"last_name には "tanaka"age には 25is_male には True(男性)、 tel_no には "090-1234-5678" をそれぞれペアにして登録した状態のイメージです。

dict_01.png

11.5 辞書を作る

辞書を作る際は { } の括弧を使います。セットと同じです。ただし辞書の場合はキーと値をペアにして登録しますのでコロン( : ) を使って キー:値 のように要素を記述してください。

contact = {"first_name": "Taro", "last_name": "Tanaka", "age": 25, "is_male": True, "tel_no": "090-1234-5678"}

辞書で注意すべき点として、キーは辞書の中でユニーク(唯一無二)でなければなりません。全く同じキーで異なる値を登録しようとした場合、後の値で上書きされます。

d = {"foo": 1, "foo": 3, "bar": 5, "hoge": 7}
d

出力結果: {'foo': 3, 'bar': 5, 'hoge': 7}

辞書では list()tuple()set() と同じように dict() という関数が用意されています。しかし、使い方が複雑なため、詳細な内容については省略します。

11.6 辞書を使う

既に作った辞書に要素を新規で追加したいときや、要素を参照したいときは 辞書名[キー] と記述します。既に存在するキーを指定して値を入れようとした場合は、既存の値が上書きされます。

# 空の辞書を作成する
pref_code = {}

# 辞書に新規で要素を登録して
# {'Hokkaido': 1, 'Aomori': 2, 'Tokyo': 13, 'Osaka': 27} という構成にする
pref_code["Hokkaido"] = 1
pref_code["Aomori"] = 2
pref_code["Tokyo"] = 12
pref_code["Tokyo"] = 13
pref_code["Osaka"] = 27

# キーが "Tokyo" のデータを参照する
pref_code["Tokyo"]

出力結果: 13

参照する際、存在しないキーを指定すると keyError が発生します。

 pref_code["Fukuoka"]

出力結果:

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-1-a04b81bd85d7> in <module>
      1 pref_code = {}
----> 2 pref_code["Fukuoka"]

KeyError: 'Fukuoka'

12. モジュールのインポート

print()len() といった関数は、とくに何もしなくても、すぐに利用できます。しかし、関数の中には「インポートする」という手順を行わないと利用できないものがあります。ここでは、インポートについて説明します。

インポートする関数のことを「モジュール」と呼びます。

12.1 Python標準の関数をモジュールとしてインポートする

モジュールとしてインポートするには import モジュール名 という命令を記述します。Python標準の各種モジュールをインポートして使ってみます。

math

mathは数学系の命令がまとまっているモジュールです。インポートすると、学校の数学で学んだ各種計算が利用できます。

たとえば ceil() で数値の切り上げ、floor() 数値の切り捨てが可能です。(四捨五入は標準関数の round() を使います。)

import math

a = 5.4

print(math.ceil(a))
print(math.floor(a))

出力結果:

6
5

random

randomは、ランダムに数値を取得したり、シーケンスの並びをランダムに変えたりするライブラリです。

まずはランダムに数値を取得する関数について見てみましょう。

メソッド名 詳細
randrange(x) 0以上x未満の整数からランダムな数値を取得する
randrange(x, y, z) x以上y未満(増分z)の整数の並びからランダムな数値を取得する。zは省略可能
randint(x, y) x以上y 以下 の整数の並びからランダムな数値を取得する

試しに使ってみます。結果はランダムですので、実行する度に変わります。

import random

print(random.randrange(11))
print(random.randrange(1, 20, 2))
print(random.randint(1, 10))

出力結果:

7
17
2

次にシーケンスに関する random の命令です。

メソッド名 詳細
choice(seq) シーケンスseqからランダムに要素を1つ取得する
choices(seq, k = 1) シーケンスseqから 重複あり でランダムにk個の要素を取得したリストを返す
sample(seq, k) シーケンスseqから 重複なし でランダムにk個の要素を取得したリストを返す
shuffle(seq) シーケンスseqを並び替える

これらのメソッドを使ってみます。(実行結果はランダムなため、毎回変わります。)

import random

nums = list(range(5))

print(random.choice(nums))
print(random.choices(nums, k = 3))
print(random.sample(nums, 3))

print(nums)
random.shuffle(nums)
print(nums)

出力結果:

3
[4, 4, 3]
[0, 2, 4]
[0, 1, 2, 3, 4]
[3, 0, 4, 2, 1]

datetime

Pythonで日付や時刻を扱う場合は "2018-06-01", "14:39:27" のように文字列として扱うより「日付オブジェクト」「時刻オブジェクト」として扱うと便利です。「3日後」のような日時計算が非常に楽になるからです。

日時をオブジェクトとして扱うために datetime を使います。datetime 自体は複数の種類(データ型)が扱えるようになっています。

種類(データ型) 概要
date 日付情報のみを管理するデータ型
time 時刻情報のみを管理するデータ型
datetime 日時の両方の情報を管理するデータ型
timedelta 時差の情報を管理するデータ型
timezone タイムゾーンの情報を管理するデータ型

datetime をインポートして、すべての種類を使えるようにしても良いですが、不要なものをインポートすると処理のパフォーマンスが低下するかもしれないので、使うもののみをインポートするほうが望ましいと言えます。必要なもののみをインポートするには from モジュール名 import 使用するもの のような記述をします。「使用するもの」の部分は , 区切りで複数指定できます。

では、datetime を使ってみましょう。なお、cloud9上のシステムと日本国内はタイムゾーンが異なるため、datetime を扱う前にタイムゾーン設定を日本(JST:標準時から+9時間)に変更した方が扱いやすくなります。

まずは現在時刻のオブジェクトを取得してみます。

from datetime import datetime, timedelta, timezone

jst = timezone(timedelta(hours=+9), 'JST')

now_dtm = datetime.now(jst)

print(now_dtm)

出力結果:

2019-06-22 16:07:12.381901+09:00

jst = timezone(timedelta(hours=+9), 'JST') は「おまじない」という理解でも構いません。念のため補足すると、timedelta(hours=+9) は「時差が標準時から+9時間」という情報を保持するオブジェクトが返ります。それに「JST」という名前をつけたタイムゾーンの情報(オブジェクト)を取得して変数 jst に格納しています。

指定の日時から日時オブジェクトを作りたいときは、指定の日時をいったん文字列で記述したあと、以下のような処理を実行します。

jst = timezone(timedelta(hours=+9), 'JST')

dtm_str = "2019-05-25 09:41:36"
dtm = datetime.strptime(dtm_str, "%Y-%m-%d %H:%M:%S")

print(dtm)

出力結果:

2019-05-25 09:41:36

strptime() メソッドの最初の引数は文字列になっている日時データ、2つ目は日付データの書式です。1つ目に指定した文字列が、日付のデータとしてどのように指定されているかを2つ目の引数で教えてあげています。"%Y-%m-%d %H:%M:%S"%Y, %m, %d, %H, %M, %Sは、それぞれ , , , , , を表す書式指定です。詳しくは上述の公式ドキュメントを参照してください。

日時計算

さきほどのコードは、日時情報のオブジェクトをそのまま print() で表示させたため、本当に便利に使えるのかわかりづらいです。そこで、日時計算をしてみます。

print(dtm + timedelta(weeks=1))

timedelta(weeks=1)は「時間差が一週間後」という指定です。一週間(7日)後の日付が自動で計算される上、31を超えても「32日」のようにはならず正しく「6月1日」となります。

timedelta() には他にも secondsdays といったキーワード引数があり、減算もできるので、日付計算が容易になります。

(補足)別名をつける

名前の長いものをインポートする場合、別名をつけて使うようにすると便利です。インポートしたものに別名をつける場合は as というキーワードを利用します。

さきほどの randomrd という別名をつけて使ってみた例を以下に示します。

import random as rd

nums = list(range(5))

print(rd.choice(nums))

13. ファイル処理

プログラムでは、ファイルとしてデータを管理することもあります。ここでは、ファイルの読み書きについて説明します。

13.1 ファイルの読み書きの基本

Pythonでは、まず、open()でファイルを開きます。すると、ファイルオブジェクトというオブジェクトが返されます。このファイルオブジェクトに備わるメソッドを使って、データを読み書きします。ファイル操作が終わったら、close()でファイルを閉じます。

ファイルを読み書きするときの雛形

こうした一連の操作をするプログラムの基本的な雛形は、次の通りです。

下記の例では、取得したファイルオブジェクトを変数fに格納していますが、ほかの変数名でもかまいません。モードは、「読み込み」「書き込み」のほか、「テキスト」「バイナリ」など、読み書きの方法を示すオプションです。

テキストとバイナリは、データを文字列として扱うか、数値データとして扱うかの違いです。このレッスンでは、テキストの場合のみ扱います。

f = open("ファイル名", モード)
# ここでfを通じてファイルを読み書きする。たとえば、1行読み込むならf.readline()
f.close()
モードの値 意味
r 読み込み。デフォルトのため省略できる
w 書き込み
x 新規作成。ファイルが存在するときはエラー
a 追記書き込み
+ 読み書き両方
t テキストファイル。上記の「r」「w」「x」「a」「+」と組み合わせて指定する。デフォルトのため省略できる
b バイナリファイル。上記の「r」「w」「x」「a」「+」と組み合わせて指定する。

ファイルを読み書きするメソッド

open()で取得したファイルオブジェクト(上記の例では、変数f)には、ファイルを読み書きする、さまざまなメソッドがあります。主なものを、次の表に示します。

下記に示すのは、すべてのメソッドではありません。代表的なもののみを示しています。
主なメソッド 解説
read() 末端まですべて読み込む
read(長さ) 指定した長さだけ読み込む
readline() 1行分読み込む
readline(長さ) 指定した長さだけ1行読み込む
readlines() すべてを読み込み、1行ずつをリストにする
seek(先頭からの位置) 指定した場所に読み書きの対象位置を移動する
tell() 現在の読み書き対象の位置を取得する
truncate(長さ) ファイルを指定した長さに切り詰める。長さを省略したときは中身をすべて空にする
write(データ) データを書き込む
writelines(リスト) リストとして構成されたデータを書き込む
close() ファイルを閉じる

withブロックで自動的にファイルを閉じる

open()で開いたファイルは、close()で閉じるのが基本です。読み込み時にclose()し忘れると、他の人が読み書きできなくなりますし、書き込み時には、ファイルが壊れることもあります。

そこでclose()し忘れないようにするため、withブロックという構文があります。先の例は、withブロックで次のように記述できます。

with open("ファイル名", モード) as f:
        # ここでfを通じてファイルを読み書きする。たとえば、1行読み込むならf.readline()

withブロックの書き方では、close()を書く必要はなく、処理がwithブロックの外側から出たときに、暗黙的にclose()が実行されます。

withブロックを用いたほうが簡便なので、以降は、この方法を使って記述します。

13.2 テキストファイルを読み込む

では、実際にやってみましょう。テキストファイルの読み込みから始めます。

サンプルファイルの準備

ここでは、下記のjinko.txtというテキストファイルを扱います。以下のリンクから jinko_txt.zip をダウンロードしてください。解凍して表示されるフォルダの中には jinko.txtjinko_sjis.txt という2つのテキストファイルが格納されています。どちらも本チャプターで使いますので、を右クリック→「名前を付けて保存」(ファイル名はjinko.txtのままにしてください)したうえで、Cloud9へアップロードしてください。アップロードする場所は Lesson1.ipynbと同じディレクトリにしましょう。

jinko_txt.zipをコチラからダウンロードしてください

jinko.txt のテキストファイルは、市区町村別の人口が書かれたテキストファイル(UTF-8の文字コードで書かれたもの:文字コードについては後述)です。Cloud9でダブルクリックすると、開いて内容を確認できます。それぞれの項目は、カンマ(「,」)で区切られていていて、左から、「都道府県」「市区町村」「人口」を示しています。1行目は、表の見出しとなる行で、2行目からが実際のデータです。

les1_13_01

JupyterLabはデフォルトでUTF-8の文字コードに対応していますが、JupyterLab以外のPython環境をご利用の方は、念のため下記の2行を実行して utf-8 と表示されることを確認してください。

それ以外の方は「13.3 PythonでUTF-8以外の文字コードのファイルを読み込む」まで一度目を通してから、ご自身の環境の文字コードに合わせた実習を行ってください。

全データを画面に表示する

まずは、全データを表示してみましょう。read()を使うと、全データを取得できます。たとえば次のようにすれば、全データを画面に表示できます。

with open("jinko.txt", "rt") as f:
    print(f.read())

出力結果:

都道府県,市区町村,人口
北海道,札幌市,1913545
北海道,函館市,279127
北海道,小樽市,131928
北海道,旭川市,347095
…略…

上記の例では、jinko.txtファイルを読み込みたいので、open("jinko.txt", "r")のようにも読み込みモードのrtを指定しています。これはデフォルトなので、open("jinko.txt", "r")や、モード自体を省略してopen("jinko.txt")とも記述できます。以下では、省略して記述します。

1行ずつ読み込む

テキストデータを加工したいときは、全部を読むのではなく、1行ずつ読みたいこととも、よくあります。そのための方法は、2つあります。

リストとして読み込む

ひとつは、readlines()を使う方法です。この方法では、ファイル全体を読み込み、リストとして取得します。

with open("jinko.txt") as f:
    l = f.readlines()
print(l)

出力結果:

['都道府県,市区町村,人口\n', '北海道,札幌市,1913545\n', '北海道,函館市,279127\n', '北海道,小樽市,131928\n', '北海道,旭川市,347095\n', '北海道,室蘭市,94535\n', '北海道,釧路市,181169\n', '北海道,帯広市,168057\n', '北海道,北見市,125689\n', '北海道,夕張市,10922\n', '北海道,岩見沢市,90145\n', '北海道,網走市,40998\n', '北海道,留萌市,24457\n', '北海道,苫小牧市,173320\n', '北海道,稚内市,39595\n', '北海道,美唄市,26034\n',…略…]

出力結果を見るとわかるように、それぞれの行がリストとして得られているのがわかります。末尾に表示されている\nは、改行を示しています。

改行には \n のほか \r もあります。詳しくは、のちに説明します。
1行ずつループで読み込む

もうひとつの方法は、readline()を使う方法です。readline()は1行読み込み、もし、読み込んだデータがないとき(ファイルの末尾に達したとき)には、Falseを返します。

そこで次のようにwhile文を使ってループ処理すると、1行ずつ処理しながら、全部のデータを画面に表示できます。

with open("jinko.txt") as f:
    while True:
        l = f.readline()
        print(l)
        if not l:
            break

出力結果:

都道府県,市区町村,人口

北海道,札幌市,1913545

北海道,函館市,279127

北海道,小樽市,131928

北海道,旭川市,347095

…略…

上記の例では、while True:とすることで、永遠に繰り返しています。そして、l = f.readline()としてファイルから1行分読み込み、printで、それを表示しています。if 文でnot lか(末尾に達したか)を判定して、もし、末尾に達していれば、break文を実行してループを終了しています。

データを加工する

読み込んだデータを加工したいこともあります。いくつかの方法を例示します。

見出しの行を読み飛ばす

ここで扱っているサンプルのように、1行目が見出しの行で、それを読み飛ばしたいことがあります。そのようなときは、最初に1回、readline()を、先に実行して読み飛ばします。

with open("jinko.txt") as f:
    l = f.readline()
    while l:
        l = f.readline()
        print(l)

出力結果(先頭に 都道府県,市区町村,人口 が表示されなくなった):

北海道,札幌市,1913545

北海道,函館市,279127

北海道,小樽市,131928

北海道,旭川市,347095

…略…
末尾の改行を取り除く

上記の実行例を見るとわかるように、出力結果が1行ずつ空いています。これはreadline()で読み込んだデータの末尾に改行が付いているのが原因です。strip()を使うと、改行を取り除くことができます。

with open("jinko.txt") as f:
    l = f.readline()
    while l:
        l = f.readline()
        m = l.strip()
        print(m)

出力結果:

北海道,札幌市,1913545
北海道,函館市,279127
北海道,小樽市,131928
北海道,旭川市,347095
…略…
先頭の5行だけを表示する

全部を処理するのではなく、一部のデータだけを処理したいこともあります。そのようなときは、たとえば、処理した回数をカウントして、一定数処理したときにループを終了するようなプログラムを書きます。

たとえば次のようにすると、先頭から5行分だけを表示できます。

count = 0
with open("jinko.txt") as f:
    l = f.readline()
    while l:
        l = f.readline()
        m = l.strip()
        print(m)
        count = count + 1
        if count >= 5:
            break

出力結果:

北海道,札幌市,1913545
北海道,函館市,279127
北海道,小樽市,131928
北海道,旭川市,347095
北海道,室蘭市,94535

カンマで区切られたデータを処理する

テキストデータがカンマで区切られていて、それを分割して処理したいこともあります。実際、これまで見てきた人口データは 北海道,札幌市,1913545 のように、「都道府県」「市区町村」「人口」が、カンマで区切られています。こうしたカンマで区切られたデータは、「CSV(Comma Separated Value)データ」と言います。

以下、このデータのうち、「北海道」の総人口を計算してみましょう。

このレッスンでは、Pythonのファイル操作を習得するため、Pythonの基本的な方法を使って収集します。しかしPandasというライブラリを使うと、より簡単にこうしたデータ処理ができます。その方法については、レッスン4で説明します。
カンマ区切りにする

カンマ区切りにするには、主に、以下の2つの方法があります。

【① split() を使う】

ひとつ目の方法は、文字列のsplit()を使って、「,」で分割する方法です。

with open("jinko.txt") as f:
    l = f.readline()
    while l:
        l = f.readline()
        v = l.split(",")
        print(v)

出力結果:

['北海道', '札幌市', '1913545\n']
['北海道', '函館市', '279127\n']
['北海道', '小樽市', '131928\n']
['北海道', '旭川市', '347095\n']
['北海道', '室蘭市', '94535\n']
…略…

出力結果を見るとわかるように、リストの一番左から順に、「都道府県」「市区町村」「人口」に分割されます。上記では、vという変数で受け取っていますから、v[0]が都道府県、v[1]が市区町村、v[2]が人口です。最後のデータ(v[2])の末尾には、改行を示す「\n」が入っている点に注意してください。

【② csv.reader を使う】

もうひとつの方法は、csv.readerを使って、次のように記述する方法です。csv.readerは、CSVファイルを読み書きするためのクラスです。

import csv

with open("jinko.txt") as f:
    reader = csv.reader(f)
    for l in reader:
        print(l)

出力結果:

['都道府県', '市区町村', '人口']
['北海道', '札幌市', '1913545']
['北海道', '函館市', '279127']
['北海道', '小樽市', '131928']
['北海道', '旭川市', '347095']
['北海道', '室蘭市', '94535']
…略…

split()を使う方法とcsv.readerを使う方法との違いは、さまざまなCSV形式に対応できるかどうかです。CSV形式データは、前後を「”」(ダブルクォーテーション)で括って表現したり、データ中に改行が存在するなどの特殊なケースがあります。split()を使う方法では、「,」で区切るので、そうした特殊な形式に対応できません。しかしcsv.readerを使った方法なら、そうしたデータも正しく読み取れます。

また実行結果を見るとわかるように、最後のデータに改行を示す「\n」は含まれない点にも注目してください。CSVデータを扱うのであれば、csv.readerを使うのがよいでしょう。

条件に合うデータだけを処理する

ときには、条件に合致するデータだけを処理したいこともあります。そのときには、ループのなかで、条件判定して処理します。

たとえば次のようにすると、北海道の人口だけを表示できます。

with open("jinko.txt") as f:
    reader = csv.reader(f)
    for l in reader:
        if l[0] == "北海道":
            print(l)

人口を足し合わせれば、その都道府県の人口の総和を求められます。たとえば次のようにすれば、北海道の人口の総和がわかります。

s = 0
with open("jinko.txt") as f:
    reader = csv.reader(f)
    for l in reader:
        if l[0] == "北海道":
            s = s + int(l[2])

print("北海道の人口", s)

出力結果:

北海道の人口 5506419

上記のプログラムでは、sという変数に人口の総計を計算していますが、足し算するときには、int(l[2])のようにして、整数に変換している点に注目してください。

s = s + int(l[2])

読み込んだデータは文字列なので、それを足し算することはできません。文字列を整数に変換するには、int()を使います。

小数を含んだ値の場合は float() を使って変換します。

文字化けと文字コード

ところでPythonでファイルを操作するとき、そのファイル形式によっては、エラーが発生したり、正しい文字で表示されなかったりすることがあります。正しい文字で表示されないことは、「文字化け」と言います。

UTF-8以外の文字コードの場合

こんどは、下記のjinko_sjis.txtというテキストファイルを扱います。jinko_sjis.txtを右クリック→「名前を付けて保存」(ファイル名はjinko_sjis.txtのままにしてください)したうえで、Cloud9へアップロードしてください。アップロードする場所は Lesson1.ipynbと同じディレクトリにしましょう。

そしてCloud9でダブルクリックして内容を確認しましょう。下記のように、正しく表示されないはずです。

les1_13_11

文字コードとは、文字をどのような数値で表現するかという決まりのことです。Jupyter NotebookやPythonでは、「UTF-8」という形式が標準で、それ以外だと、上図のように正しく表示されません。いま試したjinko_sjis.txtは、「シフトJIS」と呼ばれる形式で保存したファイルです。WindowsのWordやExcelなどでテキスト形式として保存した場合は、多くの場合、この文字コードになります。

文字コード 概説
UTF-8 全世界の文字を共通に示せる「Unicode」と呼ばれる表現方式を、扱いやすく加工した形式
Shift JIS
(シフトJIS)
Windowsや古いmacで使われる標準的な形式。たとえば、WordやExcelなどで、
テキスト形式で保存すると、この文字コードになる。「SJIS」や「Shift-JIS」とも呼ばれる
JIS メールなどで使われる形式
EUC-JP 少し昔のLinuxなどのUnix系のOSで標準的に使われていた形式
(現在、これらのOSではUTF-8を使うのが標準なので、いまは、あまり使われていない)

13.3 PythonでUTF-8以外の文字コードのファイルを読み込む

PythonでUTF-8以外のファイルを読み込もうとすると、次のようにエラーとなります。

with open("jinko_sjis.txt") as f:
    while True:
        l = f.readline()
        print(l)
        if not l:
            break

出力結果:

les1_13_12

こうした問題を避けるには、open()encoding オプションを指定して、次のようにします。

with open("jinko_sjis.txt", encoding='shift-jis') as f:
    while True:
        l = f.readline()
        print(l)
        if not l:
            break

encodingには、「shift_jis(シフトJIS)」「euc_jp(EUC-JP)」「iso-2022jp」などを指定できます。これらの表記には別名があり、別名を使っても同じです。

encodingに指定する値 主な別名 意味
shift_jis shiftjis、sjis シフトJIS
cp932 932、ms932、mskanji、ms-kanji Windows用のシフトJIS。丸数字などの表現方法が異なる。Windowsで作られたファイルで、丸数字や記号などが正しく扱えないときは、shift_jisの代わりにcp932を指定するとよい
euc_jp eucjp、ujis、u-jis EUC-JP
iso2022_jp iso2022jp、iso-2022-jp JIS
改行コードの扱い

文字コードと並んで、改行コードの問題があります。改行コードとは、改行を、どのような数値で示すかの規定のことです。次の3種類があります。

名称 文字コードでの表現 採用している主なOS
CR \r macOS
LF \n Linuxなど
CRLF \r\n Windows

Pythonでファイルを扱うときは、open()newlineオプションに、改行コードを指定できます。もし、ファイルを読み込んだときに、「まったく改行されずに全部、ひとまとまりとして読み込まれてしまう」「余計な改行が入る」という場合は、newllineオプションを変更するとよいでしょう。

たとえばWindowsの標準的なファイル(文字コードがシフトJIS、改行コードがCRLF)を読み込む場合は、次のようにします。

with open("jinko_sjis.txt", encoding='shift-jis', newline='\r\n') as f:
    while True:
        l = f.readline()
        print(l)
        if not l:
            break

13.4 テキストファイルに書き込む

ファイルの読み込み方法を説明したところで、今度は、ファイルへの書き込み方法を説明します。

テキストファイルへの書き込みの基本

ファイルへの書き込みは、ファイルの読み込みと同じく、open()を使います。ただしこのとき、モードとして、書き込みを示す w を指定します。テキストファイルのときは wt が正式ですが、t はデフォルトのため省略できるため、w のみでかまいません。そして write()writelines() を使ってデータを書き込みます。

モードに w を指定したときは、ファイルを削除してから上書きしますが、a を指定すると、ファイルを消さず、末尾に追記していくこともできます。
f = open("ファイル名", "w")
# ここでfを通じてファイルを書き込む。たとえば、writeなど
f.close()

これは次のように、withブロックで表現することもできます。

with open("ファイル名", モード) as f:
        # ここでfを通じてファイルを書き込む。たとえば、writeなど

実際にやってみましょう。次のプログラムを実行すると、writeexample.txtというファイルができます。

with open("writeexample.txt", "w") as f:
    f.write("abc\r\n")
    f.write("defgh")

Cloud9上で確認すると、write()で記述した文字が記述されていることがわかります。

なおwriteは、末尾に改行を付けません。付けたいのなら、上記例にもあるように、\nまたは\rもしくは\r\nを、文字列の末尾に明示的に含めるようにします。

このレッスンでは使いませんが、書き込みのときも読み込みのときと同様、open() に、encodingnewline を指定することもできます。

les1_13_14

13.5 複数行の書き込み

複数行データを、まとめて書き込むこともできます。複数行データを書き込むには、writelines()を使って、たとえば、次のようにします。

data = ["abc", "defgh"]
with open("writeexample2.txt", "w") as f:
    f.writelines(data)

writelines()は、指定したデータを1行ずつ書き込みます。ただし、writelines() を使った場合、改行はされませんので注意してください。

13.6 CSVデータの書き込み

リストをCSVデータとしてファイルに書き込むこともできます。方法は2つあります。

join を使う

1つ目の方法は、joinを使って、文字列を結合してから書き込む方法です。まずは、簡単な結合例を見てみましょう。次のようにすると、データをカンマで接続した文字列を作れます。

data = ["abc", "def", "ghi"]
csvdata = ",".join(data)
print(csvdata)

出力結果:

abc,def,ghi

しかしこの方法は、数値の場合は、次のようにエラーが発生します。

data = [1, 2, 3]
csvdata = ",".join(data)
print(csvdata)

出力結果:

les1_13_16

エラーを回避するには、数値を文字列を変換します。いくつかの方法がありますが、たとえば、次のようにstr()を使って、文字列に変換します。

data = [1, 2, 3]

for i in range(len(data)):
    data[i] = str(data[i])

csvdata = ",".join(data)
print(csvdata)

こうして変換したデータをwrite() で書き込めば、CSVデータを作れます。以下にCSVデータを書き出す例を示します。

alldata = [
   [1, 2, 3],
   [2, 3, 5]
]
with open("writeexample3.txt", "w") as f:
        for data in alldata:
            csvdata = ",".join([str(x) for x in data])
            f.write(csvdata + "\r\n")

writeexample3.txtの出力結果:

1,2,3
2,3,5

csv.writer を使う

①の方法は、カンマでつなげるだけなので、データに改行やカンマが含まれていると、意図しない結果になる恐れがあります。

どんなデータでも表現できるようにするには、csv.writerを使うのが確実です。csv.writer()を使って、まず、writerオブジェクトを取得します。そしてそのwriterows()writerrow()を使って、データを1行ずつ書き込みます。

writerows()は、全データをまとめて書き込みます。

import csv

alldata = [
   [1, 2, 3],
   [2, 3, 5]
]

with open("writeexample4.txt", "w") as f:
    writer = csv.writer(f)
    writer.writerows(alldata)

writerow()は、1行ずつ書き込みます。たとえば、次のようにすると、writeexample5.txt は、 writeexample3.txtwriteexample4.txt と同じ出力結果となります。

import csv

alldata = [
   [1, 2, 3],
   [2, 3, 5]
]

with open("writeexample5.txt", "w") as f:
    writer = csv.writer(f)
    for data in alldata:
            writer.writerow(data)

14. まとめ

Pythonの基本的な文法を紹介しました。次のレッスンからは、機械学習プログラムを作るために利用するパッケージライブラリの使い方を学びます。Pythonの基本文法は理解している前提で進みますので、よく復習しましょう。

Lesson2 NumPyの使い方(数値計算)へ進む
Copyright 2020 SAKURA Internet Inc. All rights reserved