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版