tkinterのButtonウィジェットをリスト内包表記で複数作ると，Button押下時の挙動がおかしくなる


環境：Python 3.9.5

下のコードの(1)(2)を入れ替えるとなぜ異なる挙動をするのでしょうか？

[code]

    from tkinter import ttk
    import tkinter as tk
    from tkinter import filedialog
    
    def browse_csvfile(i):
      """
        エクスプローラーによりファイルを選び，そのファイルパスを押されたボタンの真左の
        Entryに挿入する．
      """
      filepaths[i]=filedialog.askopenfilename(filetypes = [('csv file','*.csv'), ], title='load')
      filepath_entrys[i].delete(0, tk.END) 
      filepath_entrys[i].insert(tk.END, filepaths[i])
    
    app = tk.Tk()
    
    main_frm=ttk.Frame(app)
    filepaths = [tk.StringVar() for _ in range(2)]
    filepath_entrys = [ttk.Entry(app, textvariable=i) for i in filepaths]
    [filepath_entrys[i].grid(column=0, row=i, sticky=tk.EW) for i in range(2)]
    filepath_entrys[0].focus()
    
    # (1)
    load_buttons = [ttk.Button(app, text="push", command=lambda:browse_csvfile(i)) for i in range(2)]
    
    # (2)
    # load_buttons = [0,0]
    # load_buttons[0]=ttk.Button(app, text="push", command=lambda:browse_csvfile(0))
    # load_buttons[1]=ttk.Button(app, text="push", command=lambda:browse_csvfile(1))
    
    [load_buttons[i].grid(column=1, row=i) for i in range(2)]
    
    app.mainloop()
    
[/code]

* * *

上のプログラムを実行すると，下の画像のようになります．  
(1)の場合では上下どちらのボタンを押しても下のEntryボックスにしかパスが代入されませんでしたが，(2)では欲しい動作をしてくれました．

[![GUI](https://i.stack.imgur.com/SZXXD.png)](https://i.stack.imgur.com/SZXXD.png)

なにか初歩的な思い違いをしているかもしれませんが，なぜこのようになるか全く思いつきません．  
異なる挙動の原因がわかる方いらっしゃいましたらご教示ください．




`browse_csvfile` 関数に `print(i)` を挿入してみると判るかと思いますが、上下どちらのボタンを押しても `1` が表示されます。

[code]

    def browse_csvfile(i):
      """
        エクスプローラーによりファイルを選び，そのファイルパスを押されたボタンの真左の
        Entryに挿入する．
      """
      print(i)
      filepaths[i]=filedialog.askopenfilename(filetypes = [('csv file','*.csv'), ], title='load')
                             :
    
[/code]

問題点は、`ttk.Button` の `command` に `lambda` 式を指定していることです。`lambda` 式は closure
であるために、変数 `i` は list comprehension のスコープ内に存在し続けることになります。そして、
**ボタンがクリックされた時点で評価される** ことになるわけですが、その時には既に list comprehension
の繰り返し処理は完了していますので `i` の値は `1` になっているわけです。

[code]

    load_buttons = [ttk.Button(app, text="push", command=lambda:browse_csvfile(i)) for i in range(2)]
    
[/code]

これを解決するには`functools` の `partial` 関数を使います。

[code]

    from functools import partial
    load_buttons = [ttk.Button(app, text="push", command=partial(browse_csvfile, i)) for i in range(2)]
    
[/code]

この場合、変数 `i` は list comprehension の時点で評価されます。つまり、上のボタンをクリックすると
`browse_csvfile(0)`, 下のボタンをクリックすると `browse_csvfile(1)` が実行されることになります。




`browse_csvfile` 関数に `print(i)` を挿入してみると判るかと思いますが、上下どちらのボタンを押しても `1` が表示されます。

[code]

    def browse_csvfile(i):
      """
        エクスプローラーによりファイルを選び，そのファイルパスを押されたボタンの真左の
        Entryに挿入する．
      """
      print(i)
      filepaths[i]=filedialog.askopenfilename(filetypes = [('csv file','*.csv'), ], title='load')
                             :
    
[/code]

問題点は、`ttk.Button` の `command` に `lambda` 式を指定していることです。`lambda` 式は closure
であるために、変数 `i` は list comprehension のスコープ内に存在し続けることになります。そして、
**ボタンがクリックされた時点で評価される** ことになるわけですが、その時には既に list comprehension
の繰り返し処理は完了していますので `i` の値は `1` になっているわけです。

[code]

    load_buttons = [ttk.Button(app, text="push", command=lambda:browse_csvfile(i)) for i in range(2)]
    
[/code]

これを解決するには`functools` の `partial` 関数を使います。

[code]

    from functools import partial
    load_buttons = [ttk.Button(app, text="push", command=partial(browse_csvfile, i)) for i in range(2)]
    
[/code]

この場合、変数 `i` は list comprehension の時点で評価されます。つまり、上のボタンをクリックすると
`browse_csvfile(0)`, 下のボタンをクリックすると `browse_csvfile(1)` が実行されることになります。

