【Python GUI実用サンプル】フォルダ一括作成ツールを拡張して並列化(tkinter ttk.Spinbox)に対応する

<tkinterトップページに戻る>

使用できる並列数を確認する

まず環境の確認をします。

コマンドプロンプトを起動してpythonを下記コマンドで起動しましょう。

python

python

次に下記コマンドを実行してください。

import os
os.cpu_count()

このコマンドで出力された数が使用できる最大の並列数になります。(私の環境では8です。)

1の場合は並列化に対応しても効果はありません。

nCpu

下記コマンドを入力してpythonはいったん閉じます。

exit()

 

今回使用する主なモジュール

multiprocessing.Pool

ttk.Spinbox

ttk.Spinboxは3.7から導入されているので、それ以前のPythonを使用している場合は通常のSpinboxを使用してください

 

下準備

今回拡張するツールは下記の記事で作成したツールです。

サンプルコードからコードを取得してください。

またファイル名をmakeDirectoryToolsSampleMultiVer.pyとしてください。

 

【Python GUI実用サンプル】Tkinter ttk.Treeviewを使ってフォルダ一括作成ツールを作ってみる

実装

・コア数入力用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.Poolmapを使用します。

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フォルダとその直下(子)のディレクトリは通常通り作成しますが、

孫以降のディレクトリは、子ごとに並列化し作成させています。

サンプル

makeDirectoryToolsSampleMultiVer

サンプルコード

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()

 

あわせて読みたい