Pythonの基礎

プログラミング言語は、Python、R、Java、Cなどたくさんあります。各言語はそれぞれの言語の仕様により、様々な特徴を持ちます。各言語がよく使われている分野というものがあり、特定分野においてその言語のユーザーが多いほど、多くのライブラリが作成されています。利用するプログラミング言語を選定する際、その分野で多くの人が使っているということは重要な選定指標になります。

リモートセンシング・GISの分野でよく使われている言語は、Python、R、Bash、C#といったところです。特にPythonは、ArgGISのほか、QGIS、GRASS GIS、Orfeo ToolboxなどのメジャーなGISはPythonをサポートしており、それらのソフトウェアが提供する機能をつなぎ合わせて実行することが容易になっています。

Rはグラフ作成や統計解析ライブラリが非常に充実しています。Pythonで大規模なデータを処理した後、Rで統計解析を行うこともよくあります。

BashはほとんどのUnix系のOSがサポートしている言語で、他と比べて基本的な機能しかありません。古くから使われている言語で互換性が高いため、例えば、pythonで書かれたプログラムをAWS上で自動的に実行させるといった、異なるプログラムやディバイス上での処理をつなげる処理によく使われます。

C#は他の4つと比べると、非常に早く実行することができる言語です。多くのPythonやRのライブラリがバックエンドでC#で書かれたプログラムを利用しています。

ここではJupyter Notebook上でPythonの基本的な使い方を学びます。

Jupyter Notebook上での出力について

Pythonで実行結果を表示させるには、通常print()関数を使います。
例えば、5+3の実行結果を表示させるには、Jupyter Notebook上で次のように書きます。
[1]:
print(30 + 12)
42

30 + 12の実行結果が42と表示されました。PythonをJupyter Notebook上で実行する場合に限り、print()が省略されても一番最後の実行結果は表示されます。

30 + 12

ただし、省略できるのは同じセルの最後の行のみです。複数行表示したい場合は、適宜print()関数を利用する必要があります。

[2]:
30 + 12
20 - 10
[2]:
10

すべての値を出力させたい場合、print()関数を複数入れて、次のように書きます。

[3]:
print(30 + 12)
print(20 - 10)
42
10

以降においてはprint()関数が不要な場合、省略されている場合があります。

エラーについて

セルに書いたコードが間違っていて実行できないとき、エラーメッセージが出ます。例えば、以下のセルは、コードが完成していません。実行すると

  File "<ipython-input-2-0f719367f49f>", line 1
    100 + 20 +
              ^
SyntaxError: invalid syntax

と表示されます。

[4]:
100 + 20 +
  File "<ipython-input-4-0f719367f49f>", line 1
    100 + 20 +
              ^
SyntaxError: invalid syntax

メッセージの一番下に、エラーの種類がなんであるかが表示されるので、まずこれを読みます。SyntaxError: invalid syntaxと書かれていますが、これは文法間違いであることを意味しています。今回は+記号の後に何も書かれていないので、SyntaxErrorとなりました。かっこの閉じ忘れや、,コンマやピリオドなどの記号忘れなどでよく出るエラーです。

エラーメッセージの下から2行目と3行目には、エラーの発生箇所が表示されます。今回は^記号でエラーの位置を示しています。修正して実行しなおしてください。

長いプログラムを書くようになるとエラーメッセージもどんどん長くなりますが、一番下から読んでいくことをお勧めします。

ほかのエラーも見ていきましょう。

[5]:
# 定義していない値を利用している
xxxx + 30
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-5-7d4d1f9949b3> in <module>
      1 # 定義していない値を利用している
----> 2 xxxx + 30

NameError: name 'xxxx' is not defined
[6]:
# 関数の名前が間違っている
printer(10 + 60)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-6-cd4d5ba46095> in <module>
      1 # 関数の名前が間違っている
----> 2 printer(10 + 60)

NameError: name 'printer' is not defined
[7]:
# 数値には使えない関数を利用している
len(100)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-2781cb6eafbb> in <module>
      1 # 数値には使えない関数を利用している
----> 2 len(100)

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

エラーが出てもあわてずメッセージを読んで、対応してください。意味の分からないエラーがあったら、検索して調べてみましょう。

代入

以下のコードを見てください。

[8]:
# from math import
x = 3
y = 4
z = pow(x , y)
print(z)
81

上記のコードは下記のことを行っています。

x = 3          # 変数`x`に 3 の値を入れる
y = 4          # 変数`y`に 4 の値を入れる
z = pow(x, y)  # `x の y 乗`を計算して結果を変数`z`に入力
print(z)       # 関数`print()`に`z`を入力

Pythonや多くのプログラミング言語では、=は「右側の値を左の変数に代入する」という意味を持ちます。エクセルの数式も、=記号で始まりますが、これも数式の結果をセルに代入しているという点で、同じです。エクセルと異なるのは、エクセルでは代入先が数式を入力するセルであるのに対し、Pythonでは変数に代入します。

値や値が代入された変数(上の例ではxy)は、関数pow()の入力として渡され、pow(x, y)の結果がzに代入されています。関数に渡される値を引数とよび、上の例の場合、「xとyを関数pow()の引数として渡す」などといいます。

また、pow()などの関数による計算結果を戻り値(返り値)といい、上の例は「pow()の戻り値(返り値)を変数zに代入」しています。Pythonでは、関数の戻り地をまた別の関数の引数として渡すことで、より複雑な処理をさせることができます。

値を代入することによって作成された変数や、後述のclassによって作成された変数などを、まとめてオブジェクトと呼びます。

基本型

型とは、データがどのような形式かを表すもので、Pythonにあらかじめ定義されている基本型は以下のものがあります。

  • 数値型

    • 整数型(int) : -1, 0, 1, 2, ….

    • 小数型(float) : -120.0, 3.1415, 1.5e12, 3.00e8

    • 複素数型(complex): 1.0j, 1+1j, 3e8+0j

  • バイナリ型

    • ブール型(bool) : True, False

  • 文字列

    • 文字列型(str) : ‘aaa’, ‘あいうえお’, “0123”

  • None

    • x = None

また、必要に応じてユーザーによってを新しく作ることもできます。 プログラムが複雑になってくると、新しいを導入することはとても便利です。

自分の扱っているデータの方を確認するには、type()関数を利用します。

[9]:
type(30)  # 整数型の例
[9]:
int
[10]:
type(2.0)
[10]:
float
[11]:
type(2+10j)
[11]:
complex

バイナリ型は、条件式の結果の値として利用されます。

[12]:
x = 4
y = 3

print(x == y)  # xとyが等しい場合、True、等しくない場合Falseを返す
False
[13]:
type(False)
[13]:
bool
[14]:
type('abcd')  # 文字列型の例
[14]:
str

文字列型は'(クォート)もしくは"(ダブルクォート)で挟んで定義します。Pythonでは'"は全く区別されないので、どちらでも好きなほうを利用してください。文字列の中に'"を入れたい場合は、出てこないほうの記号を利用するか、\記号を直前に挿入してエスケープすることができます。

[15]:
print("string with ' a quotation mark.")
string with ' a quotation mark.
[16]:
print('string with " a double quotation mark.')
string with " a double quotation mark.
[17]:
print('string with \' a quotation and  \" a double quotation mark.')
string with ' a quotation and  " a double quotation mark.
[18]:
type(None)  # Noneタイプの例
[18]:
NoneType

Noneタイプは、関数を呼び出す際に引数が与えられなかった場合のデフォルト値や、リストやディクショナリの要素がない場合の値などとして用いられます。

[19]:
# 複数の名前付き引数を持つ関数
def my_func(a, b, c=None):
    print(f'a is {a}.')
    print(f'b is {b}.')
    if c is not None:
        print(f'c is {c}.')

my_func(a='aaa', b='bbb')  # -> a is aaa. b is bbb.
a is aaa.
b is bbb.
[20]:
my_func(a='aaa', b='bbb', c='ccc')
a is aaa.
b is bbb.
c is ccc.
[21]:
# 要素にNoneを持つリストの例
['aaa', 'bbb', None, 'ddd']
[21]:
['aaa', 'bbb', None, 'ddd']

コレクション型

コレクション型は、複数の要素(item)をひとまとめにした構造をしています。異なる型の値であったり、別のコレクション型を要素として持つ入れ子構造を取ることもできます。

コレクション型は以下のものがあります。

  • リスト型(list)

  • タプル型(tuple)

  • セット型(set)

  • 辞書型(dict)

またそれぞれの特徴をまとめると以下のとおりです。

要素の並び

要素の変更

要素の重複

list

一定

可(ミュータブル)

['a', 'b', 'b', None, 100]

tuple

一定

不可(イミュータブル)

('a', 'b', 'b', None, 100)

set

不定

可(ミュータブル)

不可

set(['a', 'b', None, 100])

dict

不定

可(ミュータブル)

キーの重複は不可

{'a':'aaa', 'b': 'bbb', 'c': 100, 'd':None}

以下、それぞれ確認していきます。

各コレクションの作成

各コレクションは以下のように作成します。

listは[]内に,区切りで要素を列挙して作成します。

[22]:
my_list  = ['a', 'b', 'b', None, 100]
print(my_list)
['a', 'b', 'b', None, 100]

tupleは()内に,区切りで要素を列挙して作成します。listとの違いは、要素をあとから変更可能かどうかという点です。tupleは変更できません(イミュータブル)。ミュータブル/イミュータブルについては、参照の項で説明します。

[23]:
my_tuple = ('a', 'b', 'b', None, 100)
print(my_tuple)
('a', 'b', 'b', None, 100)

setはset()にリストを与えて作成ます。set型は要素の重複が不可のため、重複した要素は1つを除いて削除されます。順序は保存されないため、print()で表示される順序は一定ではありません。

[24]:
my_set   = set(['a', 'b', 'b', None, 100])
print(my_set)
{'a', 100, 'b', None}

dictは{}内に,区切りで要素を列挙して作成します。各要素は、keyvalueの組からなります。上記の例では、keyは'a', 'b', 'c', 'd'で、valueは'aaa', 'bbb', 100, Noneです。重複するkeyは利用できません。keyには文字列は整数値などhashableな値が利用できます。

[25]:
my_dict  = {'a':'aaa', 'b': 'bbb', 'c': 100, 1:None}
print(my_dict)
{'a': 'aaa', 'b': 'bbb', 'c': 100, 1: None}

入れ子構造のコレクションは次のように作成します。

[26]:
my_nested_list = ['a', 'b',  [100, 200, 300], 'd']
my_nested_list
[26]:
['a', 'b', [100, 200, 300], 'd']

コレクションへのアクセス

コレクション型の中に保存された各要素は以下のように取り出すことができます。順序が決まっているlistとtupleはシーケンスと呼ばれ、[]の中に取り出したい要素の位置を示すインデックス番号を入れることで要素を取り出すことができます。dictの場合は[]の中にkeyを入れることで取り出すことができます。Pythonではインデックス番号は0から始まり、先頭から0、1、2…となります。

[27]:
print(my_list)
['a', 'b', 'b', None, 100]
[28]:
print(my_list[0])    # 先頭の要素を取得
a
[29]:
print(my_tuple[1])   # 2番目の要素を取得
b
[30]:
print(my_tuple[1:4]) # 2番目から4番目までの要素を取得
('b', 'b', None)
[31]:
print(my_tuple[-1])  # 後ろから1番目の要素を取得
100
[32]:
print(my_dict['c'])  # keyの値が'c'である要素を取得
100

インデックス番号は、[開始:終了:ステップ]の順で指定します。ステップの個所は省略可能ですが、正の値を指定するとその数を飛ばして取り出すことができ、負の値を指定すると後ろから取り出すことができます。

[33]:
print(my_list)
['a', 'b', 'b', None, 100]
[34]:
print(my_list[::2])
['a', 'b', 100]
[35]:
print(my_list[::-1])
[100, None, 'b', 'b', 'a']

入れ子の場合、コレクションの外側のインデックスから指定して要素を取得します。

[36]:
print(my_nested_list)
['a', 'b', [100, 200, 300], 'd']
[37]:
print(my_nested_list[2])
[100, 200, 300]
[38]:
print(my_nested_list[2][1])
200

setは順序も不定でkeyを持たないため、ある特定の要素を取り出すことはできませんが、list()関数でlistに変換して取り出すことはできます。その際、順不同になることに気を付けてください。

[39]:
print(my_set)
print(list(my_set))
{'a', 100, 'b', None}
['a', 100, 'b', None]

以下の様にforループで一つずつ取り出すことも可能です。

[40]:
for i in my_set:  # 変数 i に my_set の要素をひとつずつ代入する
    print(i)
a
100
b
None

listやtupleのように番号を用いて要素を取り出すことができるオブジェクトを、シーケンシャル(Sequencial)なオブジェクトといいます。

値の変更

ミュータブルなコレクションは、以下のように要素の一部を変更することができます。

[41]:
my_list = ['a', 'b', 'c']
my_list[1] = 'bbb'
print(my_list)
['a', 'bbb', 'c']
[42]:
my_dict = {'a':'aaa', 'b':'bbb', 'c':'ccc'}
my_dict['c'] = 1000
print(my_dict)
{'a': 'aaa', 'b': 'bbb', 'c': 1000}

参照(Reference)

listやdictのようなミュータブルなオブジェクトの要素を変更する際に気を付けることの一つに、参照という概念があります。

Pythonでは=で変数から新しい変数に代入する場合、参照渡しという方法でデータがコピーされます。 参考:Python♪「参照渡し」「浅いコピー」「深いコピー」まずは理屈抜きで覚えよう。

参照渡しは、「浅いコピー」とも呼ばれます。浅いコピーの反対に位置するものは深いコピーと呼ばれます。Pythonの変数に何かデータを代入する際、変数にはデータが格納されているメモリアドレスが紐づけされます。Pythonのオブジェクトのコピーをファイルのコピーに例えると、深いコピーは通常のファイルのコピー、浅いコピーはファイルのショートカットを作成することに似ています。

この参照渡しによる浅いコピーでどのようなことが起こるのか確認しましょう。

まずは深いコピーの例です。

[43]:
my_tuple_abc = ('a', 'b', 'c')
my_tuple_xyz = my_tuple_abc

my_tuple_xyz = ('x', 'y', 'z')
print(my_tuple_xyz)
('x', 'y', 'z')
[44]:
print(my_tuple_abc)
('a', 'b', 'c')

my_tuple_abcmy_tuple_xyzにコピーした後、my_tuple_xyzの値を(x, y, z)に変更しました。変更していないmy_tuple_abcの値は(a, b, c)のままです。

浅いコピーでは以下のような挙動になります。リストはミュータブルなオブジェクトです。

[45]:
my_list_abc = ['a', 'b', 'c']
my_list_xyz = my_list_abc

my_list_xyz[0:3] = ['x','y','z']  # Listの要素を変更する
print(my_list_xyz)
['x', 'y', 'z']
[46]:
print(my_list_abc)
['x', 'y', 'z']

=でオブジェクトがコピーされると、イミュータブル、ミュータブルどちらも場合も(ファイルのショートカットを作成するように)データのメモリアドレスをコピーします。

コピーされたイミュータブルなmy_tuple_xyzは、メモリ上のデータを書き換えることはできないので、my_tuple_xyz = ('x', 'y', 'z')の個所では新たメモリ上の別の場所にデータが格納され、そのアドレスがmy_tuple_xyzに格納されます。

一方、ミュータブルなmy_list_xyzはメモリ上のデータを書き換えることができるので、my_list_xyz[0:3] = ['x','y','z']の個所では、my_list_abcと共有しているデータを更新します。

このように、list、dict等、ミュータブルな型を持つオブジェクトをコピーし、その後に値を変更する際は元の変数が参照しているデータも変更してしまうことに注意が必要です。

参照渡しによる変数のコピーは一見複雑に見えますが、実際にメモリ上のデータを変更しないので、メモリの利用効率や処理速度の向上のメリットがあります。

.copy()メソッドやcopy.deepcopy()関数を利用すると、参照渡しではなくデータの実態をコピーすることでができます。

[47]:
my_list_abc = ['a', 'b', 'c']
my_list_xyz = my_list_abc.copy()

my_list_xyz[0:3] = ['x','y','z']  # Listの要素を変更する
print(my_list_xyz)
['x', 'y', 'z']
[48]:
print(my_list_abc)                # -> ['a', 'b', 'c']  コピー元のリストの要素は変更されない
['a', 'b', 'c']

慣れないうちは、多少効率が落ちますが、ミュータブルなオブジェクトをコピーする際は常に明示的に深いコピーを行うといいと思います。

演算子(Operator)

演算子は、+-*/%などがあり、二つの引数の間に入れることで作用します。左右に数値を指定した場合、各オペレータは次の値を返します。

  • + 足し算

  • - 引き算

  • * 掛け算

  • / 割り算 (返り値は常にfloat)

  • // 割り算の商

  • % 割り算の余り

左右2つの値のどちらかがfloatであれば、返り値もfloatです。各オペレータの左右にintやfloatの数値を入れて、挙動を確認してください。

[49]:
print(3 * 5)
print(3.0 * 5)
15
15.0
[50]:
print(10 / 2)
print(10.0 / 2)
5.0
5.0
[51]:
print(10 // 2)
print(10 // 2.0)
5
5.0
[52]:
print(10 % 7)
print(10.0 % 7)
3
3.0

上記は数値に対しての演算子の説明をしましたが、演算子の機能は適用する型によって変化します。 例えば、文字列型(str)は2つの文字列を結合する様に働きます。

[53]:
'this is ' + 'a text'
[53]:
'this is a text'

リストを与えるとリストを結合します。

[54]:
list_abc = ['a', 'b', 'c']
list_012 = [0, 1, 2]
list_abc + list_012
[54]:
['a', 'b', 'c', 0, 1, 2]

行列計算によく用いられるnumpyライブラリの提供するndarrayという型のオブジェクトでは、各要素が足し合わされます。

[55]:
import numpy as np  # numpyライブラリをnpという名前でインポートする
my_array1 = np.array([0, 1, 2])      # [0, 1, 2]の要素を持つndarrayを作成
my_array2 = np.array([0, 100, 200])
my_array1 + my_array2                # 2つのndarrayをオペレータ + を足し合わせる
[55]:
array([  0, 101, 202])

その他に、よく利用される演算子は以下のものがあります。 - not - is - and - ** - > - >= - == - <= - !=

演算子がどの様に働くかは適用される型によって異なります。詳しくは公式ドキュメントを参照してください。

文字列整形

文字列は++=で結合することができます。+は前後の2つの文字列を結合した文字列を返します。+=は、左側の文字列に右側の文字列を追加します。

[56]:
my_string = 'Happy ' + 'birthday'
my_string
[56]:
'Happy birthday'
[57]:
my_string += ' to you'
my_string
[57]:
'Happy birthday to you'

\nで文字列の中で改行することができます。(Jupyter Notebook上で改行表示するには常にprint()関数に入れる必要があります。)

[58]:
print(my_string +  '\n' + my_string)
Happy birthday to you
Happy birthday to you

文字列型もシーケンスに分類される型で、各文字はlistのように番号によって取り出すことができます。

[59]:
print(my_string[10])
print(my_string[6:15])
print(my_string[::-1])
h
birthday
uoy ot yadhtrib yppaH

文字列を結合させるには、先に紹介した+を使う方法、f'string and {parameter}'の書き方、'string and {}'.format(parameter)などがあります。

[60]:
my_name = 'Takahisa'
my_country = 'Japan'

print("I'm " + my_name + ' from ' + my_country)
print(f"I'm {my_name} from {my_country}")
print("I'm {} from {}".format(my_name, my_country))
I'm Takahisa from Japan
I'm Takahisa from Japan
I'm Takahisa from Japan

練習

以下に定義されるmy_numbersの最小値、最大値、合計値を計算し、結果を指定する文字列に当てはめて表示する以下のセルを完成させてください。

最小値、最大値、合計値の計算は、それぞれmax()min()sum()関数にlistを与えることで計算することができます。

入力

my_numbers = [22, 40, 50, 45, 47, 76, 98, 1, 98, 42,
              21, 70, 95, 67, 80, 35, 36, 52, 93, 13,
              46, 35, 54, 86, 48, 19, 57, 74, 29, 83,
              59, 40, 64, 58, 70, 77, 49, 82, 14, 36,
              7, 55, 4, 90, 67, 92, 87, 90, 31, 62]

出力

"Max is 98, min is 1, sum is 2746."
"Average is 54.92."
[61]:
my_numbers = [22, 40, 50, 45, 47, 76, 98, 1, 98, 42,
              21, 70, 95, 67, 80, 35, 36, 52, 93, 13,
              46, 35, 54, 86, 48, 19, 57, 74, 29, 83,
              59, 40, 64, 58, 70, 77, 49, 82, 14, 36,
              7, 55, 4, 90, 67, 92, 87, 90, 31, 62]

# max_number = max(my_numbers)
# min_number =
# ...

# print(f'')

条件式(if, elif, else)

条件によってコードの実行を変化させるために、ifelifelseから始まる条件式を利用します。次の例では、a999の時、1を加えて、それ以外の時は何もしないというコードです。

条件式は、

if 条件:
    処理1
    処理2
    ...

そのほかの処理

の形になっています。

[62]:
a = 999

if a == 999:
    print('increasing a by 1.')
    a += 1

print(a)
increasing a by 1.
1000

処理1処理2の行は、左側に半角スペース4つ入れることで、同じ深さまでインデントされています。Pythonではif 条件:の行から、インデントされている処理2までを一つのまとまりとして認識します。このまとまりをブロックと呼びます。

そのほかの処理はインデントがifの行と同じ位置に戻されているので、この行はifのブロックの外側にあるといえます。ブロックの外側にあるそのほかの処理は、ifの条件式に関係なく実行されます。

条件によって異なる処理を行う場合、ifの後に、elifelseが利用できます。

[63]:
a = 999

if a < 0:
    a = 255
elif a > 254:
    a = 254
else:
    pass

print(a)
254

この例では、「aの値が0から254の間になく、小さい値の場合aに255を、大きい値の場合254を与える」をしています。

elseのブロックは、すべての条件に当てはまらない場合に実行されるコードを書きます。今回は「なにもしない」を意味するpassを書きました。このpassの部分は、

else:
    # TODO: implement anothre process
    pass

などとして、条件分岐の処理を後から追加するために一時的においておく際によく利用されます。

「条件」の箇所は、TrueまたはFalseを返す式やその値を入力します。その他の値(数値など)が与えられた場合、bool()関数にその値が入力された際の返り値に準じます。

以下のセルでaの値を変化させて確認してください。

[64]:
a = 100
# a = 0
# a = -2
# a = 'aaa'
# a = ''
# a = ['a', 'b', 'c']
# a = True
# a = None

if a:
    print(f'{type(a)}: {a} is True')
else:
    print(f'{type(a)}: {a} is False')
<class 'int'>: 100 is True

その他、条件式の例を下に示します。

[65]:
my_integer = 21

if my_integer % 2 == 0:
    # when 0, 2, 4, 6, 8, 10, ...
    print(f'{my_integer} is even')
elif my_integer % 3 == 0:
    # when 3, 9, 15, 21, ...
    print(f'{my_integer} is odd and a multiple of 3')
else:
    print(f'{my_integer} can\'t be divided into 2 or 3.')
21 is odd and a multiple of 3
[66]:
my_dict = {'a':'000', 'b':100, 'c':200}

if type(my_dict['a']) != int:
    # int() 関数で整数値に変換する
    my_dict['a'] = int(my_dict['a'])

my_dict
[66]:
{'a': 0, 'b': 100, 'c': 200}
[67]:
10 % 3
[67]:
1

ループ(for, while)

ループは、同じ処理を繰り返し行うための仕組みで、for文で作成する方法とwhile文で作成する方法があります。

for文は以下の様に作成します。

[68]:
items = ['a', 'b', 'c']

for item in items:
    print(item)
a
b
c

forループは、次のような構造になっています。

for 要素を代入する変数 in シーケンスオブジェクト:
    要素が代入された変数を用いて行う処理

if文と同様に、forから始まり:で終わる行からコードブロックが始まります。 シーケンスには、コレクションのように複数の要素からなるリストやディクショナリのほか、range()関数を利用することもよくあります。文字列を与えると、一文字ずつ要素を代入する変数に代休されます。

要素を代入する変数には、ijkなどがよく用いられますが、「シーケンスオブジェクト」の単数形の単語を用いると、ループがネストされた際にもコードが読みやすくなります。

forループは以下の順序で処理が行われます。

  1. シーケンスからまず一つ値を取り出し、「要素を代入する変数」に代入する

  2. 値が入力された変数を用いて、インデントされているコードブロックが実行される

  3. コードブロックの実行が最後まで行ったら、シーケンスから次の値が変数に代入される

  4. 再度コードブロックが実行される

  5. シーケンスの最後の値でコードブロックが実行されたら、forループのブロックから抜ける

いくつか例を示します。

[69]:
# range()関数を用いたforループの例
for i in range(3,8):
    print(i)
3
4
5
6
7
[70]:
# strを用いた例
for i in 'Hello':
    print(i)
H
e
l
l
o
[71]:
# ネストされたループの例
years = [2018, 2019, 2020]
seasons = ['Dry season', 'Rainy season']
for year in years:
    for season in seasons:
        print(f'{season} in {year}')
Dry season in 2018
Rainy season in 2018
Dry season in 2019
Rainy season in 2019
Dry season in 2020
Rainy season in 2020
[72]:
# forループで要素に処理を行い、別のリストに格納して取り出す
number_list = [0, 1, 2, 3, 4, 5, 6]
out_list = []

for number in number_list:
    if number % 2 == 0:
        out_list.append(number * 2)

print(out_list)
[0, 4, 8, 12]

また、Python独自のforループの書き方として、上の例は次のように書くことができます。

[73]:
number_list = [0, 1, 2, 3, 4, 5, 6]

out_list = [number * 2 for number in number_list if number % 2 == 0]

print(out_list)
[0, 4, 8, 12]

一行でfor文が書けてしまうので、なれると非常に便利な書き方です。

whileを用いたループは以下のように作成します。

[74]:
items = ['a', 'b', 'c']
i = 0
while i < len(items):  # len(items)はリストの要素の数を返す
    print(items[i])
    i += 1
a
b
c

whileループは、次のような構造になっています。

while 条件式:
    処理

whileから始まり:で終わる行からコードブロックが始まります。 条件式がTrueであるかぎり、処理が繰り返し実行されます。

  1. whileの右の「条件式」が評価される

  2. 条件式の結果がTrueであれば、処理が実行される。Falseであればwhileのコードブロックから抜ける。

  3. 処理の実行が終われば、再度1に戻る

whileループでは「処理」の中で「条件式」が変化するようにしておかないと、無限にループが回ってしまいます。 上の例では、処理が一回行われるたびにiの値を1増加させることで、3回処理が行われた時点でi3となります。4回目のループの前に条件式が再度評価されFalseとなるので、whileのコードブロックが終了します。

whileループは処理の中で条件式が変化するように書く必要があり、間違えて無限ループを発生させてしまうことがよくあります。for文と比べて使い勝手が悪いかもしれませんが、「長さのわからない外部ファイルを一行ずつ読み込む」「一定時間できるだけ処理を繰り替えす」「外部からの入力をまつ」「処理が成功するまで試行を繰り返す」など、ループの終了回数が設計段階で不明な時によく利用されます。

練習

ifのセクションで確認した出力をループを使って書いてみましょう。

入力

items = [100, 0, -2, 'aaa', '', ['a', 'b', 'c'], True, None]

出力

<class 'bool'>: True is True
<class 'int'>: 100 is True
<class 'int'>: 0 is False
<class 'int'>: -2 is True
<class 'str'>: aaa is True
<class 'list'>: ['a', 'b', 'c'] is True
<class 'NoneType'>: None is False
[75]:
items = [True, 100, 0, -2, 'aaa', ['a', 'b', 'c'], None]

# for item in items:
# while ...

関数

関数は、コードをひとまとめにして、あとから何度も同じ処理を行うための機能です。これまで使ってきた値の合計を計算するsum()や、オブジェクトの型を調べるtype()、値を表示するprint()も関数です。関数は関数名()の形で利用し、カッコ内に必要に応じて引数を与えます。

関数の定義は、def 関数名(引数名, 引数名, ...):から始まるコードブロックで次のように行います。

[76]:
# 2つの値の差の絶対値を求める関数の定義
def absolute_diff(x, y):
    diff = x - y
    absolute_diff = abs(diff)
    return absolute_diff
[77]:
# 定義した関数を利用する
absolute_diff(x=-10, y=-4)
[77]:
6

この例では、absolute_diffという名前の関数を次のように定義しました。

  • この関数は引数としてxyをとる。

  • 引数の差の絶対値を返す

コードブロック内で計算された結果を関数の返り値として返すには、returnから始まる行に返したいオブジェクトを書きます。この例では、計算結果は変数absolute_diffに入っているため、

return absolute_diff

と書くことで、関数の返り値としてabsolute_diffが返されます。

関数定義のコードブロックの範囲は、if文やforループと同様にインデントによって規定されます。

引数が規定値を持つ場合、関数を利用する際にxyの値を与えなかった場合、既定値が利用されます。次の例では、規定値としてn=3を指定しています。

[78]:
def print_n_times(string, n=3):
    for i in range(n):
        print(string)

print_n_times(string='pizza')
pizza
pizza
pizza

引数を指定すると上書きされて実行されます。

[79]:
print_n_times(string='pizza', n=10)
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza

関数を利用する際に引数名を省略することができます。ただし、省略していない引数のあとでは省略できません。

[80]:
def my_func(a, b, c='ccc'):
    print(f'a is {a}, b is {b}, c is {c}')

my_func(a='aaa', b='bbb', c='ccc')
a is aaa, b is bbb, c is ccc
[81]:
# 引数名を省略して関数を利用する
my_func('aaa', 'bbb', 'ccc')
a is aaa, b is bbb, c is ccc
[82]:
# 一部の引数名を省略して関数を利用する
my_func('aaa', b='bbb', c='ccc')

# 引数名は途中からは省略できない。以下はエラーになる。
# my_func(a='aaa', 'bbb', c='ccc')
a is aaa, b is bbb, c is ccc

モジュールとパッケージ

ここまでは、PythonのプログラムをJupyter Notebookのコードセルに書いてきました。.ipynbのファイル内に保存されたこれらのコードは、ほかのJupyter Notebookから呼び出したり再利用することができません。Pythonのコードをテキストファイルに書き、.pyの拡張子で保存すると、そのコードを他のから呼び出すことができるようになります。この.pyファイルのことをモジュールと呼びます。ここでは簡単なモジュールを作成し、読み込みをしてみます。

テキストファイルの作成は、Windows標準のnotepadを使ってもいいですし、Jupyter NotebookのLauncherタブのOther->Text Fileから開くエディタを利用することもできます。 後者の場合、シンタックスハイライト機能で色付けされたコードを編集することができます。

以下の一行をhello.pyと言う名前のテキストファイルに保存してください。保存場所はJupyter Notebookと同じフォルダとします。

print('Hello world!')

作成したモジュールを利用するには以下の方法があります。

  1. ターミナルからモジュールを直接実行する

  2. ほかのPythonコードからモジュールを呼び出し(import)実行する

モジュールをターミナルから実行する

ターミナルから実行は、以下の手順で行います。

  1. ターミナルを開く

  2. python モジュールファイル名を実行する

ではやってみます。

  1. 新しくUbuntuターミナルを開く

  2. conda activate py38でanaconda環境に入る

  3. python Win_Documents/myprojects/hello.pyを実行する

表示:

Hello world!
1.のターミナルはJupyter Notebookが動いているものとは別に開いてください。
3.のモジュールファイルへのパス(アドレス)は、hello.pyを保存した場所に応じて変更してください。また、python Win_Documents/myprojects/hello.pyの代わりに、cd Win_Documents/myprojects/コマンドでターミナルの作業ディレクトリを移動してから、python hello.pyと実行することもできます。

Jupyter notebookにはビルトインのターミナルも付属しています。Ubuntuのターミナルを新たに立ち上げる代わりに、Jupyter LabのLauncherタブからTerminalUbuntuのターミナルを起動して、上の手順の2.以降と同じ手順でも実行できます。

モジュールをPythonコードから呼び出す

作成したモジュールをPythonから利用する方法は次のとおりです。

[83]:
import hello
Hello world!

import モジュール名でモジュールが読み込まれると、モジュールファイルに含まれるコードが実行されます。今回はhello.pyに書かれているprint('Hello world!')が実行されました。モジュールに関数定義を書いておくと、インポート先でその関数が使えるようになります。

以下をgreeting.pyという名前でJupyter Notebookと同じフォルダに保存して、Jupyter Notebookにインポートしてみます。

print('module imported.')

DEFAULT_NAME = 'John Doe'

def say_hi(name=DEFAULT_NAME):
    print(f'Hi, {name}!')
[84]:
import greeting
module imported.

インポートされた時点でgreeting.pyが実行されるので、まずはprint('module imported.')の出力が表示されました。

モジュール内で定義された変数には、モジュール名.変数名でアクセスできます。

[85]:
print(greeting.DEFAULT_NAME)
John Doe

モジュールで定義された関数を呼び出す際も、モジュール名.関数名()で呼び出すことができます。

[86]:
greeting.say_hi()
greeting.say_hi('Takahisa')
Hi, John Doe!
Hi, Takahisa!

モジュールからインポートする際に、次のように書くと、モジュール名を省略できます。

from モジュール名 import 関数名
[87]:
from greeting import say_hi
say_hi()
Hi, John Doe!

また、次のように書くと長い関数などを別名でimportすることができます。

[88]:
from greeting import say_hi as hi
hi()
Hi, John Doe!

インポートするオブジェクトをカンマ区切りで列挙すると、同一のモジュールから複数のオブジェクト(関数や変数など)をインポートすることができます。

[89]:
from greeting import say_hi, DEFAULT_NAME
say_hi()
print(DEFAULT_NAME)
Hi, John Doe!
John Doe

外部ライブラリを利用する

Pythonでは、モジュールのほかにパッケージライブラリという言葉も使います。関数やクラスなどをまとめて.pyファイルに記載したPythonスクリプトファイルをモジュールといいます。関連する複数のモジュールをディレクトリにまとめたものをパッケージと呼びます。複数のパッケージをまとめたものをライブラリといいます。パッケージを指してライブラリと呼ぶこともあります。

モジュールを直接利用する場合、.pyファイルの場所を指定しなくてはいけません。モジュールをライブラリの形に構成し、Python環境へインストールすることで、importして使いやすくなります。

基本的な機能はデフォルトで標準ライブラリとしてPythonに付属しているため、個別にインストールせずにimportして利用できます。例えば以下のような標準ライブラリがあります。

  • os: WindowsやLinuxなど異なるOS上での違いを吸収して、ファイル操作を行うことができる

  • math: 三角関数、指数関数、丸め誤差の処理

  • sys: プロセスのメモリ管理やエラー処理

  • re: 正規表現による文字列の高度な置換や検索

  • rand: 様々な乱数の精製

標準ライブラリのほか、リモートセンシングやGISの分野では以下のようなオープンソースライブラリが利用されます。

  • rasterio: GeoTiffなどのラスタ形式の衛星画像に関する処理

  • geopandas: Shapefileなどのベクタ形式のデータ処理

  • pyproj: CRS(座標系)の管理

  • numpy: ベクトルや読み込んだ画像を用いた計算

  • OpenCV: 画像データに対するフィルタ処理やセグメンテーション等の高度な処理

  • scikit-learn: 一般的な機械学習アルゴリズムの利用

これらの外部ライブラリを利用する際には、事前に各ライブラリがPython環境にインストールされている必要があります。Anacondaを用いてインストールする場合、ターミナルから、

conda install -c conda-forge rasterio

などとしてインストールすることができます。

プロパティとメソッド

「プロパティ」と「メソッド」は、オブジェクト指向プログラミング(OOP)での用語で、それぞれこれまで出てきた「変数」と「関数」に相当します。OOPの解説はここではしませんが、まずは使い方に慣れてください。

以下の例を見てください。

[90]:
import numpy as np
my_array = np.array([[4.24, 6.51, 1.92],
                     [3.02, 2.03, 5.86]])
print(my_array)
print(type(my_array))
[[4.24 6.51 1.92]
 [3.02 2.03 5.86]]
<class 'numpy.ndarray'>

変数my_arrayには、np.array()関数にリストが渡された際の返り値が代入されました。

OOPでは、値が代入された変数のことをオブジェクトといいます。上の例では「オブジェクトmy_arraynp.array()で作成された」と言います。my_arrayの型はnumpy.ndarray型です。特にOOPでは型のことをクラスと呼んでいます。(英語では歴史的な経緯からtypeやclassが混在して用いられていますが、日本語だと変数は型、オブジェクトはクラスと訳されていることが多いです。)

そして、プロパティメソッドは各クラス(型)にて定義され、クラスから作成されたオブジェクトはそれらを引き継ぎます。

numpy.ndarrayクラスのオブジェクトのプロパティの一つを確認してみます。

[91]:
my_array.size
[91]:
6

numpy.ndarrayクラスには、sizeというプロパティが定義されているので、my_array.sizeとすることで、my_arrayの要素の数を取得することができました。

また、shapeというプロパティには保存されたデータ配列の形が定義されています。この例では2行3列のデータのため、

[92]:
my_array.shape
[92]:
(2, 3)

となります。

ほかにもnumpy.ndarrayクラスには様々なプロパティが定義されていますが、すべてオブジェクト名.プロパティ名の形でアクセスすることができます。

numpy.ndarrayでよく利用するプロパティは以下のものがあります。

  • shape

  • size

  • dtype : 要素のデータタイプint, floatなど

  • T : 要素の行列軸の入れ替えなど

次にmy_arrayの持つメソッドを呼び出してみます。

[93]:
my_flat_array = my_array.flatten()
my_flat_array
[93]:
array([4.24, 6.51, 1.92, 3.02, 2.03, 5.86])

メソッドはオブジェクト自身が持つ関数なので、プロパティと呼び出し方法は似ていますが、メソッド名の後ろに()を付けます。numpy.ndarrayの持つflatten()メソッドは、「オブジェクトの持っている行列を1行に展開したnumpy.ndarrayを返す」という機能です。

sort()メソッドを見てみましょう。sort()メソッドは、「オブジェクトのデータを並び替える」という機能を提供しています。 flatten()と少し違うのは、公式ドキュメント

Sort an array in-place.

とあるように、オブジェクト自身のデータをアップデートするように書き換えるという点です。

[94]:
my_flat_array.sort()
my_flat_array
[94]:
array([1.92, 2.03, 3.02, 4.24, 5.86, 6.51])

メソッドは関数なので、メソッドによっては引数をとることができます。round()メソッドは、四捨五入するメソッドですが、引数に桁数を指定することができます。

[95]:
print(my_flat_array.round())
print(my_flat_array.round(decimals=1))
[2. 2. 3. 4. 6. 7.]
[1.9 2.  3.  4.2 5.9 6.5]

numpy.ndarrayでよく利用するメソッドは以下のものがあります。

  • astype() : 要素の型の変更

  • copy() : 参照コピーではなくdeep copyをする

  • mean(), min(), max(), std(), sum() : 平均、最小、最大値、標準偏差、合計を求める

  • resize() : shapeを変更する

などなど。

返り値を持つメソッドやプロパティは、次のように連結して書くこともできます。

[96]:
round_sum = my_array.round() \
                    .sum()
print(round_sum)
24.0

Pythonのコードの途中で改行する場合、改行の直前で \(バックスラッシュ)を入れることで改行をエスケープすることができます。改行箇所が()などでくくられている際は、開業は無視されるので、\でエスケープする必要はありません。

各クラスによって定義されているプロパティやメソッドは異なります。どのようなプロパティやメソッドが準備されているかは公式ドキュメントで確認できます。また、dir()関数でオブジェクトやクラスのプロパティやメソッド名が取得できます。

[97]:
print(dir(str))
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

よく使うクラスのプロパティやメソッドを知っておくと、非常に効率的にプログラムを書けるようになります。