【Python GUI実用サンプル】フォルダ一括作成ツールを拡張して並列化(tkinter ttk.Spinbox)に対応する
目次
使用できる並列数を確認する
まず環境の確認をします。
コマンドプロンプトを起動してpythonを下記コマンドで起動しましょう。
python
次に下記コマンドを実行してください。
import os os.cpu_count()
このコマンドで出力された数が使用できる最大の並列数になります。(私の環境では8です。)
1の場合は並列化に対応しても効果はありません。
下記コマンドを入力してpythonはいったん閉じます。
exit()
今回使用する主なモジュール
multiprocessing.Pool
ttk.Spinbox
※ttk.Spinboxは3.7から導入されているので、それ以前のPythonを使用している場合は通常のSpinboxを使用してください
下準備
今回拡張するツールは下記の記事で作成したツールです。
サンプルコードからコードを取得してください。
またファイル名をmakeDirectoryToolsSampleMultiVer.pyとしてください。
実装
・コア数入力用Widget作成!
まずコア数用の制御変数を用意します。
下記コードをself.addDirの制御変数宣言の後に追加してください。
self.nCpu = IntVar(value=1)
制御変数を宣言する際valueを指定することで初期値を与えることができます。
次にAddrirパーツの下に来るように下記コードを追加してください。
cpuMax = os.cpu_count() nCpuLabel = ttk.Label(inputFrame,text="nCpu") nCpuLabel.grid(column=0,row=3) nCpu = ttk.Spinbox(inputFrame, textvariable = self.nCpu ,from_=1 , to=cpuMax) nCpu.grid(column=1,row=3)
Spinboxはfrom_、toをオプションを指定することで
指定した範囲内の値のみに制限できます。
並列数は1~os.cpu_count()まで指定が可能なので、
それぞれのオプションに指定します。
・multiprocessing.Poolをimport
ファイル上部に下記コードを追加してください。
from multiprocessing import Pool
ここまでのサンプルコード
from tkinter import * import tkinter.ttk as ttk import tkinter.filedialog as filedialog import os import tkinter.messagebox as messagebox from multiprocessing import Pool # ディレクトリ構造をクラス化する class Directory(): # subDirs -> Directory List def __init__(self, dirPath,subDirs=None): self.dirPath = dirPath self.subDirs = subDirs def getSubDirs(self): return self.subDirs def getDirPath(self): return self.dirPath #Directory作成ロジック class MakeDirectoryLogic(): def __init__(self,rootPath): self.setRootPath(rootPath) #Directoryの情報をもとに実際にフォルダを作成する def makeDirectory(self,rootPath,dirList): # パスが存在しない場合は即終了 if os.path.exists(rootPath) ==False: return for dir in dirList: path = os.path.join(rootPath,dir.getDirPath()) print("path",path) os.mkdir(path) # 子の階層を作成する children = dir.getSubDirs() if len(children) > 0: self.makeDirectory(path,children) # tree =ttk.Treeview # ツリーの情報からDirectoryリストを作成する def createDirectoryList(self,rootiid,tree,dirList): dirname = tree.item(rootiid,"text") children = tree.get_children(rootiid) childlist=[] dir = Directory(dirname,childlist) dirList.append(dir) if len(children)>0: for child in children: self.createDirectoryList(child,tree,childlist) # ルートパスを設定する def setRootPath(self,rootPath): self.rootPath = rootPath # GUIから呼び出すディレクトリ作成アクション def makeDirectoryAction(self,tree,rootiid): dirList=[] self.createDirectoryList(rootiid,tree,dirList) self.makeDirectory(self.rootPath,dirList) class MakeDirectoryTools(ttk.Frame): def __init__(self,master): super().__init__(master) self.rootPath = StringVar() self.targetDir = StringVar() self.addDir = StringVar() self.nCpu = IntVar(value=1) self.iid="" self.rootiid="" self.logic = MakeDirectoryLogic(self.rootPath.get()) self.create_widgets() def create_widgets(self): leftframe = self.createTreeView() leftframe.pack(side="left") rightframe= self.createInputPanel() rightframe.pack(side="right") self.pack() def createTreeView(self): treeFrame = ttk.Frame(self) self.tree = ttk.Treeview(treeFrame) # ツリーの項目が選択されたら指定されたを新しく階層を作るディレクトリ名を更新する self.tree.bind("<<TreeviewSelect>>",self.targetDirectory) # 列名をつける self.tree.heading("#0",text="Directory") self.tree.pack() # rootのiidを登録 self.rootiid = self.tree.insert("","end",text="makedirectoryTools") self.iid = self.rootiid return treeFrame def createInputPanel(self): inputFrame = ttk.LabelFrame(self,text="InputParam") # 新しく作成するディレクトリのルート rootPathLabel = ttk.Label(inputFrame,text="RootPath") rootPathLabel.grid(column=0,row=0) rootpathEntry = ttk.Entry(inputFrame,textvariable=self.rootPath) rootpathEntry.grid(column=1,row=0) rootpathButton = ttk.Button(inputFrame,text="open",command=self.openFileDialog) rootpathButton.grid(column=2,row=0) #新しく階層を作るディレクトリを表示するWidget addPathLabel = ttk.Label(inputFrame,text="TargetDir") addPathLabel.grid(column=0,row=1) addpathEntry = ttk.Entry(inputFrame,state="readonly",textvariable=self.targetDir) addpathEntry.grid(column=1,row=1) addPathButton = ttk.Button(inputFrame,text="delete",command = self.deleteDirectory) addPathButton.grid(column=2,row=1) #追加するディレクトリを表示するWidget addNodeLabel = ttk.Label(inputFrame,text="AddDir") addNodeLabel.grid(column=0,row=2) addNodeEntry = ttk.Entry(inputFrame,textvariable=self.addDir) addNodeEntry.grid(column=1,row=2) addNodeButton = ttk.Button(inputFrame,text="add",command=self.insertDirectory) addNodeButton.grid(column=2,row=2) cpuMax = os.cpu_count() nCpuLabel = ttk.Label(inputFrame,text="nCpu") nCpuLabel.grid(column=0,row=3) nCpu = ttk.Spinbox(inputFrame, textvariable = self.nCpu,from_=1 , to=cpuMax) nCpu.grid(column=1,row=3) # 作成するボタン makeDirButton = ttk.Button(inputFrame,text="makeDirectory",command=self.makeDirectory) makeDirButton.grid(column=2,row=4) return inputFrame # ルートパスのディレクトリを決める def openFileDialog(self): folder = filedialog.askdirectory(); self.rootPath.set(folder) self.logic.setRootPath(folder) # 指定されたディレクトリを反映 def targetDirectory(self,event): self.iid = self.tree.focus() if self.iid : self.targetDir.set(self.tree.item(self.iid,"text")) #指定されたディレクトリに子階層を加える def insertDirectory(self): addDir = self.addDir.get() # ディレクトリ名がない場合は処理しない if addDir != "": children = self.tree.get_children(self.iid) # 同じ階層に同じ名前で作成は不可 for child in children: childname = self.tree.item(child,"text") if childname == addDir: messagebox.showerror("登録エラー","既に登録されています") return self.tree.insert(self.iid,"end",text=self.addDir.get()) #ディレクトリを削除する def deleteDirectory(self): if self.iid == "" or self.iid == self.rootiid: messagebox.showerror("削除エラー","削除する階層を選択してください。\nmakedirectoryToolsディレクトリは削除できません。") return self.tree.delete(self.iid) # ディレクトリを作成する def makeDirectory(self): rootPath = self.rootPath.get() # ルートのパスが存在しない場合は作成できないのでエラーダイアログを出して終了 if rootPath =="": messagebox.showerror("作成エラー","ディレクトリを作成するルートディレクトリを指定してください。") return elif os.path.exists(rootPath) == False: messagebox.showerror("作成エラー","ルートディレクトリが不正です。") return mkpath = os.path.join(rootPath,self.tree.item(self.rootiid,"text")) # 上書きを防ぐため、すでにこのツールで作成されたフォルダがある場合はエラーダイアログを出して終了 if os.path.exists(mkpath): messagebox.showerror("作成エラー","既にmakedirectoryToolsディレクトリが作成されています") return # ロジックのディレクトリ作成アクションを呼び出す self.logic.makeDirectoryAction(self.tree,self.rootiid) if __name__ == '__main__': master = Tk() master.title("MakeDirectory Tools") MakeDirectoryTools(master) master.mainloop()
・並列実行用アクションを作成する1
既存のディレクトリ作成アクションはそのまま残しておいて並列実行用アクションを作成します。
下記のコードをMakeDirectoryLogicに追加してください。
def makeDirectoryMultiVerAction(self,tree,rootiid,nCpu = 1):
次に呼び出し部分も修正します。
通常のmakeDirectoryActionをコメントアウトし先ほど追加した関数に変更します。
# ロジックのディレクトリ作成アクションを呼び出す # self.logic.makeDirectoryAction(self.tree,self.rootiid) self.logic.makeDirectoryMultiVerAction(self.tree,self.rootiid,self.nCpu.get())
・並列実行用アクションを作成する2
実際にmakeDirectoryMultiVerActionを作成します。
今回は並列化にmultiprocessing.Poolのmapを使用します。
map(関数、関数に渡す引数のリスト)で実行することができます。
今回は前回のmakeDirectoryをそのまま使用するため、makeDirectoryMultiVerという引数をmakeDirectoryに割り当てる関数を被せています。
下記がmakeDirectoryMultiVer関数とmakeDirectoryMultiVerAction関数のコードです。
def makeDirectoryMultiVer(self,map): self.makeDirectory(map[0],map[1]) def makeDirectoryMultiVerAction(self,tree,rootiid,nCpu = 1): dirList=[] self.createDirectoryList(rootiid,tree,dirList) path = os.path.join(self.rootPath,dirList[0].getDirPath()) print("path",path) os.mkdir(path) children = dirList[0].getSubDirs() mapArgsList=[] # 引数を作成 and 子のディレクトリは作る for child in children: childpath = os.path.join(path,child.getDirPath()) os.mkdir(childpath) map = [childpath,child.getSubDirs()] print(map) mapArgsList.append(map) print(nCpu) p = Pool(nCpu) p.map(self.makeDirectoryMultiVer,mapArgsList) p.close()
makedirectoryToolsフォルダとその直下(子)のディレクトリは通常通り作成しますが、
孫以降のディレクトリは、子ごとに並列化し作成させています。
サンプル
サンプルコード
from tkinter import * import tkinter.ttk as ttk import tkinter.filedialog as filedialog import os import tkinter.messagebox as messagebox from multiprocessing import Pool # ディレクトリ構造をクラス化する class Directory(): # subDirs -> Directory List def __init__(self, dirPath,subDirs=None): self.dirPath = dirPath self.subDirs = subDirs def getSubDirs(self): return self.subDirs def getDirPath(self): return self.dirPath #Directory作成ロジック class MakeDirectoryLogic(): def __init__(self,rootPath): self.setRootPath(rootPath) #Directoryの情報をもとに実際にフォルダを作成する def makeDirectory(self,rootPath,dirList): # パスが存在しない場合は即終了 if os.path.exists(rootPath) ==False: return for dir in dirList: path = os.path.join(rootPath,dir.getDirPath()) print("path",path) os.mkdir(path) # 子の階層を作成する children = dir.getSubDirs() if len(children) > 0: self.makeDirectory(path,children) # tree =ttk.Treeview # ツリーの情報からDirectoryリストを作成する def createDirectoryList(self,rootiid,tree,dirList): dirname = tree.item(rootiid,"text") children = tree.get_children(rootiid) childlist=[] dir = Directory(dirname,childlist) dirList.append(dir) if len(children)>0: for child in children: self.createDirectoryList(child,tree,childlist) # ルートパスを設定する def setRootPath(self,rootPath): self.rootPath = rootPath # GUIから呼び出すディレクトリ作成アクション def makeDirectoryAction(self,tree,rootiid): dirList=[] self.createDirectoryList(rootiid,tree,dirList) self.makeDirectory(self.rootPath,dirList) def makeDirectoryMultiVer(self,map): self.makeDirectory(map[0],map[1]) def makeDirectoryMultiVerAction(self,tree,rootiid,nCpu = 1): dirList=[] self.createDirectoryList(rootiid,tree,dirList) path = os.path.join(self.rootPath,dirList[0].getDirPath()) print("path",path) os.mkdir(path) children = dirList[0].getSubDirs() mapArgsList=[] # 子のディレクトリは作る for child in children: childpath = os.path.join(path,child.getDirPath()) os.mkdir(childpath) map = [childpath,child.getSubDirs()] print(map) mapArgsList.append(map) print(nCpu) p = Pool(nCpu) p.map(self.makeDirectoryMultiVer,mapArgsList) p.close() class MakeDirectoryTools(ttk.Frame): def __init__(self,master): super().__init__(master) self.rootPath = StringVar() self.targetDir = StringVar() self.addDir = StringVar() self.nCpu = IntVar(value=1) self.iid="" self.rootiid="" self.logic = MakeDirectoryLogic(self.rootPath.get()) self.create_widgets() def create_widgets(self): leftframe = self.createTreeView() leftframe.pack(side="left") rightframe= self.createInputPanel() rightframe.pack(side="right") self.pack() def createTreeView(self): treeFrame = ttk.Frame(self) self.tree = ttk.Treeview(treeFrame) # ツリーの項目が選択されたら指定されたを新しく階層を作るディレクトリ名を更新する self.tree.bind("<<TreeviewSelect>>",self.targetDirectory) # 列名をつける self.tree.heading("#0",text="Directory") self.tree.pack() # rootのiidを登録 self.rootiid = self.tree.insert("","end",text="makedirectoryTools") self.iid = self.rootiid return treeFrame def createInputPanel(self): inputFrame = ttk.LabelFrame(self,text="InputParam") # 新しく作成するディレクトリのルート rootPathLabel = ttk.Label(inputFrame,text="RootPath") rootPathLabel.grid(column=0,row=0) rootpathEntry = ttk.Entry(inputFrame,textvariable=self.rootPath) rootpathEntry.grid(column=1,row=0) rootpathButton = ttk.Button(inputFrame,text="open",command=self.openFileDialog) rootpathButton.grid(column=2,row=0) #新しく階層を作るディレクトリを表示するWidget addPathLabel = ttk.Label(inputFrame,text="TargetDir") addPathLabel.grid(column=0,row=1) addpathEntry = ttk.Entry(inputFrame,state="readonly",textvariable=self.targetDir) addpathEntry.grid(column=1,row=1) addPathButton = ttk.Button(inputFrame,text="delete",command = self.deleteDirectory) addPathButton.grid(column=2,row=1) #追加するディレクトリを表示するWidget addNodeLabel = ttk.Label(inputFrame,text="AddDir") addNodeLabel.grid(column=0,row=2) addNodeEntry = ttk.Entry(inputFrame,textvariable=self.addDir) addNodeEntry.grid(column=1,row=2) addNodeButton = ttk.Button(inputFrame,text="add",command=self.insertDirectory) addNodeButton.grid(column=2,row=2) cpuMax = os.cpu_count() nCpuLabel = ttk.Label(inputFrame,text="nCpu") nCpuLabel.grid(column=0,row=3) nCpu = ttk.Spinbox(inputFrame, textvariable = self.nCpu,from_=1 , to=cpuMax) nCpu.grid(column=1,row=3) # 作成するボタン makeDirButton = ttk.Button(inputFrame,text="makeDirectory",command=self.makeDirectory) makeDirButton.grid(column=2,row=4) return inputFrame # ルートパスのディレクトリを決める def openFileDialog(self): folder = filedialog.askdirectory(); self.rootPath.set(folder) self.logic.setRootPath(folder) # 指定されたディレクトリを反映 def targetDirectory(self,event): self.iid = self.tree.focus() if self.iid : self.targetDir.set(self.tree.item(self.iid,"text")) #指定されたディレクトリに子階層を加える def insertDirectory(self): addDir = self.addDir.get() # ディレクトリ名がない場合は処理しない if addDir != "": children = self.tree.get_children(self.iid) # 同じ階層に同じ名前で作成は不可 for child in children: childname = self.tree.item(child,"text") if childname == addDir: messagebox.showerror("登録エラー","既に登録されています") return self.tree.insert(self.iid,"end",text=self.addDir.get()) #ディレクトリを削除する def deleteDirectory(self): if self.iid == "" or self.iid == self.rootiid: messagebox.showerror("削除エラー","削除する階層を選択してください。\nmakedirectoryToolsディレクトリは削除できません。") return self.tree.delete(self.iid) # ディレクトリを作成する def makeDirectory(self): rootPath = self.rootPath.get() # ルートのパスが存在しない場合は作成できないのでエラーダイアログを出して終了 if rootPath =="": messagebox.showerror("作成エラー","ディレクトリを作成するルートディレクトリを指定してください。") return elif os.path.exists(rootPath) == False: messagebox.showerror("作成エラー","ルートディレクトリが不正です。") return mkpath = os.path.join(rootPath,self.tree.item(self.rootiid,"text")) # 上書きを防ぐため、すでにこのツールで作成されたフォルダがある場合はエラーダイアログを出して終了 if os.path.exists(mkpath): messagebox.showerror("作成エラー","既にmakedirectoryToolsディレクトリが作成されています") return # ロジックのディレクトリ作成アクションを呼び出す # self.logic.makeDirectoryAction(self.tree,self.rootiid) self.logic.makeDirectoryMultiVerAction(self.tree,self.rootiid,self.nCpu.get()) if __name__ == '__main__': master = Tk() master.title("MakeDirectory Tools") MakeDirectoryTools(master) master.mainloop()