リスト内包表記

Pythonにはリスト内容表記がある。for~in~を使ってこうやって書く:

>>> [x*x for x in [1,2,3,4,5]]
[1, 4, 9, 16, 25]

表記こそ違うけどやってることはHaskellのリスト内包表記とおなじだ。

forは複数つけることもできる:

>>> [(x,y) for x in [1,2,3,4,5] for y in [1,2,3,4,5]]
[(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5),
 (3, 1), (3, 2), (3, 3), (3, 4), (3, 5), (4, 1), (4, 2), (4, 3), (4, 4), (4, 5),
 (5, 1), (5, 2), (5, 3), (5, 4), (5, 5)]

これは次の二重のforと同じ。

>>> r = []
>>> for x in [1,2,3,4,5]:
...     for y in [1,2,3,4,5]:
...         r.append((x,y))
...
>>> r
[(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5),
 (3, 1), (3, 2), (3, 3), (3, 4), (3, 5), (4, 1), (4, 2), (4, 3), (4, 4), (4, 5),
 (5, 1), (5, 2), (5, 3), (5, 4), (5, 5)]

さらに、ifを使って条件を制限できる:

>>> [(x,y) for x in [1,2,3,4,5] for y in [1,2,3,4,5] if x <= y]
[(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 2), (2, 3), (2, 4), (2, 5), (3, 3),
 (3, 4), (3, 5), (4, 4), (4, 5), (5, 5)]

書き換え可能オブジェクトを関数の引数のデフォルト値にするときの注意点

Pythonの関数の引数にはデフォルト値を設定することができて、デフォルト値が設定されている引数は呼び出し時に省略することができる。それはいいんだけど、そのデフォルト値に書き換え可能オブジェクトを設定したときには注意が必要だ。関数の中でこの引数を書き換えてしまうと、奇妙なことが起こる。たとえば次のようなコードの場合だ。

def foo(a = []):
    a.append("bar")
    print a

foo()
foo()
foo()
foo([1,2,3])
foo()

関数fooは省略可能な引数aをとり、aが省略されたときには空のリストを代入するようになっている。ところが、Pyhtonでは引数のデフォルト値の代入は関数が初めて呼び出されたときだけにおこり、しかも以後その値(オブジェクト)が引き継がれる。実行してみよう:

^o^ > python default_arg.py
['bar']
['bar', 'bar']
['bar', 'bar', 'bar']
[1, 2, 3, 'bar']
['bar', 'bar', 'bar', 'bar']

関数fooを全部で5回呼び出してるけど、引数を与えているのは4回目だけでほかは省略している。1回目の呼び出しの結果は簡単に予想できるとおりだ。省略された引数の変わりに空のリストが使われて、関数の中で’bar’が追加されている。2回めの呼び出しの結果は予想と違う。予想では1回目と同じ結果になりそうなものなのに、’bar’が2つある。これは、前回呼び出されたときのオブジェクトを使いまわしているせいだ。1回目の呼び出しで’bar’を1つ追加し、2回目の呼び出しでもう1つ追加している。結果、’bar’が2つあるってわけ。3回目の呼び出しも同様。4回目の呼び出しでは引数を与えているので予想通りの結果になっているけど、5回目の呼び出しではやっぱり’bar’が増えている。

これはないわぁ。何でこんな仕様になってるのか知らないけど、これを避けるためには実質的に引数を省略しないことしかないんじゃないかな。これはうれしくないないなぁ。

変数と書き換え可能オブジェクト

Pythonの代入は、データに名前を紐付けることだ。Rubyとおんなじだな。だから、次のように代入した場合、変数l1とl2は同じリストオブジェクトに紐づいている。

>>> l1 = [1,2,3,4,5]
>>> l2 = l1

だからl1を変更すると、同時にl2も変更されたことになる。

>>> l1.append(6)
>>> l1
[1, 2, 3, 4, 5, 6]
>>> l2
[1, 2, 3, 4, 5, 6]

これは書き換え可能オブジェクトに共通する問題。書き換え可能オブジェクトとはリスト、辞書、setなど。一方で数値や文字列は書き換え可能ではないので、こういう問題は起きない(操作すると必ず新しいオブジェクトが作られる)。

この問題を回避するにはオブジェクトのコピーを作ればいい。コピーをつくるにはcopyモジュールのcopy関数を使う。

>>> import copy
>>> l1 = [1,2,3,4,5]
>>> l2 = copy.copy(l1)
>>> l1.append(6)
>>> l1
[1, 2, 3, 4, 5, 6]
>>> l2
[1, 2, 3, 4, 5]

ところで、文字列が書き換え可能ではないというのは、Rubyとは違う点だ。たとえばリストではインデックスを利用した代入ができるが、

>>> l = [1,2,3,4,5]
>>> l[0] = 0
>>> l
[0, 2, 3, 4, 5]

文字列ではできない。

>>> s = "abcde"
>>> s[0] = "A"
Traceback (most recent call last):
  File "", line 1, in 
TypeError: 'str' object does not support item assignment

ファイルの入出力

ファイルのopenとclose

ファイルをオープンするにはopen関数、クローズするにはcloseメソッドを使う。

f = open("sample.txt", "r")
(何らかの処理)
f.close()

openの戻り値はファイルオブジェクトで、ファイルへの入出力にはこのファイルオブジェクトを使う。
2番目の引数はモードで次のモードが指定できる。

r 読み込みモード
w 書き込みモード。既存のファイルには上書きする
a 追加書き込みモード

ほかにもあるけど省略。

ファイルの読み込み

ファイルの内容すべてをいっぺんに読み込むには、readメソッド。

import sys

filename = sys.argv[1]

file = open(filename, "r")
contents = file.read()
print contents
file.close()

実行結果:

^o^ > type sample.txt
Mon
Tue
Wed
Thu
Fri
Sat
Sun

^o^ > python file_read.py sample.txt
Mon
Tue
Wed
Thu
Fri
Sat
Sun


ファイルを1行ずつ読み込むには、readlineを使う。

import sys

filename = sys.argv[1]

file = open(filename, "r")
while True:
    line = file.readline()
    if not line:
        break
    print line.rstrip("\n")

file.close()

whileループの中で1行ずつ読み込んで出力している。ファイルの最後に到達すると空文字列が返ってくるんだろうか、そこでbreakしてループを抜け出している。
あと、print文は最後に改行を出力するので、lineの最後についている改行文字をrstripで取り去っている。このprintの改行を出力する動作はおせっかいに思えてならない。

readlinesはファイルすべてを行ごとに分割して、文字列のリストを返す。

import sys

filename = sys.argv[1]

file = open(filename, "r")
for line in file.readlines():
    print line.rstrip("\n")

file.close()

大きなファイルでなければこの方がすっきり書ける。

さらに、readlinesを使わなくてもファイルオブジェクト自体をforに渡すことで、1行ずつ処理することができる。このやり方だと大きなファイルでも大丈夫みたい。

import sys

filename = sys.argv[1]

file = open(filename, "r")
for line in file:
    print line.rstrip("\n")

file.close()

ファイルへの書き込み

ファイルへの書き込みは、writeメソッド。

import sys

filename = sys.argv[1]

file = open(filename, "w")
file.write("Hello, world.\n")
file.close()

実行結果:

^o^ > python file_write.py sample.out
^o^ > type sample.out
Hello, world.

writeメソッドは余計な改行文字を出力しないから素直でいいね。

writelinesは文字列のリストを受け取って、ファイルに出力する。

import sys

filename = sys.argv[1]

seq = ["foo\n", "bar\n", "baz\n"]

file = open(filename, "w")
file.writelines(seq)
file.close()

実行結果:

^o^ > python file_writelines.py sample.out
^o^ > type sample.out
foo
bar
baz

mapとreduce

map

まずは標準的な使い方。

>>> map((lambda x: x ** 2), [1,2,3,4,5])
[1, 4, 9, 16, 25]

面白いのは、引数にリストを複数とれること。この場合、Haskell の zipWith 相当の動作をする。

>>> map((lambda x,y: x + y), [1,2,3,4,5], [10,20,30,40,50])
[11, 22, 33, 44, 55]

リスト3つでもいける。

>>> map((lambda x,y,z: x * y + z), [1,2,3,4,5], [6,7,8,9,10], [10,20,30,40,50])
[16, 34, 54, 76, 100]

いくつまでいけるのかは確認してない。

reduce

mapときたらreduce。injectでもfoldlでもなくreduce。何でこう、畳み込み関数って言語によって名前が違うんだろう。

>>> reduce((lambda x,y: x * y), [1,2,3,4,5], 1)
120

第3引数は省略可能で、そのときはリストの最初の値が初期値になるみたい。

>>> reduce((lambda x,y: x * y), [1,2,3,4,5])
120

この例の場合だと結果は同じだ。

追記:こういう例のほうが初期値の有無による動作の違いがわかりやすい:

>>> reduce((lambda x,y: (x,y)), [1,2,3,4,5], 0)
(((((0, 1), 2), 3), 4), 5)
>>> reduce((lambda x,y: (x,y)), [1,2,3,4,5])
((((1, 2), 3), 4), 5)

関数に関数を渡す

リストのsortメソッドには、比較用の関数を渡すことができる。
まずは何も渡さない場合:

a = ["abc", "def", "BCD", "EFG"]

a.sort()
print a
^o^ > python sort.py
['BCD', 'EFG', 'abc', 'def']

比較用の関数を渡さないと、ASCII順にソートされている。次は小文字に変換してから比較する関数を渡してみる。

a = ["abc", "def", "BCD", "EFG"]

def cmp_lower(a, b):
    return cmp(a.lower(), b.lower())

a.sort(cmp_lower)
print a

実行結果:

^o^ > python sort2.py
['abc', 'BCD', 'def', 'EFG']

大文字小文字を無視した順になった。

こんな風に関数に関数を渡すことができる。Pythonでは関数は第一級のオブジェクトだったことだな。

じゃあ、関数を返すことはできるのか。

def foo():
    print "foo"
    def bar():
       print "bar"
    return bar

bar = foo()
bar()

foo関数のなかでbar関数を定義して、それをreturnしている。実行すると:

^o^ > python return_function.py
foo
bar

予想通りの結果になった。最初の出力 foo はfoo関数の実行中に出力されたもの。次の bar はfoo関数から返ってきた関数を呼び出したときに出力されたものだ。

Pythonでは関数に関数を渡すことも、関数を返すこともできる。

forループ

forループは、「for 変数 in シーケンス」という形をしている。シーケンスとはリスト、タプル、文字列の総称みたいなもの。

リストでの例:

>>> for i in [1,2,3,4,5]:
...     print str(i) + ":" + "Hello" * i
...
1:Hello
2:HelloHello
3:HelloHelloHello
4:HelloHelloHelloHello
5:HelloHelloHelloHelloHello

タプルの例:

>>> for t in ('Foo', 'Bar', 'Baz'):
...     print t
...
Foo
Bar
Baz

文字列でもいける:

>>> for c in "abcde":
...     print c
...
a
b
c
d
e

setだとどうか:

>>> for c in set(['a', 'b', 'c']):
...     print c
...
a
c
b

いけた。

if ~ elif ~ else

Python での条件分岐。これも関数と同じくブロックをインデントであらわしている。
例は引数の大小で出力が変化する。

import sys

num = int(sys.argv[1])

if num < 10:
    print "smaller than 10."
elif 10 < num:
    print "bigger than 10."
else:
    print "just 10."

引数は文字列なのでint関数を使って整数に変換している。

実行結果:

^o^ > python if.py 3
smaller than 10.

^o^ > python if.py 16
bigger than 10.

^o^ > python if.py 10
just 10.

コマンドライン引数

スクリプトのコマンドライン引数は、sys モジュールの argv に格納されている。

import sys

print sys.argv

実行結果:

^o^ > python argv.py foo bar baz
['argv.py', 'foo', 'bar', 'baz']

見てのとおり、sys.argv の最初の要素はスクリプト名で、コマンドライン引数は2番目以降。Rubyと違うのでちょっと注意だな。