質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

2回答

2318閲覧

tkinterのtoplevel(サブ画面)を閉じても、メモリが開放されない

hijiri

総合スコア61

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2021/02/19 01:49

tkinterで、toplevelを使っていくつか画面を開きたいと思います。
その際、サブ画面を閉じたときにメモリが開放されずに困っております。

main.py

python

1import tkinter as tk 2import sub 3 4def open(event): 5 sub.main() 6 7 8root = tk.Tk() 9root.geometry('900x750') 10 11btn = tk.Button(root, text="開く") 12btn.pack() 13btn.bind("<ButtonRelease-1>", lambda event: open(event)) 14 15root.mainloop()

sub.py

python

1import tkinter as tk 2import tkinter.ttk as ttk 3 4class Treelist(tk.Frame): 5 def __init__(self, root, item): 6 super().__init__(root) 7 self.pack(fill=tk.Y) 8 9 self.tree = ttk.Treeview(self, column=(1), show="", selectmode="browse") 10 self.scrollbar = ttk.Scrollbar(self, command=self.tree.yview) 11 self.tree.configure(yscrollcommand=lambda f, l: self.scrollbar.set(f, l)) 12 self.scrollbar.pack(side="right", fill=tk.Y) 13 self.tree.pack(fill=tk.Y) 14 self.tree.column(1, width=100) 15 for a in item: 16 self.tree.insert("", "end", values=(a)) 17 18def main(): 19 root = tk.Toplevel(name="sub_screen") 20 root.geometry('900x750') 21 root.title("sub") 22 23 item = [x for x in range(1000000)] 24 tree = Treelist(root, item) 25 del item 26 27 root.mainloop() 28 29if __name__ == "__main__": 30 main()

というコードを書いてみたのですが、メイン画面のボタンを押すと、サブ画面が表示されます。
そこでメモリ使用率が急増するのですが、サブ画面を閉じてもメモリがほとんど開放されず、サブ画面を開くたびにメモリがどんどん加算されていきます。

サブ画面のitemはdelしているので、ツリービューに残ったデータが消えずにどんどん加算していってるのだと思います。

サブ画面を閉じたとき(右上の☓ボタンを押したとき)に、メモリを開放するにはどのようにしたらよいでしょうか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

mainloop() は、イベントループなので、通常はプログラム中で一度のみで良いです。

メモリに関しては、期待されてるような「メモリの解放」をしたい場合は、
恐らく、サブ画面を別プロセスにすることになります。


但し、巨大な数のオブジェクトを作ってる事自体がそもそもの問題なので、
オフトピックになるかもしれませんが、別の切り口からの解決策の提案。

  • UI を工夫する。例: 大規模なリストは扱い辛いので、

 一定件数づつ表示のページにする、等。

  • データが膨大にあっても、表示できる数は画面のサイズ上、限りがあるので

 全ての要素をインスタンス化せずに、表示する部分のみのインスタンスを作り
データを入れ替えて対応する。
→ データの規模に関わらず、一定量のメモリで済む

例えば、他のGUIライブラリでは、データベース等にある膨大なデータを
必要な部分のみをその都度読み込み、省メモリで表示するクラスが提供されてます。
(Qt の QListWidget/QListView, wx の ListCtrl/ListView 等)

Tkinter の基本ウィジェットには提供されてないので、
ライブラリを探すか、自分で実装することになります。

tkinterでは、具体的には
スクロールバーの位置により表示する内容を変更するといった処理にします。
(事例が少ないので難易度は少し高め、手間はかかります)

雑なコードですが、メモリの比較用に
100万件の数値をリストボックスに表示。

python

1#!/usr/bin/env python3.9 2 3import tkinter as tk 4from tkinter import ttk 5 6def main(): 7 MINNUM, MAXNUM, SIZE = 0, 1000000, 10 8 9 root = tk.Tk() 10 listvar = tk.StringVar(value=list(range(SIZE))) 11 listbox = tk.Listbox(root, listvariable=listvar) 12 listbox.grid(row=0, column=0, sticky=tk.NSEW) 13 vbar = tk.Scrollbar(root, orient=tk.VERTICAL) 14 vbar.grid(row=0, column=1, sticky=tk.NS) 15 root.grid_rowconfigure(0, weight=1) 16 root.grid_columnconfigure(0, weight=1) 17 18 current_val = 0 19 def yview(command, num=1, unit=None): 20 nonlocal current_val 21 if command == "moveto": 22 value = float(num) 23 start = max(0, value) 24 last = min(1, value) 25 val = int(value * (MAXNUM-MINNUM)) 26 val = max(MINNUM, min(MAXNUM, val)-SIZE) 27 listvar.set(tuple(range(val, val+SIZE))) 28 vbar.set(start, last) 29 current_val = val 30 elif command == "scroll": 31 size = SIZE*int(num) if unit == "pages" else int(num) 32 val = max(MINNUM, min(current_val+size, MAXNUM-SIZE)) 33 listvar.set(tuple(range(val, val+SIZE))) 34 current_val = val 35 36 vbar.config(command=yview) 37 root.mainloop() 38 39if __name__ == '__main__': 40 main() 41

100万件のデータは、メモリの使用量を解りする為の仮のテストデータだと思いますが。
この回答の趣旨は、大規模なデータを表示する際のアプローチの提示。

※ 実用するには、リサイズ時の表示枠対応や、
他に矢印キーでの移動やマウスホィール対応等、手入れが必要な部分はあります。
充分なテストはしてません。

※ 外部ライブラリでは、pandastable 等。保守状況は把握してません、対応バージョンは古いかもしれない。

投稿2021/02/20 04:35

teamikl

総合スコア8664

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hijiri

2021/02/20 07:29

御返事、ありがとうございます。 教えていただいた方法だと、メモリの使用量が全く異なりました。 もう少し勉強してみたいと思います! ちなみにですが、サブ画面を別プロセスにするというのを、ご存知でしたら教えていただいてよろしいでしょうか? 「サブ画面を別プロセスにしたらどうか?」ということは考えてみたのですが、色々調べてもやり方がわからなかったもので・・・
teamikl

2021/02/20 09:18 編集

subprocess や multiprocessing モジュールを使う事になります。 サブ画面が完全に独立した場合は良いのですが、 メイン画面とのやりとりが必要な場合は、別プロセスで起動する以外にも、 プロセス間のデータの共有や受け渡し等の、新たな課題もでてきます。
hijiri

2021/02/21 01:05

ありがとうございます。 まずは教えていただいたように、メモリの使用が少なくなるような設計を目指すようにしたいと思います。
guest

0

destroyを追加すると良くなるかもしれません。

python

1def main(): 2 root = tk.Toplevel(name="sub_screen") 3 root.geometry('900x750') 4 root.title("sub") 5 6 item = [x for x in range(1000000)] 7 tree = Treelist(root, item) 8 del item 9 10 root.mainloop() 11 root.destroy() 12

試してみてください。

投稿2021/02/19 04:34

ppaul

総合スコア24666

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hijiri

2021/02/19 05:27

御返事ありがとうございます。 試してみたのですが、メモリは解放されません。 メイン画面を閉じると _tkinter.TclError: can't invoke "destroy" command: application has been destroyed とエラーが出ます。 もともと、sub画面で root.mainloop() print("test") とした場合、sub画面を閉じても出力されず、main画面を閉じたときに出力されてしまいます。 ですので、main画面を閉じたときに、sub画面に書いたdestroyが作動するようで、「アプリケーションがすでにありません」とのエラーが出るのではないかと考えています。 sub画面のmainloop()は、main画面のmainloop()とつながっているようで、困っています。
hijiri

2021/02/19 08:20

続けて失礼します。 toplevelの場合は、mainloop()は必要ないんですね。 ただ、mainloop()を除いたsub.pyにボタンを作成し、ボタンを押してroot.destroy()を実行させても、sub画面は閉じますが、メモリはやはり解放されません・・・
ppaul

2021/02/19 08:40

どうやってメモリの解放を確認しているのでしょうか。 標準ライブラリのfreeでメモリを戻すと次のmallocやcallocで使われることがありますが、プロセスの使用しているメモリは減りません。 つまり、現在の仮想メモリを使うOSでは、外部から見える使用メモリは、そのプロセスが始まってからOSに依頼して取ったメモリの総量で、それは減ることはありません。 もしも、「サブ画面を開くたびにメモリがどんどん加算されていきます」という症状が改善されるなら、それはfreeが働いて、再利用しているということなのです。
hijiri

2021/02/20 07:07

メモリにつきましては、タスクマネージャーを開いて、プロセス(今回の場合なら、tkinterを起動したときに出てくるpython)のメモリを確認したまま動かしています。 サブ画面を開くと一気にメモリ使用量が増え、サブ画面を閉じると、わずかではありますがメモリ使用量が減ります。 サブ画面を閉じたときに、最初にメイン画面を開いたとき(起動させたとき)と同じくらいのメモリ使用量になるのかと思っていたのですが、圧倒的に多いままとなっています・・・ タスクマネージャのメモリ使用量というのは、その時点においてそのプロセスが使用しているメモリの量だと思っているのですが、私の認識が間違っているのでしょうか?
ppaul

2021/02/20 07:55

メモリという言葉に複数の意味があることを勉強しないと、このあたりの問題は理解できません。 https://ja.wikipedia.org/wiki/%E4%BB%AE%E6%83%B3%E8%A8%98%E6%86%B6 を読んで、まず仮想メモリと物理メモリの違いを理解しましょう。 そして、 https://www.dcom-web.co.jp/lab/etc/virtual_memory_and_physical_memory_in_windows を読んでみてください。 こういうOS関連の知識がないと、プロセスとは何かとか、別プロセスにするとかいう言葉の意味も理解できないと思います。
hijiri

2021/02/21 01:05

ありがとうございます。 メモリの定義が複数というのは、初めて知りました。 もう少し勉強してみたいと思います!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問