== 演算子と is 演算子

どっちも等しいかどうかの比較演算子だけど、簡単に言うと、== は値として等しいか(同値性)で、is はオブジェクトとして等しいか(同一性)の判定をする。だから次のようにaとbに別々に代入した場合では、値が等しくてもオブジェクトとしては等しくないので is での比較の結果は False になる。

>>> a = (1,2,3)
>>> b = (1,2,3)
>>> a == b
True
>>> a is b
False

id関数でオブジェクトのIDをみても、べつのオブジェクトだということがわかる。

>>> id(a)
5089160
>>> id(b)
4602696

一方で、bにaを代入した場合には、オブジェクトとしても等しいので、== でも is でも True になる。

>>> a = (1,2,3)
>>> b = a
>>> a == b
True
>>> a is b
True

ちなみに否定の場合は is not。

>>> a is b
True
>>> a is not b
False

randomモジュール

CodeIQの問題を解くのに random モジュールを調べたのでメモ。

cf. http://docs.python.jp/2.7/library/random.html

使った関数

random.randrange は指定された範囲の整数からランダムな整数を返す。

>>> import random
>>> random.randrange(1, 100)
51

random.sample は母集団のシーケンスから選ばれたリストをかえす。リストの長さを指定する。

>>> random.sample(range(1,101), 10)
[54, 3, 5, 58, 24, 18, 47, 57, 62, 40]

使ってない関数からいくつか

random.randint は整数a,bを指定しa≦N≦bであるような整数Nを返す。random.randrangeよりもこっちを使ったほうがよかったかも。

>>> random.randint(1,100)
20

random.choice は空でないシーケンスから要素をひとつ返す。

>>> random.choice(range(10))
8

random.shuffle はシーケンスxを直接かき混ぜる。

>>> x = range(10)
>>> x
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> random.shuffle(x)
>>> x
[6, 2, 3, 8, 5, 4, 1, 7, 9, 0]

random.random は値域[0.0,1.0)からランダムな浮動小数点数を返す。

>>> random.random()
0.7121347944194496

とりあえずこんなとこかな。

fileinputモジュール

コマンドライン引数で指定したファイルや標準入力からの入力を簡単に処理できるモジュール。
fileinput.input で各ファイルから1行ずつ読み込んでくれる。

次の例は、ファイルの各行をファイル名と行番号つきで出力する。

import fileinput

for line in fileinput.input():
    print fileinput.filename(), fileinput.filelineno(), ":", line.rstrip("\n")

実行例:

^o^ > python file_input.py fib.py file_read.py
fib.py 1 : def fib():
fib.py 2 :     a = 1
fib.py 3 :     b = 1
fib.py 4 :     while True:
fib.py 5 :         yield a
fib.py 6 :         a, b = b, a+b
fib.py 7 :
fib.py 8 :
fib.py 9 : i = fib()
fib.py 10 :
fib.py 11 : for c in range(10):
fib.py 12 :     print i.next()
fib.py 13 :
file_read.py 1 : import sys
file_read.py 2 :
file_read.py 3 : filename = sys.argv[1]
file_read.py 4 : print filename
file_read.py 5 : print "--"
file_read.py 6 :
file_read.py 7 : file = open(filename, "r")
file_read.py 8 : contents = file.read()
file_read.py 9 : print contents
file_read.py 10 : file.close()
file_read.py 11 :

標準入力へリダイレクトした場合:

^o^ > python file_input.py < fib.py
<stdin> 1 : def fib():
<stdin> 2 :     a = 1
<stdin> 3 :     b = 1
<stdin> 4 :     while True:
<stdin> 5 :         yield a
<stdin> 6 :         a, b = b, a+b
<stdin> 7 :
<stdin> 8 :
<stdin> 9 : i = fib()
<stdin> 10 :
<stdin> 11 : for c in range(10):
<stdin> 12 :     print i.next()
<stdin> 13 :

パイプを使った場合:

^o^ > type fib.py | python file_input.py
<stdin> 1 : def fib():
<stdin> 2 :     a = 1
<stdin> 3 :     b = 1
<stdin> 4 :     while True:
<stdin> 5 :         yield a
<stdin> 6 :         a, b = b, a+b
<stdin> 7 :
<stdin> 8 :
<stdin> 9 : i = fib()
<stdin> 10 :
<stdin> 11 : for c in range(10):
<stdin> 12 :     print i.next()
<stdin> 13 :

Rubyで点数を集計するとき、あなたはどうしてますか? をPythonで

Python でやってみた。

cf. Rubyで点数を集計するとき、あなたはどうしてますか? - hp12c

import re

class Score:
    def __init__(self, name, a, b):
        self.name = name
        self.a = a
        self.b = b

    def total(self):
        return self.a + self.b

data ="""player gameA gameB
Bob 20 56
Ross 68 33
Bob 78 55
Kent 90 15
Alice 84 79
Ross 10 15
Jimmy 80 31
Bob 12 36
Kent 88 43
Kent 12 33
Alice 90 32
Ross 67 77
Alice 56 92
Jimmy 33 88
Jimmy 11 87"""

a = re.split("\n", data)
headers = re.split("\s+", a[0])
values = map((lambda x: re.split("\s+", x)), a[1:])

scores = {}
for s in values:
    player = s[0]
    if player in scores:
        scores[player].a += int(s[1])
        scores[player].b += int(s[2])
    else:
        scores[player] = Score(player, int(s[1]), int(s[2]))

lanking = scores.values()
lanking.sort(lambda a,b: cmp(b.total(), a.total()))

print "%s\t%s\t%s\ttotal" % tuple(headers)
for player in lanking:
    print "%s\t%d\t%d\t%d" % (player.name, player.a, player.b, player.total())

実行結果:

^o^ > python game_score.py
player  gameA   gameB   total
Alice   230     203     433
Jimmy   124     206     330
Kent    190     91      281
Ross    145     125     270
Bob     110     147     257

Pythonで偽と見なされる値

Python のライブラリリファレンス5.1. 真理値テストには、次の値が偽と判定されると書いてある。

  • None
  • False
  • 数値におけるゼロ。例えば 0, 0L, 0.0, 0j 。
  • 空のシーケンス型。例えば ”, (), [] 。
  • 空のマッピング型。例えば {} 。
  • __nonzero__() または __len__() メソッドが定義されているようなユーザ定義クラスのインスタンスで、それらのメソッドが整数値ゼロまたは bool 値の False を返すとき。

そこで、まず次のようなスクリプトを書いて確かめてみた。

def istrue(val):
    if val:
        print "true"
    else:
        print "false"

print "number 0:", istrue(0)

print "empty string \"\":", istrue("")

print "empty list []:", istrue([])

print "empty tuple ():", istrue(())

print "empty dictionary {}:", istrue({})

print "False:", istrue(False)

print "None:", istrue(None)

実行:

^o^ > python false.py
number 0: false
empty string "": false
empty list []: false
empty tuple (): false
empty dictionary {}: false
False: false
None: false

予想通りすべて false を返した。

もうひとつ、__nonzero__メソッドや__len__メソッドを持ったオブジェクトで試してみよう。Fooクラスは真になり、Barクラスは偽になるようにしてみる。

class Foo:
    def __nonzero__(self):
        return True

class Bar:
    def __len__(self):
        return 0

def istrue(val):
    if val:
        print "true"
    else:
        print "false"

print "Foo:", istrue(Foo())

print "Bar:", istrue(Bar())

実行:

^o^ > python true_or_false.py
Foo: true
Bar: false

これも予想通り、Foo は true を返し、Bar は false を返した。

ところで、__nonzero__ と __len__ の両方を持っていて、返す値が矛盾する場合はどうなるんだろう。Bazクラスは__nonzero__ に対しては True を返し、__len__ に対しては 0 を返す。

class Baz:
    def __nonzero__(self):
        return True

    def __len__(self):
        return 0

def istrue(val):
    if val:
        print "true"
    else:
        print "false"

print "Baz:", istrue(Baz()) 

実行:

^o^ > python true_or_false2.py
Baz: true

結果は true だった。どうやら __nonzero__ のほうが優先するみたいだ。

エラトステネスの篩

Ruby でやってる人がいたので Python でやってみた。

cf. エラトステネスの篩 - Mae向きな日記

以前 Haskell でやったことがあるんだけどすっかり忘れててちょっと苦労したよ。
最初に書いたのはこのコード。1,000,000までの素数を列挙している。素直というか素朴というか力技。

def prime_list(n):
    lis = range(1, n + 1, 2)
    lis[0] = 2
    while True:
        if len(lis) == 0:
            break
        p = lis.pop(0)
        yield p
        lis = [x for x in lis if x % p != 0]


primes = prime_list(1000000)

for i in primes:
    print i

ところが答えはあってるみたいだけどやたらと遅い。正確には計ってないけど4分半くらいかかる。
で、以前の Haskell のコードを見ながら改良したのがこれ。

def prime_list(n):
    limit = int(n ** 0.5) + 1
    lis = range(1, n + 1, 2)
    lis[0] = 2
    while True:
        if len(lis) == 0:
            break
        p = lis.pop(0)
        yield p
        if p <= limit:
            lis = [x for x in lis if x % p != 0]


primes = prime_list(1000000)

for i in primes:
    print i

篩にかけるのを1,000,000の二乗根までに限っている。それ以上は篩にかけても意味がないからね。これを思い出すまでに時間がかかってしまった。 で、結果はといえば劇的に速くなって約4秒程度。こんなに違うとは。

ファイルの文字数、ワード数、行数を数える

というのをやってる人がいたので、やってみた。

cf. 何とかPython版完成 - しんちゃんの日記

import sys

def count(str):
    words = str.split()
    word_cnt = len(words)
    char_cnt = len(str)
    lines = str.split("\n")
    line_cnt = len(lines)
    if lines[-1] == '':
        line_cnt -= 1
    return char_cnt, word_cnt, line_cnt

for file in sys.argv[1:]:
    content = open(file, "r").read()
    char_count, word_count, line_count = count(content)
    print "chars =", char_count, " words =", word_count, " lines =", line_count, " filename =", file

実行:

^o^ > mywc.py mywc.py
chars = 466  words = 59  lines = 19  filename = mywc.py

wcコマンドと比べてみる。

^o^ > wc mywc.py
 19  59 485 mywc.py

なんか文字数が違うな。ちょうど行数の分だけ違う、ってことは改行文字のせいか?
ためしに mywc.py の改行をCR+LFからLFだけに変えてみと:

^o^ > wc mywc.py
 19  59 466 mywc.py

文字数も合った。wcコマンドはCR+LFを2文字と数えて、(少なくともWindowsの)Pythonは1文字と数えるみたいだ。

正規表現をつかう

Python の正規表現は Ruby や Perl のように組み込みではなくて、reモジュールとして提供されている。使い方は2通りあって、ひとつはreモジュールの関数を使うやり方、もうひとつはre.compile関数で正規表現オブジェクトを作って使うやり方だ。

reモジュールの関数を使う

ここでは関数を使うやり方を試してみよう。

re.findall は文字列中で正規表現にマッチする部分すべてをリストとして返す。

>>> re.findall("a.", "abcacbcba")
['ab', 'ac']

re.split は文字列を分割する。

>>> re.split("b", "abcacbcba")
['a', 'cac', 'c', 'a']

re.sub は正規表現にマッチする部分を置き換える。

>>> re.sub("a", "A", "abcacbcba")
'AbcAcbcbA'

re.search は文字列中にマッチする部分を見つけて、マッチオブジェクトを返す。最初に見つかって部分しか返ってこないみたい。

>>> m = re.search("b.", "abcacbcba")
>>> m.group()
'bc'

re.match は re.serch と同様だけど、文字列の先頭だけを対象とする。マッチオブジェクトを返す。

>>> m = re.match("a.", "abcacbcba")
>>> m.group()
'ab'

先頭にマッチしない場合は m.group() がエラーになる。

>>> m = re.match("b.", "abcacbcba")
>>> m.group()
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: 'NoneType' object has no attribute 'group'

re.finditer はマッチオブジェクトを返すイテレータを返す。

>>> for m in re.finditer("a.", "abcacbcba"):
...     print m.group()
...
ab
ac

正規表現オブジェクトを使う

同じ正規表現を何度も使う場合は、正規表現オブジェクトを作ったほうが速度的に有利みたい。正規表現オブジェクトを作るには re.compile 関数を使う。

>>> r = re.compile("a.")
>>> r.findall("abcacbcba")
['ab', 'ac']

r が正規表現オブジェクト。正規表現オブジェクトには reモジュールの関数と同じ動作をするメソッドがあって、使い方も同じ(引数に正規表現パターンがない以外は)。

マッチオブジェクト

search や match はマッチオブジェクトを返す。マッチオブジェクトには、マッチした文字列やサブグループの情報が保存されている。

>>> r = re.compile("a(..)a(..)")
>>> m = r.match("abcacbcba")
>>> m.groups()
('bc', 'cb')
>>> m.group()
'abcacb'
>>> m.group(0)
'abcacb'
>>> m.group(1)
'bc'
>>> m.group(2)
'cb'

これはたぶん後方参照やなんかに使えるんだろう。

MD5ハッシュを計算する

今回は小ネタ。md5モジュールを使って MD5ハッシュを計算する。

import md5
import sys

file = sys.argv[1]
content = open(file, "rb").read()

m = md5.new(content)
print m.hexdigest()

実行例:

^o^ > python mkmd5.py sample.txt
26b2953c0dd7a8b6052c8f76385ee5c4

以前 Ruby で作ったスクリプトでチェックしてみる。

^o^ > python mkmd5.py sample.txt > sample.txt.md5

^o^ > type sample.txt.md5
26b2953c0dd7a8b6052c8f76385ee5c4

^o^ > chkmd5.rb sample.txt.md5
valid:      sample.txt

OKみたいだ。

CSVファイルを読み書きする

CSVファイルの読み込み

CSVファイルを読み書きするには csv モジュールを使う。

csv.reader関数は、CSVファイルを読み込むオブジェクトを返す。これを使って1行ずつ処理するわけだ。次の例は、名前とメールアドレスからなるCSVファイルを読み込んで、変換して出力する。

import csv

csvfile = open("mail.csv", "r")
reader = csv.reader(csvfile)
for row in reader:
    print '"%s" <%s>' % (row[0], row[1])

実行:

^o^ > type mail.csv
Andy,[email protected]
Bill,[email protected]
Charlie,[email protected]

^o^ > python csv_read.py
"Andy" <[email protected]>
"Bill" <[email protected]>
"Charlie" <[email protected]>

CSVファイルの書き込み

csv.reader があれば csv.writer もある。csv.writer関数は、CSVファイルに書き込むオブジェクトを返す。そして writerowメソッドで1行ずつ書き込むって感じだ。

import csv

data = [("Andy", "[email protected]"),
        ("Bill", "[email protected]"),
        ("Charlie", "[email protected]")]

csvfile = open("sample.csv", "wb")
writer = csv.writer(csvfile)
for d in data:
    writer.writerow(d)

上の例では書き込むデータとしてタプルのリストを用意しているけど、リストのリストでもいいらしい。注意点としては、書き込み用ファイルをopenするときにバイナリモード “wb” で開くこと。そうでないと余計な改行が出力されてしまう。
実行:

^o^ > python csv_write.py

^o^ > type sample.csv
Andy,[email protected]
Bill,[email protected]
Charlie,[email protected]

その他細かいことは省略

実はCSVファイルには標準というものがない。値がクオートしてあるとかないとか、そういう細かい違いがソフトごとにある。そういう違いを吸収するために、csv.reader や csv.writer には dialect とかその他のパラメータがあるんだけど、ここでは省略する。
詳しい情報はこのあたり。

cf. http://docs.python.jp/2.7/library/csv.html

とりあえず Excel 互換の CSV なら上の例のような簡単な書き方で大丈夫みたいだ。