エキスパート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:シーケンス内の連続した要素をグループ化する(イテレータを返すのか???)

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

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

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

続きを読む

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の日記

Python クックブック 第2版

Python クックブック 第2版

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

Python クックブック 第2版

Python クックブック 第2版

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のシーケンスから行える。


Python クックブック 第2版

Python クックブック 第2版

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モジュールがあるが、
今まで行列演算を実装したことがなかったので、折角の機会ということで実装。

参考サイト

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

Python クックブック 第2版

Python クックブック 第2版

Pythonクックブック(4章)

リストのリスト

リストのかけ算をする時には気をつけなければいけない。
リストのかけ算は、リストの要素を乗数分新しく作るのではなく、同じ要素に対する参照を乗数分作るだけ。

# ベタにリストのリストを作る
>>> multi1 = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
# 各要素は別のリストオブジェクトを参照している
>>> multi1[0] is multi1[1]
False
# リストのかけ算でリストのリストを作る
>>> multi2 = [[0, 0, 0]] * 3
# 各要素は同じリストオブジェクトを参照している
>>> multi2[0] is multi2[1]
True
>>> multi1[0][0] = 'oops'
# 各要素は別々のオブジェクトを参照しているので引きずられない
>>> multi1
[['oops', 0, 0], [0, 0, 0], [0, 0, 0]]
>>> multi2[0][0] = 'oops'
# 各要素が同じオブジェクトを参照しているので引きずられてしまう
>>> multi2
[['oops', 0, 0], ['oops', 0, 0], ['oops', 0, 0]]
>>> 

リストのリストを作りたい時はかけ算ではなくリスト内包表記を使う。

>>> multi3 = [[0 for column in range(3)] for row in range(3)]
>>> multi3[0] is multi3[1]
False
>>> multi3[0][0] = 'oops'
>>> multi3
[['oops', 0, 0], [0, 0, 0], [0, 0, 0]]
>>> 

シーケンスの展開

本文中では文字列についてはそんな場面が少ないので省略されてた。
せっかくなのでどんな感じになるのか書いてみる。
・・・が、1文字でも文字列として扱われることに気付かず、スタックオーバーフローだらけ。
「なんで?」と悩む。。。
気付いてしまえばなんてことはないことだから、いかにこういうこと状態を無くせるかだなぁ。

それから、例外を使う方法は遅いということで実際に測ってみたが、確かに遅い。
あと、文字列も展開するとやっぱり遅い。再帰の回数が増えるから、その分遅くなってしまうのだろう。

[kobakoba0723@fedora13-intel64 ~]$ cat loop_sequence.py 
import timeit


def list_or_tuple(obj):
    return isinstance(obj, (list, tuple))


def list_or_tuple_or_string(obj):
    #
    # basestringで判定すると、'a'などの1文字でもTrueと判定され、無限ループ → スタックオーバーフロー
    #
    # return isinstance(obj, (list, tuple, basestring))
    if isinstance(obj, (list, tuple)):
        return True
    else:
        if isinstance(obj, basestring):
            if len(obj) == 1:
                return False
            else:
                return True
        else:
            return False


def nostring_iterable(obj):
    try:
        iter(obj)
    except TypeError:
        return False
    else:
        return not isinstance(obj, basestring)


def flatten(sequence, to_expand=list_or_tuple):
    for item in sequence:
        if to_expand(item):
            for subitem in flatten(item, to_expand):
                yield subitem
        else:
            yield item


if __name__ == '__main__':
    sequence = [1, 2, [3, [], 4, [5, 6], 7, [8, ], ], 9,
                ('a', 'b'), (('c', 'd'), 10, 11), 'egg', 'spam']
    print "===== use list_or_tuple ======"
    for x in flatten(sequence):
        print x,
    print

    print "===== use list_or_tuple_or_string ======"
    for x in flatten(sequence, to_expand=list_or_tuple_or_string):
        print x,
    print

    print "===== use nonstring_iterable ====="
    for x in flatten(sequence, to_expand=nostring_iterable):
        print x,
    print

    print "---- measure time -----"
    t = timeit.Timer("""
    for x in flatten(seq):
        pass
    """,
    "from __main__ import flatten; seq=%s" % sequence)
    print "list_or_tuple: %f" % t.timeit(number=1000)

    t = timeit.Timer("""
    for x in flatten(seq, to_expand=list_or_tuple_or_string):
        pass
    """,
    "from __main__ import flatten, list_or_tuple_or_string; seq=%s" % sequence)
    print "list_or_tuple_or_string: %f" % t.timeit(number=1000)

    t = timeit.Timer("""
    for x in flatten(seq, to_expand=nostring_iterable):
        pass
    """,
    "from __main__ import flatten, nostring_iterable; seq=%s" % sequence)
    print "nostring_iterable: %f" % t.timeit(number=1000)
[kobakoba0723@fedora13-intel64 ~]$ python loop_sequence.py 
===== use list_or_tuple ======
1 2 3 4 5 6 7 8 9 a b c d 10 11 egg spam
===== use list_or_tuple_or_string ======
1 2 3 4 5 6 7 8 9 a b c d 10 11 e g g s p a m
===== use nonstring_iterable =====
1 2 3 4 5 6 7 8 9 a b c d 10 11 egg spam
---- measure time -----
list_or_tuple: 0.033982
list_or_tuple_or_string: 0.059997
nostring_iterable: 0.071849

参考サイト

リストのかけ算@atsuoishimotoの日記
timeit - 小さなPythonコードの実行時間を計る@Python Module of The Week

Python クックブック 第2版

Python クックブック 第2版

Pythonクックブック(4章)

オブジェクトのコピー

Pythonでは明示的にしないと、オブジェクトのコピーはされない。
immutableオブジェクトの場合は気にしなくても良いけど、mutableオブジェクトの場合は気にしないといけない。
代入文は参照先の共有ってことに注意。

ビルドインクラスの場合、コピー元を引数としてビルドインクラスを呼び出すと、
シャローコピー相当のことができるなんてことは知らなかった。

>>> import copy
>>> list_a = [1, 2, 3]
# 参照先の共有
>>> list_b = list_a
# シャローコピー
>>> list_c = list(list_a)
>>> list_d = copy.copy(list_a)
# 参照先を共有しているので等価
>>> list_a is list_b
True
# シャローコピーして、新しいオブジェクトになっているので非等価
>>> list_a is list_c
False
>>> list_a is list_d
False
>>> list_a[0] = 10
# 参照先を共有してるので変更の影響を受けるが、新しいオブジェクトは影響を受けない
>>> list_a, list_b, list_c, list_d
([10, 2, 3], [10, 2, 3], [1, 2, 3], [1, 2, 3])
>>> 

ディクショナリ、集合もリスト同様に、dict(instance)、set(instance)でシャローコピー可能
集合の要素の追加はadd()メソッドで可能

>>> dict_a = {'a':1, 'b':2}
# シャローコピー
>>> dict_b = dict(dict_a)
>>> dict_a is dict_b
False
>>> set_a = set(list_a)
# シャローコピー
>>> set_b = set(set_a)
>>> set_a.add(4)
>>> set_a, set_b
(set([1, 2, 3, 4]), set([1, 2, 3])
>>> 

ずっと「集合はimmutable」って勘違いしてたけど、mutableなものとimmutableなものがある。
mutableなのがsetで、immutableなのがfrozenset。

リスト内包

リストの各要素に対して関数を実行するには、リスト内包表記ではなく、mapを使うべきと書いてある。
なんでだろう?速いのかな?

[kobakoba0723@fedora13-intel64 ~]$ cat test.py 
import timeit


list_a = [1, 2, 3]


def add_4(x):
    return x + 4


def use_list_comprehension():
    return [add_4(x) for x in list_a]


def use_map():
    return map(add_4, list_a)


if __name__ == '__main__':
    list_b1 = use_list_comprehension()
    list_b2 = use_map()
    print list_a, list_b1, list_b2

    print 'list_comprehension: %f' % timeit.Timer("use_list_comprehension()", "from __main__ import use_list_comprehension").timeit(number=100)
    print 'map function: %f' % timeit.Timer("use_map()", "from __main__ import use_map").timeit(number=100)
[kobakoba0723@fedora13-intel64 ~]$ python test.py 
[1, 2, 3] [5, 6, 7] [5, 6, 7]
list_comprehension: 0.000106
map function: 0.000119
[kobakoba0723@fedora13-intel64 ~]$ 

そうでもないなぁ。。。なんだろう。

map(function, iterable, ...)だから、複数個のiterableオブジェクトを処理できるとか、
リストに限らず文字列でも大丈夫とかいったメリットはあるんだろうなぁ。

参考サイト

組み込み型@Python 標準ライブラリ

Python クックブック 第2版

Python クックブック 第2版