LoginSignup
10
5

More than 5 years have passed since last update.

Make Python code with Python

Posted at

本日は(ポエム)

電車の中で

Pythonでx, yとかの2重ループをやめたい

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

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

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

Numbaで速くしたい時は

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 の値を変化させたとき上と同等の作業をするループ処理を

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

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

eval と exec と戯れる。

evalexec でいけるんじゃないか?というところから始まって書いてみました。

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([文字列たち])にしてコードを生成させるようにしました。

例えば、

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で得ればミッション終了です。

実行結果は?

$ python meta.py
0.4330342960357666
0.40373848915100097

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

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

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

10
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
5