ひゃまだのblog

ひゃまだ(id:hymd3a)の趣味のブログ

pythonでネストした関数間で変数を共有する

(2021-05-25 初稿 - )

はじめに

 pythonスクリプトで、ネストした関数間で変数が共有できなくて困った。
実際には、tkinterを用いたスクリプトで、bind関数を作ったので、関数の中に関数を作るはめになった。
よほどのことがない限り、普通は関数の外に関数を作るけどね。^^;

以下のサイトに詳しい解説があるので参照のこと。多謝

スクリプト

まずは、簡単な例を。

def fn_parent():
    count = 0
    def fn_child():
        print(count)
    return fn_child()

fn_parent()
$ python3 ./nonlocal01.py 
0

親関数で定義された変数の値参照はOK。

しかし、親関数で定義された変数の値を変更しようとすると。

def fn_parent():
    count = 0
    def fn_child():
        count += 1
        print(count)
    return fn_child()

fn_parent()

1が返ってくることを期待するが…

$ python3 ./nonlocal02.py 
Traceback (most recent call last):
  File "./notlocal01.py", line 9, in 
    fn_parent()
  File "./notlocal01.py", line 7, in fn_parent
    return fn_child()
  File "./notlocal01.py", line 5, in fn_child
    count += 1
UnboundLocalError: local variable 'count' referenced before assignment

上記のように、値を変更しようとすると、定義する前に関数を参照しやがってと怒られる。(-_-;)

そこで、nonlocalを入れることにより、正常に動作し、かつ、値も変えられる。

def fn_parent():
    count = 0
    def fn_child():
        nonlocal count
        count += 1
        print(count)
    return fn_child()

fn_parent()
$ python3 ./nonlocal03.py 
1

ちなみに、筆者が作成していたtkinterスクリプトは以下のとおり。
ディレクトリとファイルの選択をやりたかった。

def ask_dir_file(dirname, filename):
    sub = tk.Tk()
    sub.withdraw()

    sub.title("Input Correct Dirname, Filename")
    sub.geometry("400x120")

    # function
    def deletevalue(event):
      #エントリーの中身を削除
      editbox1.delete(0, tk.END)
      editbox2.delete(0, tk.END)

    def ok_get(event):
        sub.quit() 

    def chg_dir(n):
        nonlocal dirname, filename
        dbase = str_base(dirname)
        dnum  = str_num(dirname)
        dirname = dbase + num_adj(dnum, n)  
        editbox1.delete(0, tk.END)
        editbox1.insert(tk.END, dirname)
        fbase   = str_base(filename.split('.')[0])
        fnum    = str_num(filename.split('.')[0])
        fext    = '.' + filename.split('.')[1]
        filename = fbase + num_adj(fnum, 0) + fext
        editbox2.delete(0, tk.END)
        editbox2.insert(tk.END,filename)

    def up_dir(event):
        chg_dir(1)

    def down_dir(event):
        chg_dir(-1)

    #ラベル
    label1 = tk.Label(sub, text='Directory:')
    label2 = tk.Label(sub, text='Filename:')
    label1.place(x = 20, y = 20)
    label2.place(x = 20, y = 50)

    #エントリー
    editbox1 = tk.Entry(sub, width=40)
    editbox1.insert(tk.END, dirname)
    editbox1.place(x=100, y=20)
    editbox2 = tk.Entry(sub, width=40)
    editbox2.insert(tk.END,filename)
    editbox2.place(x=100, y=50)

    #ボタン
    button1 = tk.Button(sub, text='OK')
    button1.bind("", ok_get)
    button1.place(x=300, y=80)
    button2 = tk.Button(sub, text='Clear')
    button2.bind("", deletevalue)
    button2.place(x=200, y=80)

    # UpキーでDirname変更
    sub.bind('', up_dir)
    sub.bind('', down_dir)

    # Enter でも OK
    sub.bind('', ok_get)
    sub.deiconify()
    sub.mainloop()
    sub.withdraw()
    return editbox1.get(), editbox2.get()

 

今回の例は、nonlocalで逃げましたが、本格的には上記サイトにも記述があるように、クラスやジェネレータを使うのが一般的だとか。

また何かわかったら記述するね。

関連ページ