Make Python code with Python

# 本日は（ポエム）

電車の中で

[Pythonでx, yとかの2重ループをやめたい](https://qiita.com/redshoga/items/ec09a91fcca3db80646e)

の記事を iPad を通して眺めてました。

私個人としては、経験上、実行速度に差がないケースしか遭遇してないので、
ネストを深くしたくないときは、

```python
import itertools
for _ in itertools.product(range(N),repeat=2):
   #write something
```

Numbaで速くしたい時は

```python
from numba import jit

@jit
def func():
   for i in range(N):
       for j in range(N):
           #write something
```

のように使い分けています。

でも、Numbaって実務で使ったことがなくて、日曜大工として kaggle の後処理で使って処理が速くなったとか、遅いループを速くしてきゃっきゃと喜ぶという程度でしか使ったことないんですよね・・・。

end of poem

# end of poem

ここからが本題です。

`itertools.product(range(N),repeat=repeat)`

の`repeat` の値を変化させたとき上と同等の作業をするループ処理を

```python
for i in range(N):
    for j in range(N):
        for k in range(N):
           # and so on...
```

をいちいち手作業で変更してあげないといけません。
自動で書けないのかな？

# eval と exec と戯れる。

`eval` と `exec` でいけるんじゃないか？というところから始まって書いてみました。

```python:meta.py
import itertools
import time


def naive(N, repeat):
    # prepare code
    tab = "\t"
    func = '\n'.join(['def loop_func(N):', tab + 's = 0'])
    loop_txt = lambda n: (n + 1) * tab + "for item_{} in range(N):".format(n)
    calc = lambda n:  (n + 1) * tab + 's += ' + \
        ' + '.join(["item_{}".format(_) for _ in range(n)])
    code = "\n".join([func] +
                     [loop_txt(n) for n in range(repeat)] +
                     [calc(repeat)] +
                     [tab + 'return s'])

    # print(code)
    exec(code)
    start_time = time.time()
    s = eval("loop_func({})".format(N))
    end_time = time.time()
    elapsed = end_time - start_time
    return s, elapsed


def product(N, repeat):
    # prepare code
    tab = '\t'
    items = ' , '.join('item_{}'.format(_) for _ in range(repeat))
    sum_items = ' + '.join('item_{}'.format(_) for _ in range(repeat))
    forloop = ' '.join([tab + 'for',
                        items, 'in',
                        'itertools.product(range(N), repeat=repeat):'])
    code = '\n'.join(['def loop_func(N, repeat):', tab + 's = 0', forloop,
                      2 * tab + 's += ' + sum_items, tab + 'return s'])
    # print(code)
    exec(code)
    start_time = time.time()
    s = eval("loop_func({}, {})".format(N, repeat))
    end_time = time.time()
    return s, end_time - start_time


def main():
    N = 10
    n_trial = 50
    repeat = 6
    naive_times = []
    product_times = []
    for _ in range(n_trial):
        naive_s, naive_elapsed = naive(N, repeat)
        prodcut_s, prodcut_elapsed = product(N, repeat)
        assert naive_s == 27000000
        assert prodcut_s == 27000000
        naive_times.append(naive_elapsed)
        product_times.append(prodcut_elapsed)
    print(sum(naive_times) / n_trial)
    print(sum(product_times) / n_trial)

if __name__ == '__main__':
    main()
```

とりあえず、自動化させたいコードをPythonの文字列として扱って、
tabと加えたり、`'\n'.join([文字列たち])`にしてコードを生成させるようにしました。

例えば、

```python
def loop_func(N):
    s = 0
    for item_0 in range(N):
        for item_1 in range(N):
            for item_2 in range(N):
                for item_3 in range(N):
                    for item_4 in range(N):
                        for item_5 in range(N):
                            for item_6 in range(N):
                                s += item_0 + item_1 + item_2 + item_3 + item_4 + item_5 + item_6
    return s
```

のような文字列を吐き出させるようにします。
あとは`exec` で実行して戻り値を `eval`で得ればミッション終了です。

# 実行結果は？

```console 
$ python meta.py
0.4330342960357666
0.40373848915100097
```

お、iteratools の方がちょっといい感じでは？　assertのエラーも出てないようなので、正常に動作しているように見えますね。repeatの数を増やすと効果が出てくるのかもしれないです（白目)


いままでは一行程度の文字列でしか `eval`, `exec` していなかったのですが、複数行に渡って　`exec` ができることを知りました。

チョットPythonのスキルが増えました。