M.Hiroi's Home Page
http://www.geocities.jp/m_hiroi/

Lightweight Language

お気楽 Python プログラミング入門

第 2 回 関数とファイル入出力

[ PrevPage | Python | NextPage ]

はじめに

前回は Python の基本的なデータ型と制御構造について説明しました。今回は関数の基本的な使い方とモジュール、ファイル入出力について説明します。

Python は柔軟なプログラミング言語なので、無理にオブジェクト指向機能を使わなくても、いろいろなプログラムを作ることができます。このとき、なくてはならない機能が関数です。関数を使いこなすとちょっと複雑なプログラムでも簡単に作ることができます。

●関数の基礎知識

プログラミングは、模型を組み立てる作業と似ています。簡単な処理は Python の組み込み関数を使って実現することができます。ところが、模型が大きくなると、一度に全体を組み立てるのは難しくなります。このような場合、全体をいくつかに分割して、まずその部分ごとに作ります。最後に、それを結合して全体を完成させます。

これは、プログラミングにも当てはまります。実現しようとする処理が複雑になると、一度に全部作ることは難しくなります。そこで、全体を小さな処理に分割して、ひとつひとつの処理を作成し、それらを組み合わせて全体のプログラムを完成させます [*1]

分割した処理を作成する場合、それを組み込み関数のようにひとつの部品として扱えると便利です。つまり、小さな部品を作り、それを使って大きな部品を作り、最後にそれを組み合わせて全体を完成させます。Python [*2] の場合、もっとも基本となる部品が「関数 (function) 」です。

-- note --------
[*1] このような方法を「分割統治法」といいます。
[*2] Python はオブジェクト指向プログラミング (OOP) をサポートしているので、OOP 的な機能を使って部品に相当するオブジェクト (object) を作ることもできます。

●関数の定義方法

Python の関数定義はとても簡単です。簡単な例として、数を 2 乗する関数を作ってみます。リスト 1 を見てください。

リスト 1 : 数を 2 乗する関数

def square(x):
    return x * x

関数を定義するときは def 文を使います。def 文の構文を図 1 に示します。def 文は図 2 のように数式と比較するとわかりやすいでしょう。

def 名前(仮引数名, ...):  ---  def square(x):
    処理A                 ---      return x * x
    処理B
    処理C

  図 1 : Python の関数定義
      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 0x00C975F0>

関数名の次にカッコで引数名を指定します。引数を取らない関数は () と記述します。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

>>> foo(100)
>>> x
100

関数 foo() は変数 x の値を n に書き換えます。global 文で x がグローバル変数であることを宣言しています。この宣言がないと、x はローカル変数になってしまいます。foo(100)を実行すると、x の値は 100 に書き換えられます。

ただし、グローバル変数はどの関数からでもアクセスできるので、グローバル変数を多用すると関数を部品として扱うのが難しくなります。ある関数を修正したら、同じグローバル変数を使っていた他の関数が動作しなくなる場合もありえます。グローバル変数はなるべく使わないほうが賢明です。ご注意ください。

-- note --------
[*3] Python には同等の機能を持つ組み込み関数 sum() が用意されています。

●デフォルト引数

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, 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() があります。

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] を使ってみてください。

-- note --------
[*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) 」です。近代的なプログラミング言語では「値呼び」が主流です。

値呼びの概念はとても簡単です。

  1. 受け取るデータを格納する変数 (仮引数) を用意する。
  2. データを引数に代入する。
  3. 関数の実行終了後、引数を廃棄する。

値呼びのポイントは 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 は値呼びですが、仮引数にデータをセットするとき、オブジェクトのコピーは行われないことに注意してください。そもそも 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 を返すので、同じオブジェクトを参照していることがわかります。

関数が参照呼びの場合でも、参照しているオブジェクトを直接書き換えなければ、値呼びと同じように動作します。Python の場合、数値、タプル、文字列などのオブジェクトは更新不能なので、関数は値呼びとして動作します。[修正]

タプルは更新不能なオブジェクトですが、リストは更新可能なオブジェクトなので、関数の引数にリストを渡してそれを破壊的に修正すると、呼び出し元の変数の値も書き換えられたかのようにみえます。次の例を見てください。

>>> def bar(x, y):
        x.append(y)
        return x
   
>>> a = [1,2,3]
>>> id(a)
12207048
>>> bar(a, 4)
[1, 2, 3, 4]
>>> a
[1, 2, 3, 4]
>>> id(a)
12207048

変数 a にリスト [1, 2, 3] をセットします。関数 bar() は引数 x のリストに append() で引数 y を追加します。append() はリストを破壊的に修正するので、bar(a, 4) とすると a の値も [1, 2, 3, 4] になります。

このように、参照呼びはオブジェクトの値を直接書き換えるときには便利なのですが、オブジェクトを参照している変数の値も同時に書き換えてしまいます。[修正]

この場合、変数 a の値が書き換えられたのではなく、a が参照しているオブジェクトを破壊的に修正しているだけなのです。bar() を呼び出す前後で変数 a が格納しているオブジェクトの識別子を求めると同じ値になる、つまり同じオブジェクトのままであることがわかります。リストの値を元のままにしておきたい場合は、x + [y] のように新しいリストを生成してください。

-- [修正] (2011/02/26) --------
Python の呼び出し方は「参照呼び」ではなく「値呼び」です。訂正するとともに深くお詫び申しあげます。値呼びと参照呼びの詳しい説明は拙作のページ
Memorandum をお読みくださいませ。

●モジュール

プログラムを作っていると、以前作った関数と同じ処理が必要になる場合があります。いちばんてっとり早い方法はソースファイルからその関数をコピーすることですが、賢明な方法とはいえません。このような場合、自分で作成した関数をライブラリとしてまとめておくと便利です。

ライブラリの作成で問題になるのが「名前の衝突」です。複数のライブラリを使うときに、同じ名前の関数や変数が存在すると、そのライブラリは正常に動作しないでしょう。この問題は「モジュール (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 の構文を示します。

from モジュール import 名前, ...

名前の衝突がない場合は、from 文を使うとモジュール名を付けなくてすむので便利です。簡単な例を示します。

>>> from foo import a, test
>>> a
10
>>> test()
module foo

モジュール foo の変数 a と関数 test() は、名前の前に foo を付けなくてもアクセスすることができます。名前の指定にアスタリスク (*) を指定すると、モジュール内で定義されているすべての名前 [*5] を利用することができます。

>>> from bar import *
>>> a
100
>>> test()
module bar

Python の場合、同じ名前の関数を再定義してもエラーにはなりません。最後に定義された関数が有効になります。from 文を使って foo と bar をインポートすると、関数 test() の定義は最後にインポートしたモジュールの test() になります。

>>> from foo import *
>>> from bar import *
>>> test()
module bar

なお、組み込み関数の再定義も可能です。from 文を使ってモジュールをインポートする場合はご注意ください。

-- note --------
[*5] ただし、アンダーバー '_' で始まる名前は除かれます。

●ファイル入出力

次はデータの入出力について簡単に説明しましょう。Python は「ファイルオブジェクト」というデータを介してファイルにアクセスします。ファイルオブジェクトはファイルと一対一に対応していて、ファイルからデータを入力する場合は、ファイルオブジェクトを経由してデータが渡されます。逆に、ファイルへデータを出力するときも、ファイルオブジェクトを経由して行われます。

●標準入出力の使い方

通常のファイルは、ファイルオブジェクトを生成しないとアクセスすることはできません。ただし、標準入出力は Python の起動時にファイルオブジェクトが自動的に生成されるので、簡単に利用することができます。一般に、キーボードからの入力を「標準入力」、画面への出力を「標準出力」といいます。標準入出力に対応するファイルオブジェクトは、モジュール sys の変数に格納されています。表 1 に変数名を示します。

表 1 : 標準入出力
変数名ファイル
stdin 標準入力
stdout 標準出力
stderr 標準エラー出力

ファイルのアクセスは標準入出力を使うと簡単です。標準入力からデータを受け取る関数に input() と raw_input() があります。対話モードで input() を実行してみましょう。

>>> input()
'foo'
'foo'
>>> input()
1234
1234
>>> input()
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
>>> input()
{'foo': 10, 'bar': 20}
{'foo': 10, 'bar': 20}
>>> input( 'Number > ' )
Number > 100
100

input() は標準入力からデータを 1 行を読み込み、それを評価した結果を返します。文字列や数だけではなく、リストやディクショナリの入力も可能です。評価できないデータを入力するとエラーになります。また、引数に文字列を渡すと、それをプロンプトとして表示します。

次は対話モードで raw_input() を実行してみます。

>>> raw_input()
foo
'foo'
>>> raw_input()
foo bar baz
'foo bar baz'
>>> raw_input( 'Number > ' )
12345678
'12345678'

raw_input() は標準入力から 1 行読み込み、それを文字列にして返します。改行文字は取り除かれます。raw_input() もプロンプトを指定することができます。

データの出力は print 文で行うと簡単です。複数のデータを表示するときはカンマ (,) で区切ります。データとデータの間には空白が挿入されます。print はデータを出力したあと改行します。末尾にカンマを入れると、データを出力したあと改行されません。簡単な例を示します。

>>> for x in [1, 2, 3, 4, 5]:
        print x,


1 2 3 4 5
>>> for x in [1, 2, 3, 4, 5]:
        print x, x * x


1 1
2 4
3 9
4 16
5 25

それでは簡単な例として、データの合計値を求めるプログラムを作ります。リスト 10 を見てください。

リスト 10 : 合計値を求める (1)

total = 0
while True:
    a = input()
    if a < 0: break
    total += a
print 'total =', total

このプログラムをファイル test.py に保存してシェルで実行すると、次のようになります。

C>python test.py
1
2
3
4
5
-1
total = 15

数値をキーボードから入力します。-1 を入力すると break 文で while ループから脱出して合計値を表示します。このデータをファイル test.dat に保存してリダイレクトすれば、合計値を求めることができます。

C>python test.py < test.dat
15

1 行に複数のデータがある場合は、raw_input() で文字列として読み込み、データを分離して数値に変換します。列ごとに合計値を求めたい場合は、リスト 11 のようなプログラムになります。

リスト 11 : 合計値を求める (2)

total = [0]
while True:
    a = raw_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 =',
for x in total: print x,

リスト total に列ごとの合計値を求めます。raw_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() で追加します。関数 int() は文字列を整数値に変換する関数です。最後に total の内容を出力します。

それでは、リスト 11 をファイル test1.py に保存して、シェルで実行してみましょう。

C>python test1.py
1 2 3
4 5 6
7 8 9

total = 12 15 18

データをファイル test1.dat に保存してリダイレクトすることもできます。ファイルの終了を検出すると raw_input() は空文字列を返すので正常に動作します。

C>python test1.py < test1.dat
total = 12 15 18

結果をファイルに格納したい場合も、次のようにリダイレクトすれば簡単です。

C>python test1.py < test1.dat > total.dat

このように、標準入出力を使うと簡単にプログラムを作ることができます。

●for 文とファイルオブジェクト

ところで、ファイルを先頭から順番に 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 =',
for x in total: print x,

標準入力 stdin を使うため、最初に import 文でモジュール sys をインポートします。そして、for a in sys.stdin で標準入力から 1 行ずつ読み込み、変数 a にセットします。ファイルの終了を検出すると for 文の繰り返しが終了します。ファイル終了のチェックが不要になるので、プログラムが簡単になります。

ただし、このプログラムはファイルをリダイレクトする場合は正常に動作しますが、データをキーボードから入力すると正常に動作しません。ご注意ください。

●ファイルのアクセス方法

標準入出力を使わずにファイルにアクセスする場合、次の 3 つの操作が基本になります。

  1. アクセスするファイルをオープンする
  2. 入出力関数(メソッド)を使ってファイルを読み書きする。
  3. ファイルをクローズする。

「ファイルをオープンする」とは、アクセスするファイルを指定して、それと一対一に対応するファイルオブジェクトを生成することです。入出力関数は、そのファイルオブジェクトを経由してファイルにアクセスします。Python の場合、入出力関数はメソッドとして定義されています。

ファイルをオープンするには関数 open() を使います。オープンしたファイルは必ずクローズしてください。この操作を行うメソッドが close() です。

Python のファイル入出力機能は、C言語の標準ライブラリを用いて実装されているので、その仕様はC言語とほとんど同じです。最初に open() から説明します。

open(filename, mode)

open() は引数にファイル名 filename とアクセスモード mode を指定して、filename で指定されたファイルに対応するファイルオブジェクトを生成して返します。

アクセスモードは文字列で指定します。表 2 にアクセスモードを示します。

表 2 : アクセスモード
モード動作
r 読み込み (read) モード
w 書き出し (write) モード
a 追加 (append) モード

読み込みモードの場合、ファイルが存在しないとエラーになります。書き出しモードの場合、ファイルが存在すれば、そのファイルのサイズを 0 に切り詰めてからオープンします。追加モードの場合、ファイルの最後尾にデータを追加します。

+ を付加すると更新モードになり、読み書き両方が可能になります。b はバイナリモードを指定します。Windows のように、ファイルがテキストとバイナリの 2 種類に分かれている処理系ではバイナリモードが必要になります。

ファイル入出力用のメソッドを表 3 に示します。

表 3 : ファイル入出力用のメソッド
名前機能
read(size) size バイト読み込んで文字列にして返す
size を省略するとファイル全体を読み込む
readline() 1 行読み込んで文字列にして返す
readlines()ファイルのすべての行を読み込んでリストに格納して返す
write(s)文字列 s をファイルに書き込む
writelines(x)リスト x に格納された文字列をファイルに書き込む

簡単な例を示しましょう。

>>> 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 で出力します。このように、データをファイルに書き込み、ファイルからデータを読み込むことができます。

●コマンドライン引数の取得

Pyton の場合、モジュール sys の変数 argv にコマンドラインで与えられた引数が格納されています。リスト 13 を見てください。

リスト 13 : 変数 argv の表示

import sys
print  sys.argv

test.py は変数 argv の内容を表示するだけです。3 つの引数を与えて起動すると、次のように表示されます。

C> python test.py foo bar baz
['test.py', 'foo', 'bar', 'baz']

リストの先頭要素は test.py になることに注意してください。簡単な例として、リスト 12 のプログラムをファイル名を指定するように改造してみましょう。

リスト 14 : 合計値を求める(4)

import sys

total = [0]
in_f = open(sys.argv[1])
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))
in_f.close()
print 'total =',
for x in total: print x,

ファイル名はリスト argv[1] に格納されています。open(argv[1]) でファイルをオープンし、ファイルオブジェクトを変数 in_f にセットします。そして、for 文でファイルから 1 行読み込み、列ごとに数値の合計値を求めます。最後に、ファイルを close() でクローズして、for 文で列ごとの合計値を表示します。

●文字列のフォーマット操作

print はデータをそのまま出力しますが、整形して出力したい場合は文字列のフォーマット操作を使います。これはC言語の標準ライブラリ関数 printf() の書式文字列と同様の操作です。フォーマット操作の構文を示します。

書式文字列 % (data1, data2, ...)

書式文字列は出力に関する様々な指定を行います。書式文字列の後ろに % を書き、その後ろにデータを指定します。データが複数ある場合はタプルに格納します。一つしかない場合は、そのまま書いてもかまいません。

書式文字列はそのまま文字列として扱われますが、文字列の途中にパーセント % が表れると、その後ろの文字を変換指示子として理解し、書式文字列に与えられたデータをその指示に従って表示します。

簡単な例を示しましょう。

>>> '%d %o %x' % (100, 100, 100)
'100 144 64'

% の次の文字 d, x, o が変換指示子です。これらの指示子は整数値を表示する働きをします。例が示すように、d は 10 進数、x は 16 進数、o は 8 進数で表示します。変換指示子の個数と与えるデータの数が合わないとエラーになるので注意してください。% を出力したい場合は %% と続けて書きます。

それから、% と変換指示子の間にオプションでいろいろな設定を行うことができます。

  1. マップキー
  2. フラグ
  3. 最小フィールド幅
  4. 精度
  5. 変換修飾子

これらのオプションは変換指示子によって動作が異なる場合があります。1 は python 独自のオプションで、2 から 5 はC言語の書式文字列とほぼ同じです。これらのオプションは省略することができますが、順番を変更することはできません。簡単な例を示しましょう。

>>> '[%d]' % 10
'[10]'
>>> '[%4d]' % 10
'[  10]'
>>> '[%4d]' % 10000
'[10000]'

整数値を表示する変換指示子は、データを表示するフィールド幅を指定することができます。最初の例がフィールド幅を指定しない場合で、次の例がフィールド幅を 4 に指定した場合です。10 ではフィールド幅に満たないので、右詰めに出力されています。もし、フィールド幅に収まらない場合は、最後の例のように指定を無視して数値を出力します。

フィールド幅を 0 で埋めたい場合は、フラグに 0 を指定します。左詰めにしたい場合は、フラグに - を指定します。

>>> '[%04d]' % 10
'[0010]'
>>> '[%-4d]' % 10
'[10  ]'

r または s 変換指示子は Python の任意のデータを表示することができます。r 変換指示子はデータを関数 repr() で文字列に変換して表示します。repr(x) はデータ x を文字列に変換しますが、できれば評価可能な形式に変換します。str(x) はきれいに表示できる文字列に変換して返します。簡単な例を示します。

>>> a = 'hello, world\n'
>>> print '%s %r' % (a, a)
hello, world
'hello, world\n'

文字列 'hello, world\n' を %s で画面に表示すると、hello, world のあとに改行が行われまます。%r で表示すると、文字列の形式 'hello, world\n' で表示されます。

s, r 変換指示子の場合でも、フィールド幅を指定することができます。

>>> a = 'hello, world'
>>> print '[%20s] [%20r]' % (a, a)
[        hello, world] [      'hello, world']

>>> print '[%-20s] [%-20r]' % (a, a)
[hello, world        ] ['hello, world'      ]

フラグに - を指定すると左詰めにして出力されます。

マップキーはデータの指定にディクショナリのキーを使用する方法です。簡単な例を示しましょう。

>>> a = {'foo': 10, 'bar': 20, 'baz': 30}
>>> a
{'baz': 30, 'foo': 10,  'bar': 20}
>>> '%(foo)d, %(bar)d, %(baz)d' % a
'10, 20, 30'

この方法の場合、引数はタプルではなくディクショナリに格納してください。

このほかにも、浮動小数点数を表示する指示子など、文字列のフォーマット操作にはたくさんの機能があります。詳細は Python のマニュアルをお読みください。

●おわりに

関数の基本的な使い方とモジュール、ファイル入出力について説明しました。ところで、関数というと忘れてはならない機能に「再帰呼び出し」があります。再帰呼び出し(再帰定義)は敬遠されている方もいると思いますが、難しい話ではありません。再帰定義を使いこなすと、複雑なアルゴリズムでも簡単にプログラムを作ることができます。次回は再帰定義を中心に、関数について詳しく説明します。


●履歴


Copyright (C) 2006-2011 Makoto Hiroi
All rights reserved.

[ PrevPage | Python | NextPage ]