質問: * Python 3.6.6
回答: まずは中で何が起こっているのか把握するために、`print`文で覗いてみましょう。 そのまま実行すると1万や10万回`print`文が実行されるので、`number引数(-n, --number)`で回数を制限します。 ついでに`repeat引数(-r, --repeat)`で試行回数(`best of r`の`r`に該当する回数)も減らしておきます。 ``` PS C:\Users\payaneco> python -m timeit -r 1 -n 3 -s "a=map(str, range(1000))" "s=''" "for i in a: s+=i;" "print(len(s))" 2890 0 0 3 loops, best of 1: 211 usec per loop PS C:\Users\payaneco> python -m timeit -r 1 -n 3 -s "a=map(str, range(1000))" "s=''.join(a)" "print(len(s))" 2890 0 0 3 loops, best of 1: 168 usec per loop ``` `len(s)`で文字列の長さを計ったところ、2回目、3回目は0になってしまいました! ここで本のヒントを読み解きます。 > > 落とし穴はsetup引数の使い方と、Python3におけるmapの振る舞いにあります。 > > > 試しにsetup引数(-s --setup)を消してみましょう。 ``` PS C:\Users\payaneco> python -m timeit -r 1 -n 3 "a=map(str, range(1000))" "s=''" "for i in a: s+=i;" "print(len(s))" 2890 2890 2890 3 loops, best of 1: 378 usec per loop PS C:\Users\payaneco> python -m timeit -r 1 -n 3 "a=map(str, range(1000))" "s=''.join(a)" "print(len(s))" 2890 2890 2890 3 loops, best of 1: 257 usec per loop ``` 欲しい結果が得られました。 `len(s)`は常に正しい値を返し、速度も`for`文より`join`の方が速くなりました。 ただし`setup引数`がある時よりも速度は遅いです。 `setup引数`は最初のステートメントに適用され、`map`関数は初めの1回のみ初期化されて、2,3回目には初期化されません。 逆に`setup引数`が無ければ毎回`a=map(str, range(1000))`が実行されるので、その分遅くなります。 ようやくpython3における`map`とは何かという話が関係してきます。 `map`とは配列に関数を適用して`イテレータ`を返す高階関数です。 ここで言う「配列」とは`range(1000)`で作成した0..999の数値です。 ここで言う「関数」とは`str`です。n=0..999の数値を`str(n)`で文字列化しています。 さてpythonのイテレータは、`for`文や`next()`関数で次の値を取得できますが、いわゆる`seek`や`prev`のように特定の場所へアクセスしたり、前の値を取得したりすることはできません。 つまり一度`for`や`join`でアクセスすると、また先頭から読み取ることができないのです。 pythonで下記のコードを実行すると、1回目は2890が取得できますが、2回目は0になってしまいます。 これが`map`というかイテレータの振る舞いです。 ``` a=map(str, range(1000)) s=''.join(a) print(len(s)) # 2890 s=''.join(a) print(len(s)) # 0 ``` ちなみに`map`を`list`化すればイテレータではなくリストになるので、正しい値を取得することができます。 ``` PS C:\Users\payaneco> python -m timeit -s "a=list(map(str, range(1000)))" "s=''.join(a)" 100000 loops, best of 3: 10.3 usec per loop PS C:\Users\payaneco> python -m timeit -s "a=list(map(str, range(1000)))" "s=''" "for i in a: s+=i;" 10000 loops, best of 3: 176 usec per loop ``` 速度も`join`の方が圧倒的に速くなりました! おそらくですが、使い終わったイテレータに`for`を実行してもループ冒頭の1回目で中断しますが、0件のイテレータに`join`を掛けるとオーバーヘッドが発生して`join`の方が遅くなると推測されます。 以上のことから、以下の「読者への課題」を理解しないと正しい結果が得られません。 * 毎回setupしないとイテレータを再利用できない * mapはイテレータを作るだけなので、リストや配列にしないと罠にハマることがある