Hatena::ブログ(Diary)

kobakoba0723の日記

2011-11-24

エキスパートPythonプログラミング

気付けば今年も後ひと月ちょっと、ここのところ会社滞在時間が延びてるなぁ。。。

Anacondaとかvirt-installとかPythonのコードを眺める機会も増えてきた。

まだまだ訳分からないけど、ちょっとずつでも読み進められるようになろう。

シーケンスの要素とインデックスを同時に

enumerate関数を使って。

>>> seq = ["one", "two", "three"]
>>> for i, element in enumerate(seq):
...     seq[i] = '%d: %s' % (i, element)
... 
>>> seq
['0: one', '1: two', '2: three']
>>> 

同じことを関数とリスト内包表記で実現できてしまうらしい

>>> def _treatment(pos, element):
...     return '%d: %s' % (pos, element)
... 
>>> seq = ["one", "two", "three"]
>>> [_treatment(i, el) for (i, el) in enumerate(seq)]
['0: one', '1: two', '2: three']
>>> 

おお〜、すごい。関数化してあるので、なんか読みやすい。

ジェネレータ

の前にイテレータ

次の要素を返すnext()とイテレータ自身を返す__iter__()を持つクラスとして実現できる。

イテレータクラスのオブジェクトをforループの要素に渡すと以下の順で実行される

  1. __init__
  2. __iter__
  3. next() : StopIterationが出るまで繰り返し

pdbモジュールを使ってステップ実行すると上記順で実行されているのが見える。

で、肝心のジェネレータ。

イテレータプロトコルに対応したもので、必要なデータを必要な時に生成して返してくれるので

大量のデータを処理したりする時にも省メモリ

関数(メソッド)をジェネレータにするには、復帰値を返す処理に yield ステートメントを使う

[kobakoba0723@fc15-x64 ~]$ cat generate.py 
def power(seq):
    print("seq in power: ", seq)
    for value in seq:
        print('powering %s' % value)
        yield value ** 2


def adder(gen_obj):
    print("gen_obj in adder: ", gen_obj)
    for value in gen_obj:
        print('adding to %s' % value)
        if value % 2 == 0:
            yield value + 3
        else:
            yield value + 2


elements = [1, 4, 7, 9, 12, 19]
res = adder(power(elements))
print next(res)
print next(res)
[kobakoba0723@fc15-x64 ~]$ python generate.py 
('gen_obj in adder: ', <generator object power at 0x1e7d640>)
('seq in power: ', [1, 4, 7, 9, 12, 19])
powering 1
adding to 1
3
powering 4
adding to 16
19
[kobakoba0723@fc15-x64 ~]$ 

ジェネレータもイテレータプロトコルの一種なのでnext()を実行すると値の生成/送出が行われる。

ジェネレータ関数を入れ子にしてあるので、adder()はジェネレータオブジェクトを受け取る。

メインの処理で、nextを実行するとpower()のyieldが実行され、adder()のジェネレータオブジェクトから値が送出される

for文は、StopIterationが挙がるまでnext()を実行するので、"print next(res)"を7回実行すると、

adder()のfor文でStopIteration例外が発生しプログラムが終了する

[kobakoba0723@fc15-x64 ~]$ python generate.py 
('gen_obj in adder: ', <generator object power at 0xa83640>)
('seq in power: ', [1, 4, 7, 9, 12, 19])
powering 1
adding to 1
3
... snip ...
powering 19
adding to 361
363
Traceback (most recent call last):
  File "generate.py", line 26, in <module>
    print next(res)
StopIteration
[kobakoba0723@fc15-x64 ~]$ 

ジェネレータには、next()の他に

  • send():yield に値を渡すことができ、yieldはsend()から渡された値を返す
  • throw():任意の例外をyield に対して渡す
  • close():GeneratorExit例外を yield に渡す

コルーチン

複数の箇所で実行を一時停止したり、再開したり出来る機能をもった関数

PEP342に例が載っているから後で読んでみよう。

それと、本にはエコーサーバサーバ側のコードが載ってるから、

クライアントのコードを書いて試してみよう。socketをどうやって使うか、からだな、まずは


itertools

一般的な使用パターンのイテレータを作れるようにしたモジュール

標準ライブラリリファレンス 9.7 itertools 効率的なループ実行のためのイテレータ生成関数

以下を覚えておけばよいらしい

  • islice:シーケンスのサブグループに対するイテレータを返す
  • tee:1つのシーケンスから複数のイテレータを返す
  • groupby:シーケンス内の連続した要素をグループ化する(イテレータを返すのか???)

ここから下は、generate.pyのトレース

続きを読む

2011-07-27

Pythonクックブック(4章)

条件に応じて関数呼び出し

Pythonでは、swith~caseは使えないのでif~elif~elseで条件分岐を作って、関数呼び出しが自分で考えつく方法

ディクショナリとget()を使うと、条件分岐を作らなくても実現可能。とっても見やすい。

if~elif~ 相当をやっているのが、get()でディクショナリのキーがヒットした時で、

else 相当をやっているのが、get()でキーがヒットしないとき

[kobakoba0723@fedora13-intel64 ~]$ cat func_dict.py 
animals = []
number_of_felines = 0


def deal_with_others():
    print "No types"


def deal_with_a_cat():
    global number_of_felines
    print "meow"
    animals.append('feline')
    number_of_felines += 1


def deal_with_a_dog():
    print "bark"
    animals.append('canine')


def deal_with_a_bear():
    print "watch out for the HUG"
    animals.append('ursine')


tokenDict = {
    "cat": deal_with_a_cat,
    "dog": deal_with_a_dog,
    "bear": deal_with_a_bear,
    }

words = ['cat', 'dog', 'lion', 'bear', 'snake', 'cats']
for word in words:
    tokenDict.get(word, deal_with_others)()
nf = number_of_felines
print 'we met %d felines%s' % (nf, 's'[nf == 1:])
[kobakoba0723@fedora13-intel64 ~]$ python func_dict.py 
meow
bark
No types
watch out for the HUG
No types
No types
we met 1 felines
[kobakoba0723@fedora13-intel64 ~]$ 

getを使わずに、tokenDict[word]とやりたい場合は、

その前にキーが存在するか word in tokenDict でチェックすれば出来るが、

条件分岐が生じるので今のままの方が解りよい。

位置引数キーワード引数の渡し方。

関数側で受け取った位置引数(*args), キーワード引数(**kwargs)をさらに別の関数に渡す時は、*args, **kwargsで渡す

[kobakoba0723@fedora13-intel64 ~]$ cat func_args.py
def func(*args, **kwargs):
    print type(args)
    print type(kwargs)
    print "'a' in subroutine", args
    print "'b' in subroutine", kwargs
    print 'args is same as a ?', a is args
    print 'kwargs is same as b ?', b is kwargs


a = (1, 2, 3)
b = {'a': 1, 'b': 2, 'c': 3}
print "'a' in main route", a
print "'b' in main route", b
func(*a, **b)
[kobakoba0723@fedora13-intel64 ~]$ python func_args.py
'a' in main route (1, 2, 3)
'b' in main route {'a': 1, 'c': 3, 'b': 2}
<type 'tuple'>
<type 'dict'>
'a' in subroutine (1, 2, 3)
'b' in subroutine {'a': 1, 'c': 3, 'b': 2}
args is same as a ? False
kwargs is same as b ? False
[kobakoba0723@fedora13-intel64 ~]$ 

呼び出し側と呼び出される側で、引数の参照先が共有されているかと思っていたがそうではない。

ただし、今後ずっと『共有されない』という保証はどこにもない(仕様として定義されていない)

参考サイト

キーの確認(in演算子, has_keyメソッド)@PythonWeb

始めてのPython(16章)@kobakoba0723の日記

2011-07-18

Pythonクックブック(4章)

ディクショナリの部分集合作成

前回dict()とzip()を使って、ディクショナリを作ったが、ちゃんと理解出来てなかったので復習。

これがよくわかってなかったので今日やった部分集合作成がチンプンカンプンだった。

まず、マップ型のヘルプを見ると、以下のようになっていて、

ビルドインクラスのdictに対して、タプルのシーケンスを渡せば良い。

class dict(object)
 |  dict(seq) -> new dictionary initialized as if via:
 |      d = {}
 |      for k, v in seq:
 |          d[k] = v

次にzipメソッドのヘルプを見ると、以下のようになっており、

シーケンスを渡すと、受け取ったシーケンスの各要素を要素とするタプルのリストを返す。

zip(...)
    zip(seq1 [, seq2 [...]]) -> [(seq1[0], seq2[0] ...), (...)]
    
    Return a list of tuples, where each tuple contains the i-th element
    from each of the argument sequences.  The returned list is truncated
    in length to the length of the shortest argument sequence.

なので、dict(zip(key_seq, value_seq)) で

keyからなるリストとvalueからなるリストを受け取って、マップオブジェクトをと生成できる。


復習も出来たので部分集合作成。

作成する部分集合に含まれるキーと元のマップオブジェクトを渡すことで生成

[kobakoba0723@fedora13-intel64 ~]$ cat sub_dict.py 
def sub_dict(whole_dict, part_key, default=None):
    return dict([(key, whole_dict.get(key, default)) for key in part_key])

if __name__ == '__main__':
    color_key = ['red', 'green', 'blue']
    color_value = [(255, 0, 0), (0, 255, 0), (0, 0, 255)]
    color_map = dict(zip(color_key, color_value))
    print sub_dict(color_map, ('red', 'green'))
[kobakoba0723@fedora13-intel64 ~]$ python sub_dict.py 
{'green': (0, 255, 0), 'red': (255, 0, 0)}
[kobakoba0723@fedora13-intel64 ~]$ 

sub_dictメソッドのdict([...])をdict(...)にすることでジェネレータを使うことが出来る。

kobakoba0723@fedora13-intel64 ~]$ cat sub_dict.py 
def sub_dict(whole_dict, part_key, default=None):
    return dict((key, whole_dict.get(key, default)) for key in part_key)

if __name__ == '__main__':
    color_key = ['red', 'green', 'blue']
    color_value = [(255, 0, 0), (0, 255, 0), (0, 0, 255)]
    color_map = dict(zip(color_key, color_value))
    print sub_dict(color_map, ('red', 'green'))
[kobakoba0723@fedora13-intel64 ~]$ python -m pdb sub_dict.py 
> /home/kobakoba0723/sub_dict.py(1)<module>()
-> def sub_dict(whole_dict, part_key, default=None):
(Pdb) s
> /home/kobakoba0723/sub_dict.py(4)<module>()
-> if __name__ == '__main__':
~ 省略 ~
(Pdb) 
> /home/kobakoba0723/sub_dict.py(8)<module>()
-> print sub_dict(color_map, ('red', 'green'))
(Pdb) 
--Call--
> /home/kobakoba0723/sub_dict.py(1)sub_dict()
-> def sub_dict(whole_dict, part_key, default=None):
(Pdb) 
> /home/kobakoba0723/sub_dict.py(2)sub_dict()
-> return dict((key, whole_dict.get(key, default)) for key in part_key)
(Pdb) 
--Call--
> /home/kobakoba0723/sub_dict.py(2)<genexpr>()
-> return dict((key, whole_dict.get(key, default)) for key in part_key)
(Pdb) 
> /home/kobakoba0723/sub_dict.py(2)<genexpr>()
-> return dict((key, whole_dict.get(key, default)) for key in part_key)
(Pdb) 
--Return--
> /home/kobakoba0723/sub_dict.py(2)<genexpr>()->('red', (255, 0, 0))
-> return dict((key, whole_dict.get(key, default)) for key in part_key)
(Pdb) 
--Call--
> /home/kobakoba0723/sub_dict.py(2)<genexpr>()->('red', (255, 0, 0))
-> return dict((key, whole_dict.get(key, default)) for key in part_key)
(Pdb) 
> /home/kobakoba0723/sub_dict.py(2)<genexpr>()->('red', (255, 0, 0))
-> return dict((key, whole_dict.get(key, default)) for key in part_key)
(Pdb) 
--Return--
> /home/kobakoba0723/sub_dict.py(2)<genexpr>()->('green', (0, 255, 0))
-> return dict((key, whole_dict.get(key, default)) for key in part_key)
(Pdb) 
--Call--
> /home/kobakoba0723/sub_dict.py(2)<genexpr>()->('green', (0, 255, 0))
-> return dict((key, whole_dict.get(key, default)) for key in part_key)
(Pdb) 
> /home/kobakoba0723/sub_dict.py(2)<genexpr>()->('green', (0, 255, 0))
-> return dict((key, whole_dict.get(key, default)) for key in part_key)
(Pdb) 
--Return--
> /home/kobakoba0723/sub_dict.py(2)<genexpr>()->None
-> return dict((key, whole_dict.get(key, default)) for key in part_key)
(Pdb) 
--Return--
> /home/kobakoba0723/sub_dict.py(2)sub_dict()->{'green': (0, 255, 0), 'red': (255, 0, 0)}
-> return dict((key, whole_dict.get(key, default)) for key in part_key)
(Pdb) 
{'green': (0, 255, 0), 'red': (255, 0, 0)}
--Return--
> /home/kobakoba0723/sub_dict.py(8)<module>()->None
-> print sub_dict(color_map, ('red', 'green'))
(Pdb) 
--Return--
> <string>(1)<module>()->None
(Pdb)

同じ文が何度も実行されているように見える。。。

なんでだろうか、ジェネレータってこういう実行され方をするんだろうか。

> /home/kobakoba0723/sub_dict.py(2)<genexpr>()->('red', (255, 0, 0))
とか
> /home/kobakoba0723/sub_dict.py(2)<genexpr>()->('green', (0, 255, 0))

Computer Science Unplugged

コンピュータを使わないで、コンピュータサイエンスの勉強をするための教材。

コンピュータサイエンスの勉強と英語の勉強も兼ねてくれそうなすてきな教材なので読み進めてみる。

対象年齢が小学生ということで、カードゲームやパズル感覚で話が展開されていてとても読みやすい。

今日はPart1のうち3つの章を読んだ。

  1. Count the Dots - Binary Numbers
  2. Colour by Numbers - Image Representation
  3. You Can Say That Again! - Text Compression

白黒画像を2進数で表す章のところで、

白白白白黒黒黒黒黒黒黒黒黒黒黒白白白白

を次のように情報量を落として記載していた。

4, 11

面白いなぁと思って調べてみるとランレングス符号化って呼ばれる圧縮方法なのか、これ。

最初の数字が必ず白の連続数を表して、白黒の順で数字が並び、最後の白の連続数は省略する決まりか。

参考サイト

Computer Science Unplugged

2011-07-12

Pythonクックブック(4章)

ディクショナリの操作(作成、検索、追加)

ディクショナリの作成は、dict()を使うことで、keyとvalueのシーケンスから行える。

おかげで、{'key':'value'}の構文を使用しなくても済む。

また、存在しないキーに対する検索でもKeyError例外を出さないようにするには、get()メソッドが便利。

存在しなければ決まった文字列を返すように出来ている。

追加はsetdefaultメソッドが便利。get同様に検索用に使うが、キーが存在しなければ初期値を設定できる。

>>> colors = ['red', 'green', 'blue']
>>> values = [(255, 0, 0), (0, 255, 0), (0, 0, 255)]
# zip()で色とRGBパラメータのシーケンスを生成し、ディクショナリを作成
>>> rgb_dict = dict(zip(colors, values))
>>> rgb_dict
{'blue': (0, 0, 255), 'green': (0, 255, 0), 'red': (255, 0, 0)}
# 存在しないキーには初期値を設定し、設定した値を返す
>>> rgb_dict.setdefault('black', (0, 0, 0))
(0, 0, 0)
>>> rgb_dict.setdefault('white', (255, 255, 255))
(255, 255, 255)
>>> rgb_dict
{'blue': (0, 0, 255), 'black': (0, 0, 0), 'white': (255, 255, 255), 'green': (0, 255, 0), 'red': (255, 0, 0)}
>>> rgb_dict.setdefault('yellow', (255, 255, 0))
(255, 255, 0)
>>> rgb_dict
{'blue': (0, 0, 255), 'yellow': (255, 255, 0), 'black': (0, 0, 0), 'green': (0, 255, 0), 'white': (255, 255, 255), 'red': (255, 0, 0)}
# 存在するキーの値は変更されない(既存の値が返される)
>>> rgb_dict.setdefault('black', (1, 1, 1))
(0, 0, 0)
>>> rgb_dict
{'blue': (0, 0, 255), 'yellow': (255, 255, 0), 'green': (0, 255, 0), 'black': (0, 0, 0), 'white': (255, 255, 255), 'red': (255, 0, 0)}
# 存在するキーには値を返す
>>> rgb_dict.get('yellow', 'not_found')
(255, 255, 0)
# 存在しないキーには、決められた値('not found')を返す
>>> rgb_dict.get('purple', 'not_found')
'not_found'
>>> 

getに似たpopというメソッドがあるが、こちらは文字通り値を検索してポップしてくれる。

ただ、setdefaultはハッシュテーブルなど、値が増えていくデータ構造をディクショナリで実現するときにこそ使うべき。

>>> hash_table = {}
>>> hash_table
{}
>>> hash_table.setdefault(1, []).append('egg')
>>> hash_table.setdefault(1, []).append('spam')
>>> hash_table.setdefault(2, []).append('bike')
>>> hash_table.setdefault(2, []).append('car')
>>> hash_table.setdefault(2, []).append('train')
>>> hash_table.setdefault(3, []).append('pc')
>>> hash_table.setdefault(3, []).append('printer')
>>> hash_table
{1: ['egg', 'spam'], 2: ['bike', 'car', 'train'], 3: ['pc', 'printer']}
>>> 
修正@07/18

実行結果は正しく行っているが、本文が正しくなかった。

(誤)

ディクショナリの作成は、dict()を使うことで、keyとvalueのシーケンスから行える。

(正)

ディクショナリの作成は、zip()/dict()を使うことで、keyとvalueのシーケンスから行える。


2011-07-11

Pythonクックブック(4章)

リストで行列を表現

リストの要素を行列の列に見立てると以下のように足す、引く、掛けるの演算が出来る。

割るは、逆行列のかけ算と定義すれば良いが、逆行列の算出方法自体を忘れてしまったので今回はパス。

[kobakoba0723@fedora13-intel64 ~]$ cat calc_matrix.py
def add(matrix1, matrix2):
    added = []
    for (row1, row2) in zip(matrix1, matrix2):
        add_row = []
        for (element1, element2) in zip(row1, row2):
            add_row.append(element1 + element2)
        added.append(add_row)
    return added


def subtract(matrix1, matrix2):
    subtracted = []
    for (row1, row2) in zip(matrix1, matrix2):
        subtracted_row = []
        for (element1, element2) in zip(row1, row2):
            subtracted_row.append(element1 - element2)
        subtracted.append(subtracted_row)
    return subtracted


def multiply(matrix1, matrix2):
    transposition_multiplied = []
    for i in range(len(matrix1[0])):
        matrix1_column = [row[i] for row in matrix1]
        multiplied_row = []
        for j in range(len(matrix2)):
            multiplied_row_element = 0
            for (element1, element2) in zip(matrix1_column, matrix2[j]):
                multiplied_row_element += element1 * element2
            multiplied_row.append(multiplied_row_element)
        transposition_multiplied.append(multiplied_row)
    # リストの各要素は、行列の"列"を表すので転置が必要
    return  [[row[i] for row in transposition_multiplied]
               for i in range(len(transposition_multiplied[0]))]

if __name__ == '__main__':
    matrix1 = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
    matrix2 = [[2, 3, 4, 5], [6, 7, 8, 9], [10, 11, 12, 13]]

    print 'matrix1:', matrix1
    print 'matrix2:', matrix2
    print 'matrix1 + matrix2 =', add(matrix1, matrix2)
    print 'matrix1 - matrix2 =', subtract(matrix1, matrix2)
    # 4*3行列とのかけ算なので、3*4行列が必要
    transposition_matrix2 = [[row[i] for row in matrix2]
                               for i in range(len(matrix2[0]))]
    print 'matrix1 * trans_matrix2 =', multiply(matrix1, transposition_matrix2)

    print

    matrix1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    matrix2 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    print 'matrix1:', matrix1
    print 'matrix2:', matrix2
    print 'matrix1 + matrxi2 =', add(matrix1, matrix2)
    print 'matrix1 - matrxi2 =', subtract(matrix1, matrix2)
    print 'matrix1 * matrxi2 = ', multiply(matrix1, matrix2)
[kobakoba0723@fedora13-intel64 ~]$ python calc_matrix.py
matrix1: [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
matrix2: [[2, 3, 4, 5], [6, 7, 8, 9], [10, 11, 12, 13]]
matrix1 + matrix2 = [[3, 5, 7, 9], [11, 13, 15, 17], [19, 21, 23, 25]]
matrix1 - matrix2 = [[-1, -1, -1, -1], [-1, -1, -1, -1], [-1, -1, -1, -1]]
matrix1 * trans_matrix2 = [[122, 140, 158, 176], [137, 158, 179, 200], [152, 176, 200, 224], [167, 194, 221, 248]]

matrix1: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
matrix2: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
matrix1 + matrxi2 = [[2, 4, 6], [8, 10, 12], [14, 16, 18]]
matrix1 - matrxi2 = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
matrix1 * matrxi2 =  [[30, 36, 42], [66, 81, 96], [102, 126, 150]]
[kobakoba0723@fedora13-intel64 ~]$ 

行列演算などの数値演算用に、サードパーティーのNumpyモジュールがあるが、

今まで行列演算を実装したことがなかったので、折角の機会ということで実装。

参考サイト

組込み関数@標準ライブラリ