前回は Python の基本的なデータ型と制御構造について説明しました。今回は関数の基本的な使い方とファイル入出力について説明します。Python は柔軟なプログラミング言語なので、無理にオブジェクト指向機能を使わなくても、いろいろなプログラムを作ることができます。このとき、なくてはならない機能が関数です。関数を使いこなすとちょっと複雑なプログラムでも簡単に作ることができます。
プログラミングは、模型を組み立てる作業と似ています。簡単な処理は Python の組み込み関数を使って実現することができます。ところが、模型が大きくなると、一度に全体を組み立てるのは難しくなります。このような場合、全体をいくつかに分割して、まずその部分ごとに作ります。最後に、それを結合して全体を完成させます。
これは、プログラミングにも当てはまります。実現しようとする処理が複雑になると、一度に全部作ることは難しくなります。そこで、全体を小さな処理に分割して、ひとつひとつの処理を作成し、それらを組み合わせて全体のプログラムを完成させます [*1]。
分割した処理を作成する場合、それを組み込み関数のようにひとつの部品として扱えると便利です。つまり、小さな部品を作り、それを使って大きな部品を作り、最後にそれを組み合わせて全体を完成させます。Python [*2] の場合、もっとも基本となる部品が「関数 (function)」です。
Python の関数定義はとても簡単です。簡単な例として、数を 2 乗する関数を作ってみます。リスト 1 を見てください。
リスト 1 : 数を 2 乗する関数 def square(x): return x * x
def 名前(仮引数名, ...): --- def square(x): 処理A --- return x * x 処理B 処理C 図 1 : Python の関数定義
関数を定義するときは def 文を使います。def 文の構文を図 1 に示します。def 文は図 2 のように数式と比較するとわかりやすいでしょう。
f (x) = x * x 名前 引数 処理内容 def square (x): return x * n 図 2 : def 文と数式の比較
それでは実際に実行してみます。
>>> def square(x): ... return x * x ... >>> square(10) 100
関数を定義するには名前が必要です。def 文の次に関数の名前を記述します。Python の場合、def 文で定義された処理内容は、名前で指定した変数に格納されます。変数 square の値は、たとえば次のように表示されます。
>>> square <function square at 0x7f9b38e20280>
関数名の次にカッコで引数名を指定します。引数を取らない関数は () と記述します。Python の場合、カッコを省略することはできません。それから、関数定義で使用する引数のことを「仮引数」、実際に与えられる引数を「実引数」といいます。seuare の定義で使用した x が仮引数、square(10) の 10 が実引数となります。
そして、カッコの後ろにコロンを付けてインデントを行い、処理内容を記述します。square() の処理内容は return x * x の一つだけですが、ブロックで複数の処理を記述することができます。
Python の場合、関数の返り値は return を使って返します。これはC言語と同じです。Perl のように、ブロックの最後で実行された処理結果が返り値とはなりません。return のない関数は None というデータを返します。None は Python の中でただ一つしか存在しない特別なデータ型で、値がないことや偽 (False) を表すために使用されます。
それでは、ここで変数 x に値が代入されている場合を考えてみましょう。次の例を見てください。
>>> x = 5 >>> x 5 >>> square(10) ?
結果はどうなると思いますか。x には 5 がセットされているので 5 の 2 乗の 25 になるのでしょうか。これは 10 の 2 乗が計算されて、結果は 100 になります。そして、square() を実行したあとでも変数 x の値は変わりません。
>>> square(10) 100 >>> x 5
square() の仮引数 x は、その関数が実行されているあいだだけ有効です。このような変数を「ローカル変数 (local variable)」もしくは「局所変数」といいます。これに対し、最初 x に値を代入した場合、その値は一時的なものではなく、その値は Python を実行しているあいだ存在しています。このような変数を「グローバル変数 (golbal variable)」もしくは「大域変数」といいます。
Python は変数の値を求めるとき、それがローカル変数であればその値を使います。ローカル変数でなければ、グローバル変数の値を使います。次の例を見てください。
>>> x = 10 >>> y = 20 >>> def foo(x): ... print(x) ... print(y) ... >>> foo(100) 100 20
最初にグローバル変数として x と y に値を代入します。関数 foo() は仮引数として x を使います。foo() を実行すると、x はローカル変数なので値は実引数の 100 になります。y はローカル変数ではないのでグローバル変数の値 20 になります。図 3 を見てください。
┌────── Python system ──────┐ │ │ │ グローバル変数 x │ │ グローバル変数 y ←───────┐ │ │ │ │ │ ┌─ 関数 foo 仮引数 x ─┐ │ │ │ │ ↑ │ │ │ │ │ ┌────┘ │ │ │ │ │ print x │ │ │ │ │ ┌──────┼─┘ │ │ │ print y │ │ │ │ │ │ │ └────────────┘ │ │ │ └────────────────────┘ 図 3 : グローバル変数とローカル変数の関係
このように、関数内ではローカル変数の値を優先します。プログラムを作る場合、関数を部品のように使います。ある関数を呼び出す場合、いままで使っていた変数の値が勝手に書き換えられると、呼び出す方が困ってしまいます。部品であるならば、ほかの処理に影響を及ぼさないように、自分自身の中で処理を完結させることが望ましいのです。これを実現するための必須機能がローカル変数なのです。
Python の場合、関数の引数はローカル変数になりますが、関数定義の中で値を代入した変数もローカル変数になります。C言語や Perl のように、ローカル変数の宣言を行う必要はありません。簡単な例を示しましょう。
リスト 2 : 要素の合計値を求める def sum_list(ls): total = 0 for n in ls: total += n return total
関数 sum_list() [*3] の引数 ls には要素が数値のリストやタプルを渡します。変数 total は関数内で 0 を代入しているのでローカル変数になります。for 文で使う変数 n も関数内で代入が行われているのでローカル変数になります。
sum_list() の処理内容は簡単です。最初に、変数 total を0 に初期化します。次に、for 文でリストの要素を順番に取り出して変数 n に代入し、n の値を total に加算していきます。最後に return で total の値を返します。実際に実行すると次のようになります。
>>> sum_list([1, 2, 3, 4, 5,]) 15
ローカル変数が値を保持する期間のことを、変数の「有効範囲 (scope : スコープ)」といいます。Python の場合、引数を含む関数内のローカル変数は、関数を実行している間だけ有効です。関数の実行が終了すると、これらの変数は廃棄されます。
C言語や Perl に慣れているユーザにとって、Python のスコープはちょっとわかりにくいかもしれません。C言語や Perl の場合、ブロックごとにローカル変数の有効範囲を設定できますが、Python にはできません。Python は関数単位でローカル変数の有効範囲を管理しています。
このように、ローカル変数の宣言は不要ですが、関数内でグローバル変数の値を更新したいときには注意が必要です。関数内で変数への代入が行われると、その変数はローカル変数として扱われるので、このままではグローバル変数の値を更新することができません。そこで、Python にはグローバル変数を宣言する global 文が用意されています。
簡単な例を示します。
>>> x = 10 >>> def foo(n): ... global x ... x = n ... >>> x 10 >>> foo(100) >>> x 100
関数 foo() は変数 x の値を n に書き換えます。global 文で x がグローバル変数であることを宣言しています。この宣言がないと、x はローカル変数になってしまいます。foo(100)を実行すると、x の値は 100 に書き換えられます。
ただし、グローバル変数はどの関数からでもアクセスできるので、グローバル変数を多用すると関数を部品として扱うのが難しくなります。ある関数を修正したら、同じグローバル変数を使っていた他の関数が動作しなくなる場合もありえます。グローバル変数はなるべく使わないほうが賢明です。ご注意ください。
Python の関数は、引数にデフォルトの値を設定することができます。値は = で指定します。簡単な例を示します。
>>> def foo(a, b = 10, c = 100): ... print(a, b, c) ... >>> foo(1) 1 10 100 >>> foo(1, 2) 1 2 100 >>> foo(1, 2, 3) 1 2 3
関数 foo() の引数 a は通常の引数で、引数 b と c がデフォルト値を指定した引数です。デフォルト引数は通常の引数の後ろに定義します。foo() を呼び出すとき、引数 a の値を渡さないといけませんが、引数 b と c の値は省略することができます。このとき、使用される値がデフォルト値です。
たとえば、foo(1) と呼び出すと 1 10 100 と表示され、引数 b と c の値はデフォルト値が使用されていることがわかります。foo(1, 2) と呼び出すと、引数 b の値はデフォルト値ではなく、実引数 2 が b の値になります。同様に、foo(1, 2, 3) と呼び出すと、仮引数 c の値は実引数 3 になるので 1 2 3 と表示されます。
引数が多い関数を呼び出すとき、引数の順番を間違えないように指定するのは大変です。Python の場合、'引数名 = 値' という形式で、引数の値を設定することができます。これを「キーワード引数」といいます。簡単な例を示します。
>>> def foo(a, b = 10, c = 100): ... print(a, b, c) ... >>> foo(1, c = 30, b = 20) 1 20 30 >>> foo(c = 30, a = 10, b = 20) 10 20 30
キーワード引数を使うと、引数の順番を覚えておく必要はありません。デフォルト引数も通常の引数もキーワード引数になります。ただし、通常の引数に値が設定されたあと、その引数はキーワード引数として使うことはできません。
foo(10, a = 100) => エラー
また、キーワード引数は通常の引数の後ろに記述しないとエラーになります。引数名にはないキーワードを指定してもエラーになります。もしも、引数名にはないキーワード引数を受け取りたい場合は、名前に ** を付けた引数を用意します。引数に定義されていないキーワード引数はディクショナリに格納されて、** を付けた変数に渡されます。簡単な例を示します。
>>> def bar(a, b = 20, **c): ... print(a, b, c) ... >>> bar(1, 2) 1 2 {} >>> bar(10, b = 20, d = 30, e = 40) 10 20 {'d': 30, 'e': 40}
キーワード引数がない場合は、空のディクショナリが変数 c に渡されます。キーワード引数がある場合、b は引数にあるので 20 は引数 b にセットされます。キーワード d と e は引数にはないので、ディクショナリに格納されて変数 c に渡されます。
辞書に格納されたデータを関数に渡す場合、要素を取り出す処理をいちいちプログラムするのは面倒です。このため Python にはとても便利な機能が用意されています。次の例を見てください。
>>> dic1 = {'a': 10, 'b': 20, 'c': 30} >>> def foo(a = 1, b = 2, c = 3): ... print(a, b, c) ... >>> foo(**dic1) 10 20 30
変数 dic1 には辞書 {'a': 10, 'b': 20, 'c': 30} が格納されています。要素を関数 foo() の引数に渡す場合、**dic1 のように ** を付けて foo() に渡します。すると、辞書が展開されてキーワード引数 a, b, c に要素 10, 20, 30 が渡されます。
キーワード引数を使わずに、引数よりも多くの値を受け取りたい場合は、名前に * を付けた引数を用意します。仮引数に入りきらない値は、タプルに格納されて * を付けた変数に渡されます。これで可変個の引数を受け取る関数を定義することができます。
>>> def baz(a, *b): ... print(a, b) ... >>> baz(1) 1 () >>> baz(1, 2) 1 (2,) >>> baz(1, 2, 3) 1 (2, 3)
仮引数 *b は通常の仮引数よりも後ろに定義します。関数 baz() は通常の引数が一つしかありません。baz(1) と呼び出すと、引数 a に 1 がセットされます。実引数はもうないので、引数 b には空タプル () が渡されます。baz(1, 2) と呼び出すと、実引数 2 がタプルに格納されて引数 c に渡されます。同様に、baz(1, 2, 3) は 2 と 3 がタプルに格納されます。
次は、0 個以上の引数を受け取る関数、つまり、引数が有っても無くてもどちらでも動作する関数を定義します。
>>> def baz0(*a): ... print(a) ... >>> baz0() () >>> baz0(1) (1,) >>> baz0(1, 2) (1, 2) >>> baz0(1, 2, 3) (1, 2, 3)
この場合、仮引数は *a だけで指定します。実引数がない場合、引数 a には空タプル () が渡されます。もし、複数の引数があれば、それらをタプルにまとめて a に渡します。
リストやタプルに格納されたデータを関数に渡す場合、要素を取り出す処理をいちいちプログラムするのは面倒です。このため Python にはとても便利な機能が用意されています。次の例を見てください。
>>> list1 = [1, 2, 3] >>> def foo(a, b, c): ... print(a, b, c) ... >>> foo(*list1) 1 2 3
変数 list1 にはリスト [1, 2, 3] が格納されています。要素を関数 foo() の引数に渡す場合、*list1 のように * を付けて foo() に渡します。すると、リストが展開されて引数 a, b, c に要素 1, 2, 3 が渡されます。
リストの先頭の要素だけを取り出して引数にセットしたい場合は、次のようにプログラムすることもできます。
>>> def foo1(a, *b): ... print(a, b) ... >>> foo1(*list1) 1 (2, 3)
foo1(a, *b) とすると、先頭の要素が引数 a に、残りの要素がタプルに格納されて引数 b に渡されます。
それでは簡単な例題として、データの探索処理を作ってみましょう。データの探索とは、あるデータの中から特定のデータを見つける処理のことです。データの探索はプログラムの中で最も基本的な操作のひとつです。
いちばん簡単な方法は先頭から順番にデータを比較していくことです。これを「線形探索 (linear searching)」といます。たとえば、リストの中からデータを探す処理はリスト 3 のようになります。
リスト 3 : データの探索 def find(n, data): for x in data: if x == n: return True return False
関数 find() はリスト data の中から引数 n と等しいデータを探します。for 文でリストの要素を一つずつ順番に取り出して n と比較します。等しい場合は True を返します。n と等しい要素が見つからない場合は for ループが終了して False を返します。find() の動作は演算子 in と同じです。
見つけた要素の位置が必要な場合は enumrate() を使うと簡単です。
リスト 4 : 位置を返す def position(n, data): for i, x in enumerate(data): if x == n: return i return -1
関数 position() は、データを見つけた場合はその位置 i を返し、見つからない場合は -1 を返します。なお、Python には position() と同じ機能を持つメソッド index() があります。ただし、index() は要素が見つからないときはエラーになるので注意してください。
find() と position() は最初に見つけた要素とその位置を返しますが、同じ要素がリストに複数個あるかもしれません。そこで、要素の個数を数える関数を作ってみましょう。リスト 5 を見てください。
リスト 5 : 個数を返す def count_item(n, data): c = 0 for x in data: if x == n: c += 1 return c
Python には同じ機能を持つメソッド count() があるので、関数名を count_item としました。ローカル変数 c を 0 に初期化し、n と等しい要素 x を見つけたら c の値を +1 します。最後に return で c の値を返します。
このように、線形探索は簡単にプログラムできますが、大きな欠点があります。データ数が多くなると処理に時間がかかるのです。近年、パソコンの性能は著しく向上しているので、線形探索でどうにかなる場合もありますが、データ数が多くて時間かかかるのであれば、次の例題で取り上げる「二分探索」や他の高速な探索アルゴリズム [*4] を使ってみてください。
次は、高速なデータ探索アルゴリズムである「二分探索(バイナリサーチ:binary searching)」を例題として取り上げます。線形探索の実行時間は要素数 N に比例するので、数が多くなると時間がかかるようになります。これに対し二分探索は log2 N に比例する時間でデータを探すことができます。ただし、探索するデータはあらかじめ昇順に並べておく必要があります。この操作を「ソート (sort)」といいます。二分探索は最初にデータをソートしておかないといけないので、線形探索に比べて準備に時間がかかります。
二分探索の動作を図 4 に示します。
[11 22 33 44 55 66 77 88 99] key は 66 ↑ 66 > 55 後半を探す 11 22 33 44 55 [66 77 88 99] 88 > 66 前半を探す ↑ 11 22 33 44 55 [66 77] 88 99 77 > 66 前半を探す ↑ 11 22 33 44 55 [66] 77 88 99 66 = 66 発見 ↑ 図 4 : 二分探索
二分探索は探索する区間を半分に分割しながら調べていきます。キーが 66 の場合を考えてみましょう。まず区間の中央値 55 とキーを比較します。データが昇順にソートされている場合、66 は中央値 55 より大きいので区間の前半を調べる必要はありません。したがって、後半部分だけを探索すればいいのです。
あとは、これと同じことを後半部分に対して行います。最後には区間の要素が一つしかなくなり、それとキーが一致すれば探索は成功、そうでなければ探索は失敗です。ようするに、探索するデータ数が 1 / 2 ずつ減少していくわけです。
図 4 の場合、線形探索ではデータの比較が 6 回必要になりますが、二分探索であれば 4 回で済みます。また、データ数が 1,000,000 個になったとしても、二分探索を使えば高々 20 回程度の比較で探索を完了することができます。
それでは、リストからデータを二分探索するプログラムを作ってみましょう。二分探索は簡単にプログラムできます。リスト 6 を見てください。
リスト 6 : 二分探索 def bsearch(x, ls): low = 0 high = len(ls) - 1 while low <= high: middle = (low + high) // 2 if x == ls[middle]: return True elif x > ls[middle]: low = middle + 1 else: high = middle - 1 return False
最初に、探索する区間を示す変数 low と high を初期化します。リストの長さは関数 len() で取得し、最後の要素の位置を high にセットします。次の while ループで、探索区間を半分ずつに狭めていきます。まず、区間の中央値を求めて middle にセットします。if 文で middle の位置にある要素とx を比較し、等しい場合は探索成功です。return で True を返します。
x が大きい場合は区間の後半を調べます。変数 low に middle + 1 をセットします。逆に、x が小さい場合は前半を調べるため、変数 high に middle - 1 をセットします。これを二分割できる間繰り返します。low が high より大きくなったら分割できないので繰り返しを終了し False を返します。
簡単な実行例を示しましょう。
>>> a = [11, 22, 33, 44, 55, 66, 77, 88, 99] >>> bsearch(44, a) True >>> bsearch(40, a) False
二分探索はデータを高速に探索することができますが、あらかじめデータをソートしておく必要があります。このため、途中でデータを追加するには、データを挿入する位置を求め、それ以降のデータを後ろへ移動する処理が必要になります。つまり、データの登録には時間がかかるのです。
したがって、二分探索はプログラムの実行中にデータを登録し、同時に探索も行うという使い方には向いていません。途中でデータを追加して探索も行う場合は、他の高速な探索アルゴリズムを検討してみてください。
ソート (sort) は、ある規則に従ってデータを順番に並べ換える操作です。たとえば、データが整数であれば大きい順に並べる、もしくは小さい順に並べます。Python にはリストをソートするメソッド sort がありますが、私達でもプログラムすることができます。
今回は簡単な例題ということで、挿入ソート (insert sort) を取り上げます。挿入ソートの考え方はとても簡単です。ソート済みのリストに新しいデータを挿入していくことでソートを行います。図 5 を見てください。
[9] 5 3 7 6 4 8 5 を取り出す [9] * 3 7 6 4 8 5 を[9]の中に挿入する [5 9] 3 7 6 4 8 9 をひとつずらして先頭に 5 を挿入 [5 9] * 7 6 4 8 3 を取り出して[5 9]の中に挿入する [3 5 9] 7 6 4 8 先頭に 3 を挿入 [3 5 9] * 6 4 8 7 を取り出して[3 5 9] に挿入 [3 5 7 9] 6 4 8 9 を動かして 7 を挿入 残りの要素も同様に行う 図 5 : 挿入ソート
最初は先頭のデータひとつがソート済みと考えて、2 番目のデータをそこに挿入することからスタートします。データを挿入するので、そこにあるデータをどかさないといけません。そこで、挿入位置を決めるため後ろから順番に比較するとき、いっしょにデータの移動も行うことにします。
プログラムをリスト 7 に示します。
リスト 7 : 挿入ソート def insert_sort(ls): size = len(ls) i = 1 while i < size: tmp = ls[i] j = i - 1 while j >= 0 and tmp < ls[j]: ls[j + 1] = ls[j] j -= 1 ls[j + 1] = tmp i += 1
len(ls) でリストの長さを求めて変数 size にセットします。最初のループで挿入するデータを選びます。ソート開始時は先頭のデータひとつがソート済みと考えるるので、2 番目のデータ(添字では 1)を取り出して挿入していきます。2 番目のループで挿入する位置を探しています。探索は後ろから前に向かって行っていて、このときデータの移動も同時に行っています。
それでは実行してみましょう。
>>> insert_sort([5, 6, 4, 7, 3, 8, 2, 9, 1]) [1, 2, 3, 4, 5, 6, 7, 8, 9]
挿入ソートはデータ数が多くなると実行時間がかかります。データ数を N とすると、実行時間は N の 2 乗に比例します。挿入ソートは簡単ですが遅いアルゴリズムなのです。高速なソートは次回の例題で取り上げます。
一般に、関数の呼び出し方には二つの方法があります。一つが「値呼び (call by value)」で、もう一つが「参照呼び (call by reference)」です。近代的なプログラミング言語では「値呼び」が主流です。
値呼びの概念はとても簡単です。
値呼びのポイントは 2. です。データを引数に代入するとき、データのコピーが行われるのです。たとえば、変数 a の値が 10 の場合、関数 foo(a) を呼び出すと、実引数 a の値 10 が foo の仮引数にコピーされます。変数に格納されている値そのものを関数に渡すので、「値渡し」とか「値呼び」と呼ばれます。また、値呼びは任意の式の値を実引数として渡すことができます。たとえば foo(a + b) の場合、引数に渡された式 a + b を計算し、その結果が foo の仮引数に渡されます。
値呼びは単純でわかりやすいのですが、呼び出し先 (caller) から呼び出し元 (callee) の局所変数にアクセスできると便利な場合もあります。仮引数に対する更新が直ちに実引数にも及ぶような呼び出し方が「参照呼び」です。たとえば、次に示すプログラムで foo の仮引数の値を書き換えた場合、参照呼びであれば呼び出し元の変数の値も書き換えられます。
>>> def foo(a): a = 100 print(a) >>> x = 10 >>> x 10 >>> foo(x) 100 >>> x 10
foo が参照呼びされているのであれば、仮引数 a の値を 100 に書き換えると、実引数である x の値も 100 になります。foo(x) を呼び出したあと、x の値は 10 のままなので、Python は「値呼び」であることがわかります。
このように Python は値呼びですが、仮引数にデータをセットするとき、オブジェクトのコピーは行われないことに注意してください。そもそも Python の変数 (引数) はオブジェクトを格納しているのではなく、オブジェクトへの参照を格納しているのです。参照はC言語のポインタや Perl のリファレンスのことで、実態はオブジェクトに割り当てられたメモリのアドレスです。図 6 を見てください。
┌─┐ ┌───┐ 変数 a │・┼──→│(1, 2)│ データ (object) └─┘ └───┘ (1) a = (1, 2) の場合 ┌─┐ ┌───┐ 変数 a │・┼──→│(1, 2)│ データ (object) └─┘ └───┘ ↑ ┌─┐ │ 変数 b │・┼────┘ └─┘ (2) b = a の場合 図 6 : Python の代入操作
変数 a にタプル (1, 2) を代入する場合、Python はタプルの実体 (object) を生成して、図 6 (1) のようにオブジェクトへの参照を a に書き込みます。a の値を変数 b に代入する場合も、図 6 (2) のように a に格納されているオブジェクトへの参照を b に書き込むだけで、タプルはコピーされません。
他のプログラミング言語、たとえばC言語の場合、変数はデータを格納する容器にたとえられますが、Python の変数はデータに付ける名札と考えることができます。したがって、代入はデータに名札を付ける動作になります。図 6 (2) のように、一つのデータに複数の名札を付けることもできるわけです。
これは引数の場合も同じです。実引数に格納されている値はオブジェクトへの参照であり、それが仮引数にコピーされます。つまり、参照 (アドレス) を値渡ししているわけです。オブジェクトは演算子 is または is not で同じものかどうか調べることができます。次の例を見てください。
>>> a = (1, 2) >>> b = a >>> a is b True >>> def foo(x): ... return a is x ... >>> foo(a) True
変数 a に (1, 2) を代入し、変数 b に a の値を代入します。a と b は同じオブジェクトを参照しているので、a is b は True になります。次に、foo(x) で変数 a と引数 x を is で比較します。foo() に a を渡すと True を返すので、同じオブジェクトを参照していることがわかります。
タプルは immutable なオブジェクトですが、リストは mutable なオブジェクトなので、関数の引数にリストを渡してそれを破壊的に修正すると、呼び出し元の変数の値も書き換えられたかのようにみえます。次の例を見てください。
>>> def bar(x, y): ... x.append(y) ... return x ... >>> a = [1, 2, 3] >>> id(a) 139953370688896 >>> bar(a, 4) [1, 2, 3, 4] >>> a [1, 2, 3, 4] >>> id(a) 139953370688896
変数 a にリスト [1, 2, 3] をセットします。関数 bar() は引数 x のリストに append() で引数 y を追加します。append() はリストを破壊的に修正するので、bar(a, 4) とすると a の値も [1, 2, 3, 4] になります。関数 id() は引数のオブジェクトの識別子 (オブジェクト ID) を求めます。オブジェクト ID はオブジェクトに固有の番号です。
この場合、変数 a の値が書き換えられたのではなく、a が参照しているオブジェクトを破壊的に修正しているだけなのです。bar() を呼び出す前後で変数 a が格納しているオブジェクト ID を求めると同じ値になる、つまり同じオブジェクトのままであることがわかります。リストの値を元のままにしておきたい場合は、x + [y] のように新しいリストを生成してください。
プログラムを作っていると、以前作った関数と同じ処理が必要になる場合があります。いちばんてっとり早い方法はソースファイルからその関数をコピーすることですが、賢明な方法とはいえません。このような場合、自分で作成した関数をライブラリとしてまとめておくと便利です。
ライブラリの作成で問題になるのが「名前の衝突」です。複数のライブラリを使うときに、同じ名前の関数や変数が存在すると、そのライブラリは正常に動作しないでしょう。この問題は「モジュール (module)」を使うと解決することができます。Python には多くのモジュールが標準で添付されています。これらのモジュールを使うことで、プログラムを効率的に開発することができます。
Python のモジュール機能はとても簡単で、プログラムが書かれているソースファイルが一つのモジュールになります。そして、拡張子 .py を除いたファイル名がモジュール名になります。
簡単な例を示しましょう。
リスト 8 : foo.py a = 10 def test(): print('module foo') リスト 9 : bar.py a = 100 def test(): print('module bar')
ファイル foo.py には変数 a と関数 test() が定義されています。ファイル bar.py にも変数 a と関数 test() が定義されています。これらのファイルはモジュールとして利用することができます。モジュール名は foo と bar になります。
モジュールを利用する場合は import 文を使います。モジュールをインポートするには、そのファイルがカレントディレクトリに存在するか、Python で定める場所 (モジュール sys の変数 path のリストに定義されているディレクトリのひとつ) に存在する必要があります。ここでは foo と bar はカレントディレクトリにあるとします。実行例は次のようになります。
>>> import foo, bar >>> a = 1000 >>> def test(): print('test') ... >>> a 1000 >>> foo.a 10 >>> bar.a 100 >>> test() test >>> foo.test() module foo >>> bar.test() module bar
最初に import で foo と bar をインポートします。複数のモジュールをインポートするときはカンマ ( , ) で区切って指定します。モジュールはインポートされたときに、モジュール内のプログラムを実行します。foo と bar には変数 a への代入と、関数 test() が定義されています。これらの処理はインポートされたときに実行されます
次に、変数 a に 1000 を代入し、関数 test() を定義します。対話モード(トップレベル)で変数や関数を定義すると、それらは main モジュールに登録されます。main モジュール内の変数や関数は、変数名や関数名だけでアクセスすることができます。したがって、a の値は 1000 であり、test() を実行すると test と表示されます。
他のモジュールに定義された変数や関数にアクセスするには、名前の前にモジュール名とドット ( . ) を付けます。foo.a とすると、モジュール foo の変数 a にアクセスするので、値は 10 になります。同様に bar.a はモジュール bar の変数 a にアクセスするので 100 になります。関数も同様に foo.test() はモジュール foo の関数 test() を実行し、bar.test() はモジュール bar の test() を実行します。このように、モジュールを使うと名前の衝突を回避することができます。
ところで、from 文を使うと他のモジュールで定義されている名前をそのまま自分のモジュールで利用することができます。from の構文を示します。
from モジュール import 名前, ...
名前の衝突がない場合は、from 文を使うとモジュール名を付けなくてすむので便利です。簡単な例を示します。
$ python3 ... 略 ... >>> from foo import a, test >>> a 10 >>> test() module foo
モジュール foo の変数 a と関数 test() は、名前の前に foo を付けなくてもアクセスすることができます。名前の指定にアスタリスク (*) を指定すると、モジュール内で定義されているすべての名前 [*5] を利用することができます。
$ python3 ... 略 ... >>> from bar import * >>> a 100 >>> test() module bar
ただし、この方法はあまり推薦されていないようです。
Python の場合、同じ名前の関数を再定義してもエラーにはなりません。最後に定義された関数が有効になります。from 文を使って foo と bar をインポートすると、関数 test() の定義は最後にインポートしたモジュールの test() になります。
$ python3 ... 略 ... >>> from foo import * >>> from bar import * >>> test() module bar
なお、組み込み関数の再定義も可能です。from 文を使ってモジュールをインポートする場合はご注意ください。
次はデータの入出力について簡単に説明しましょう。Python は「ファイルオブジェクト」というデータを介してファイルにアクセスします。ファイルオブジェクトはファイルと一対一に対応していて、ファイルからデータを入力する場合は、ファイルオブジェクトを経由してデータが渡されます。逆に、ファイルへデータを出力するときも、ファイルオブジェクトを経由して行われます。
通常のファイルは、ファイルオブジェクトを生成しないとアクセスすることはできません。ただし、標準入出力は Python の起動時にファイルオブジェクトが自動的に生成されるので、簡単に利用することができます。一般に、キーボードからの入力を「標準入力」、画面への出力を「標準出力」といいます。標準入出力に対応するファイルオブジェクトは、モジュール sys の変数に格納されています。表 1 に変数名を示します。
変数名 | ファイル |
---|---|
stdin | 標準入力 |
stdout | 標準出力 |
stderr | 標準エラー出力 |
ファイルのアクセスは標準入出力を使うと簡単です。関数 input() は標準入力から 1 行読み込みます。このとき、改行文字は削除されます。引数に文字列を渡すと、それをプロンプトとして表示します。
簡単な実行例を示します。
>>> input() foo 'foo' >>> input() foo bar baz 'foo bar baz' >>> input('Number > ') Number > 12345 '12345'
データの出力は print() で行うと簡単です。print() の仕様を以下に示します。
print(*args, file=sys.stdout, sep=' ', end='\n', flush=False)
print() に複数のデータを渡すと、データとデータの間に引数 sep を挿入します。そして、print() はデータを出力したあと引数 end を出力します。end のデフォルトは改行です。改行したくない場合は end に異なるデータ (たとえば空文字列など) を指定してください。
簡単な実行例を示します。
>>> print(1, 2, 3, 4, 5) 1 2 3 4 5 >>> print(1, 2, 3, 4, 5, sep='') 12345 >>> for x in [1, 2, 3, 4, 5]: ... print(x, end='') ... 12345>>>
それでは簡単な例として、データの合計値を求めるプログラムを作ります。リスト 10 を見てください。
リスト 10 : 合計値を求める (1) total = 0 while True: a = int(input()) if a < 0: break total += a print('total =', total)
このプログラムをファイル test.py に保存してシェルで実行すると、次のようになります。
$ python3 test.py 1 2 3 4 5 -1 total = 15
数値をキーボードから入力します。関数 int() は引数を整数に変換します。変換できない場合はエラーになります。-1 を入力すると break 文で while ループから脱出して合計値を表示します。
このデータをファイル test.dat に保存してリダイレクトすれば、合計値を求めることができます。
$ cat test.dat 1 2 3 4 5 -1 $ python3 test.py < test.dat total = 15
1 行に複数のデータがある場合は、読み込んだデータを分離して数値に変換します。列ごとに合計値を求めたい場合は、リスト 11 のようなプログラムになります。
リスト 11 : 合計値を求める (2) total = [0] while True: a = input() if a == '': break for x, y in enumerate(a.split()): if x < len(total): total[x] = total[x] + int(y) else: total.append(int(y)) print('total =', *total)
リスト total に列ごとの合計値を求めます。input() で 1 行読み込んで変数 a にセットします。データの終了は空文字列 '' で判断します。a が空文字列 '' ならば break 文で while ループを脱出します。次に、空白文字 (スペースやタブ) を区切り文字として、a.split() で文字列を分離します。split() は文字列のメソッドです。簡単な使用例を示します。
>>> a = '1 2 3 4 5' >>> a.split() ['1', '2', '3', '4', '5']
split() は分離した文字列をリストに格納して返します。このあと、for文 と enumerate() を使って、要素を一つずつ取り出して、関数 int() で数値に変換して total に加算します。x < len(total) の場合は、total[x] に y を加算するだけですが、そうでない場合は total の範囲を超えてしまいます。total の最後尾に int(y) を append() で追加します。最後に total の内容を出力します。
それでは、リスト 11 をファイル test1.py に保存して、シェルで実行してみましょう。
$ python3 test1.py 1 2 3 4 5 6 7 8 9 total = 12 15 18
データをファイル test1.dat に保存してリダイレクトすることもできます。
$ cat test1.dat 1 2 3 4 5 6 7 8 9 $ python3 test1.py < test1.dat total = 12 15 18
結果をファイルに格納したい場合も、次のようにリダイレクトすれば簡単です。
$ python3 test1.py < test1.dat > total.dat
このように、標準入出力を使うと簡単にプログラムを作ることができます。
ところで、ファイルを先頭から順番に 1 行ずつ読み込む場合、ファイルをコレクションと同じように扱えると便利です。Python の場合、for 文にファイルオブジェクトを与えると、ファイルから 1 行ずつ読み込むことができます。リスト 12 を見てください。
リスト 12 : 合計値を求める (3) import sys total = [0] n = 0 for a in sys.stdin: for x, y in enumerate(a.split()): if x <= n: total[x] = total[x] + int(y) else: total.append(int(y)) n += 1 print('total =', *total)
標準入力 stdin を使うため、最初に import 文でモジュール sys をインポートします。そして、for a in sys.stdin で標準入力から 1 行ずつ読み込み、変数 a にセットします。ファイルの終了を検出すると for 文の繰り返しが終了します。ファイル終了のチェックが不要になるので、プログラムが簡単になります。
なお、キーボードからデータを入力する場合は、最後に入力の終わりを表すデータ (Unix 系 OS ならば Ctrl-D, Windows ならば Ctrl-Z) を入力してください。
標準入出力を使わずにファイルにアクセスする場合、次の 3 つの操作が基本になります。
1 はアクセスするファイルを指定して、それと一対一に対応するファイルオブジェクトを生成することです。入出力関数は、そのファイルオブジェクトを経由してファイルにアクセスします。Python の場合、入出力関数はメソッドとして定義されています。ファイルをオープンするには関数 open() を使います。オープンしたファイルは必ずクローズしてください。この操作を行うメソッドが close() です。
Python のファイル入出力機能は、C言語の標準ライブラリを用いて実装されているので、その仕様はC言語とほとんど同じです。最初に open() から説明します。
open(filename, mode)
open() は引数にファイル名 filename とアクセスモード mode を指定して、filename で指定されたファイルに対応するファイルオブジェクトを生成して返します。
アクセスモードは文字列で指定します。表 2 にアクセスモードを示します。
モード | 動作 |
---|---|
r | 読み込み (read) モード |
w | 書き出し (write) モード |
a | 追加 (append) モード |
名前 | 機能 |
---|---|
read(size) | size バイト読み込んで文字列にして返す size を省略するとファイル全体を読み込む |
readline() | 1 行読み込んで文字列にして返す |
readlines() | ファイルのすべての行を読み込んでリストに格納して返す |
write(s) | 文字列 s をファイルに書き込む |
writelines(x) | リスト x に格納された文字列をファイルに書き込む |
読み込みモードの場合、ファイルが存在しないとエラーになります。書き出しモードの場合、ファイルが存在すれば、そのファイルのサイズを 0 に切り詰めてからオープンします。追加モードの場合、ファイルの最後尾にデータを追加します。+ を付加すると更新モードになり、読み書き両方が可能になります。b はバイナリモードを指定します。バイナリモードはあとで説明します。
ファイル入出力用のメソッドを表 3 に示します。
簡単な例を示しましょう。
>>> out_f = open('test.dat', 'w') >>> for x in range(10): ... out_f.write(str(x) + '\n') ... >>> out_f.close() >>> in_f = open('test.dat') >>> for x in in_f: ... print(x) ... 0 1 2 3 4 5 6 7 8 9 >>> in_f.close() >>>
test.dat をライトモードでオープンして、0 から 9 までの数値を書き込みます。str(x) は引数 x を文字列に変換する関数です。数値の後ろに改行文字をつけて、1 行に数値を 1 個書き込みます。
それから、test.dat のデータを読み込みます。open() でアクセスモードを省略すると、リードモード 'r' に設定されます。あとは、for 文でファイルから 1 行ずつデータを読み込み、それを print() で出力します。このように、データをファイルに書き込み、ファイルからデータを読み込むことができます。
なお、with 文を使うと自動的にファイルをクローズすることができます。
with open(filename, mode) as 変数: 処理 ...
open() で生成されたファイルオブジェクトは as の後ろの変数にセットされます。with 文の処理を終了すると (エラーで抜ける場合も) ファイルはクローズされます。with 文は「例外処理」で詳しく説明します。
ファイルは大きく分けると、「テキスト」と「バイナリ」の 2 種類があります。テキストファイルは特定の文字コード (ASCII コードや UTF-8 など) でエンコードされたデータが格納されています。コマンド cat などで画面に表示したり、エディタで編集することができます。
これに対し、バイナリファイルはテキストファイル以外のものを指します。バイナリファイルはテキストファイルと違い、特定の文字コードの範囲には収まらないデータが含まれているため、cat で画面に表示すると、わけのわからない文字を画面に巻き散らすことになります。
Python でバイナリファイルを操作する場合、一番簡単な方法は open() の引数 mode に 'b' を付加することです。これで、ファイルをバイナリモードでオープンすることができます。基本的な入出力にはメソッド read() と write() を使います。このとき、返り値または引数のデータ型は、文字列ではなく「バイトシーケンス (byte sequence)」になります。
バイトシーケンスは要素がバイト (0 - 255) の一次元配列です。Python のバイトシーケンスには bytes と bytearray の二種類があり、bytes は immutable、bytearray は mutable なデータ型になります。どちらもシーケンスの操作が可能 (bytearray は mutable な操作も可能) です。
bytes のリテラル表記は b'...' のように文字列リテラルの前に b を付けます。そのほかに、コンストラクタ bytes(), bytearray() も用意されています。
簡単な例を示しましょう。
>>> a = b'12345678' >>> a b'12345678' >>> a[0] 49 >>> a[7] 56 >>> a[2:6] b'3456' >>> bytes(10) b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' >>> bytes(range(10)) b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t' >>> bytearray(10) bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') >>> b = bytearray(a) >>> b bytearray(b'12345678') >>> b[0] = ord('a') >>> b bytearray(b'a2345678')
このほかにも、バイトシーケンスには文字列と同様の操作を行うメソッドが多数用意されています。詳細はリファレンスマニュアル 4.8. バイナリシーケンス型 (https://docs.python.jp/3/library/stdtypes.html#binary-sequence-types-bytes-bytearray-memoryview) をお読みください。
read(size) はファイルから size バイト読み込んで bytes に格納して返します。引数を省略するとファイルの終了まで読み込みます。データを bytearray に読み込みたいときはメソッド readinto() を使います。write() は引数のバイトシーケンス (bytes-like-object) をファイルに書き込みます。
簡単な例を示しましょう。
>>> with open('test.dat', 'wb') as f: ... f.write(bytes(range(16))) ... 16 >>> with open('test.dat', 'rb') as f: ... f.read() ... b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f' >>> a = bytearray(20) >>> a bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') >>> with open('test.dat', 'rb') as f: ... f.readinto(a) ... 16 >>> a bytearray(b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x00\x00\x00\x00')
write() は書き込んだバイト数を返します。readinto() は実際に読み込んだバイト数を返します。bytearray の大きさよりも小さな値が返ってきたら、ファイルの終了に到達したことがわかります。
Pyton の場合、モジュール sys の変数 argv にコマンドラインで与えられた引数が格納されています。リスト 13 を見てください。
リスト 13 : 変数 argv の表示 (test3.py) import sys print(sys.argv)
test3.py は変数 argv の内容を表示するだけです。3 つの引数を与えて起動すると、次のように表示されます。
$ python3 test3.py foo bar baz ['test3.py', 'foo', 'bar', 'baz']
リストの先頭要素は test.py になることに注意してください。簡単な例として、リスト 12 のプログラムをファイル名を指定するように改造してみましょう。
リスト 14 : 合計値を求める (test4.py) import sys total = [0] with open(sys.argv[1]) as in_f: for a in in_f: for x, y in enumerate(a.split()): if x < len(total): total[x] = total[x] + int(y) else: total.append(int(y)) print('total =', *total)
ファイル名はリスト argv[1] に格納されています。with 文の open(argv[1]) でファイルをオープンし、ファイルオブジェクトを変数 in_f にセットします。そして、for 文でファイルから 1 行読み込み、列ごとに数値の合計値を求めます。with 文を使っているので、close() でファイルをクローズする必要はありません。
$ cat test1.dat 1 2 3 4 5 6 7 8 9 $ python3 test4.py test1.dat total = 12 15 18
print() はデータをそのまま出力しますが、整形して出力したい場合は文字列のフォーマット操作を使います。Python2 ではC言語によく似た書式指定を使いましたが、Python3 では推薦されていないようです。Python3 にはいくつか方法がありますが、ここでは str.format() を紹介します。
書式文字列.format(引数, ...)
書式文字列の {} は format() の引数を文字列に変換します。{n} とすると n 番目の引数を変換します。以下に主な変換指示子を示します。
: と変換指示子の間に桁数などを指定することができます。
この他にもいろいろな機能があります。詳細はライブラリリファレンス 書式指定文字列の文法 をお読みくださいませ。
簡単な使用例を示します。
>>> '{} {} {}'.format(1, 2, 3) '1 2 3' >>> '{1} {2} {0}'.format(1, 2, 3) '2 3 1' >>> for x in range(16): print('{} {:b} {:o} {:x}'.format(x,x,x,x)) ... 0 0 0 0 1 1 1 1 2 10 2 2 3 11 3 3 4 100 4 4 5 101 5 5 6 110 6 6 7 111 7 7 8 1000 10 8 9 1001 11 9 10 1010 12 a 11 1011 13 b 12 1100 14 c 13 1101 15 d 14 1110 16 e 15 1111 17 f >>> '{:e} {:f} {:g}'.format(1.2345, 1.2345, 1.2345) '1.234500e+00 1.234500 1.2345' >>> '{:e} {:f} {:g}'.format(1.2345e10, 1.2345e10, 1.2345e10) '1.234500e+10 12345000000.000000 1.2345e+10' >>> '{:9}'.format('abc') 'abc ' >>> '{:<9}'.format('abc') 'abc ' >>> '{:>9}'.format('abc') ' abc' >>> '{:^9}'.format('abc') ' abc '
関数の基本的な使い方とモジュール、ファイル入出力について説明しました。ところで、関数というと忘れてはならない機能に「再帰呼び出し」があります。再帰呼び出し (再帰定義) は敬遠されている方もいると思いますが、難しい話ではありません。再帰定義を使いこなすと、複雑なアルゴリズムでも簡単にプログラムを作ることができます。次回は再帰定義を中心に、関数について詳しく説明します。