memopy

pythonで作ってみました的なブログ

python クラスの定義と継承とは

python クラスの定義と継承とは

前回の記事は、tkinterのドキュメントのサンプルスクリプトとして記載されている"A Simple Hello World Program"が難しすぎる。という記事を投稿した。
前の記事
python tkinterのクラス化手法によるGUI作成 - memopy
今回は、pythonのクラスとはなにか。クラスの継承とはなにかについてまとめたいと思う。
特に、クラスの継承はpython2とpython3で方法が一部異なるため、この点を整理したい。

続きを読む

python tkinterのクラス化手法によるGUI作成

python tkinterのドキュメント"A Simple Hello World Program"が難しすぎる

皆さんはpython tkinterのドキュメントをご覧になったことがあるだろうか。
※python3の場合
25.1. tkinter — Python interface to Tcl/Tk — Python 3.6.1 documentation
※python2の場合
24.1. Tkinter — Python interface to Tcl/Tk — Python 2.7.13 documentation

A Simple Hello World Program

この中にA Simple Hello World Programというサンプルスクリプトがある。
しかし、このサンプルスクリプトpython初心者にとって非常に難しい。

※python3の場合

import tkinter as tk

class Application(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.pack()
        self.create_widgets()

    def create_widgets(self):
        self.hi_there = tk.Button(self)
        self.hi_there["text"] = "Hello World\n(click me)"
        self.hi_there["command"] = self.say_hi
        self.hi_there.pack(side="top")

        self.quit = tk.Button(self, text="QUIT", fg="red",
                              command=root.destroy)
        self.quit.pack(side="bottom")

    def say_hi(self):
        print("hi there, everyone!")

root = tk.Tk()
app = Application(master=root)
app.mainloop()

※python2の場合

from Tkinter import *

class Application(Frame):
    def say_hi(self):
        print "hi there, everyone!"

    def createWidgets(self):
        self.QUIT = Button(self)
        self.QUIT["text"] = "QUIT"
        self.QUIT["fg"]   = "red"
        self.QUIT["command"] =  self.quit

        self.QUIT.pack({"side": "left"})

        self.hi_there = Button(self)
        self.hi_there["text"] = "Hello",
        self.hi_there["command"] = self.say_hi

        self.hi_there.pack({"side": "left"})

    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.pack()
        self.createWidgets()

root = Tk()
app = Application(master=root)
app.mainloop()
root.destroy()       

いずれにせよ、このスタンドアロンスクリプトを実行すると、次のGUIが作成され、ボタンをクリックすると、インタプリタコンソールにhi there, everyone!と、いかにも英語圏の挨拶が表示されるプログラムとなっている。
f:id:memopy:20170608172803p:plain

このサンプルスクリプトは、A Simple Hello World Programという位置付けでありながら、次の点で初心者にとっては非常に難しいものである。

また次のような遠因も輪をかけてこのA Simple Hello World Programを難しくしている。

  • そもそも、tkinterの日本語ドキュメントや文献が少ない
  • 文献によって、手続き型で記述しているものと、オブジェクト指向によって記述しているものがあり、両者を混在させて参考にできない

私もそうであったが、最初は、インターネットや書籍に公開されているスクリプトをコピーして、自分のパソコンで動かすであろう。
そして、そのあと、自分なりにいろいろ変更を加えてみて、GUIがどのように変化するのか学習するものである。
しかし、pythonはご存知のとおり、日本語のドキュメントが少ないし、さらにtkinterはもっと日本語ドキュメントが少ない。そして、python2系と3系では、クラス化の手法がかなり異なるため、文献によっては自分の環境では動作せず行き詰る。
私もこのtkinterは特に行き詰ったため、その点を整理したいと思う。

続きを読む

pythonで複数行のcsvファイルを1行にする(正規化)

pythonで複数行のcsvファイルを1行にする(正規化)

今回も前回に引き続き、pythoncsvファイルの編集を行う。
前回の記事はこち
pythonでcsvファイルの編集をする - memopy

今回は、複数行のレコードを1行にするいわゆる正規化の方法について紹介する。
下図のようなcsvファイルを想定する。
f:id:memopy:20170608153634p:plain
このように必要な情報が1行にまとまっていない、情報の繰り返しがあるデータはデータベースでは非常に扱いづらい。
これを1行にし、繰り返しをなくすことを正規化という。

続きを読む

pythonでcsvファイルの編集をする

pythoncsvファイルの編集をする

今回、職場で後輩から「csvファイルをArcGISに読ませる前処理で編集、加工しないといけないんですけど、pythonでできますか?」という質問があった。
csvファイルの列を結合したり、入れ替えたり、文字を置換したり、特定の行だけ削除したり・・・
簡単にできるのでここでも紹介する。

※下の図は、csvファイルをExcelで開いた例
f:id:memopy:20170605192055p:plain
※注:編集後の電話番号はExcelで開くと、数値型に変換されて先頭の0がなくなるが、見やすくするために加工した。

続きを読む

⑫ 画面遷移の処理を定義(最終回)【python tkinter sqlite3で家計簿を作る】

⑫ 画面遷移の処理を定義(最終回)【python tkinter sqlite3で家計簿を作る】

家計簿アプリもほとんど完成に近づいてきた。
今回は各画面の遷移の処理を実装する。
f:id:memopy:20170604184024p:plain

メインスクリプトに対して、2つの画面を表示する関数し、その関数の中にさらに各ボタンが押下されたときのコールバック関数を定義している。
関数の状況を概観すると次のスクリプトになる。

# -*- coding:utf-8 -*-

def create_gui():
    "登録画面を表示する関数"
        def select_button():
        "表示ボタンが押下されたときに表示画面を表示する"
            pass
        def qui_button():
        "終了ボタンが押下されたときにアプリを終了する"       
            pass
        def qui_button():
        "登録ボタンが押下されたときにデータをDBに登録する"      
            pass   
        # ここに登録画面のGUI部分の定義
        pass

def select_gui():
    "表示画面を表示する関数"
        def create_button():
        "登録ボタンが押下されたときに表示画面を表示する"
            pass
        def qui_button():
        "終了ボタンが押下されたときにアプリを終了する"      
            pass
        def select_button():
        "表示ボタンが押下されたときに表にデータを表示する"       
            pass
        # ここに表示画面のGUI部分の定義
        pass

# このスクリプトが実行されたときに最初に登録画面を表示する
create_gui()

それぞれ、passと記載した箇所に、今までに作成したコードを転記していく。

続きを読む

⑪ コールバック関数の定義【python tkinter sqlite3で家計簿を作る】

⑪ コールバック関数の定義【python tkinter sqlite3で家計簿を作る】

今回は、「表示」ボタンが押下されたときのコールバック関数の定義をする。
この処理は、期間に入力された日付に応じてレコードを絞り込むものである。

前回までに作成したGUI
f:id:memopy:20170604103013p:plain

コールバック関数に必要な処理は

  1. Treeviewのアイテム(item)を全て削除する
  2. 期間に入力された値をSELECT文のWHERE句に組み込む
  3. 再度、Treeviewのアイテムを表示

となる。

Treeviewのアイテムを全て削除する

Treeviewのitemを全て削除するには、deleteメソッドを用いる。
deleteメソッドには、削除するアイテムを指定しなければならない。
この指定には、itemを指定しなければならない。
全てのitemを取得するには、tree.get_childern()を用いる。
tree.get_childern()はリストの値で返ってくるため、for文で全てのitemを削除する。

for i in tree.get_children()
    tree.delete(i)

また、結合(starred expression)の機能を用いて、シンプルに記述することができる。

tree.delete(*tree.get_children())

期間に入力された値をSELECT文のWHERE句に組み込む

SELECT文のWHERE句にBETWEEN演算子を用いて、開始日と終了日を指定する。

sql = """
SELECT acc_date,item_name,amount
FROM acc_data as a,item as i
WHERE a.item_code = i.item_code AND
acc_date BETWEEN '{}' AND '{}'
ORDER BY acc_date
""".format(start,end)

ただし、開始日と終了日が空欄だった場合エラーになるので、デフォルト値を指定する必要がある。

if start == "":
    start = "1900/01/01"
if end == "":
    end = "2100/01/01"
sql = """
SELECT acc_date,item_name,amount
FROM acc_data as a,item as i
WHERE a.item_code = i.item_code AND
acc_date BETWEEN '{}' AND '{}'
ORDER BY acc_date
""".format(start,end)

最後にこのSQLを発行して、Treeviewのアイテムに追加すればよい。
このコールバック関数をまとめると次のようになる。

# 表示ボタンが押されたときの処理
def select_sql(start,end):
    # treeviewのアイテムをすべて削除
    tree.delete(*tree.get_children())
    # 開始日と終了日が空欄だったらデフォルト値の設定
    if start == "":
        start = "1900/01/01"
    if end == "":
        end = "2100/01/01"
    #SELECT文の作成
    sql = """
    SELECT acc_date,item_name,amount
    FROM acc_data as a,item as i
    WHERE a.item_code = i.item_code AND
    acc_date BETWEEN '{}' AND '{}'
    ORDER BY acc_date
    """.format(start,end)
    # ツリービューにアイテムの追加
    i=0
    for r in c.execute(sql):
        # 金額(r[2])を通貨形式に変換
        r = (r[0],r[1],"¥{:,d}".format(r[2]))
        tree.insert("","end",tags=i,values=r)
        if i & 1:
            tree.tag_configure(i,background="#CCFFFF")
        i+=1    

処理をテストする。
f:id:memopy:20170604113554p:plain
それぞれ、期間を入力して、その期間に応じたレコードを表示させることができた!

以上のコールバック関数を組み込んだ表示画面GUIスクリプトは次のようになる。

# -*- coding: utf-8 -*-

import tkinter as tk
import tkinter.ttk as ttk
import sqlite3

# 表示ボタンが押されたときの処理
def select_sql(start,end):
    # treeviewのアイテムをすべて削除
    tree.delete(*tree.get_children())
    # 開始日と終了日が空欄だったらデフォルト値の設定
    if start == "":
        start = "1900/01/01"
    if end == "":
        end = "2100/01/01"
    #SELECT文の作成
    sql = """
    SELECT acc_date,item_name,amount
    FROM acc_data as a,item as i
    WHERE a.item_code = i.item_code AND
    acc_date BETWEEN '{}' AND '{}'
    ORDER BY acc_date
    """.format(start,end)
    # ツリービューにアイテムの追加
    i=0
    for r in c.execute(sql):
        # 金額(r[2])を通貨形式に変換
        r = (r[0],r[1],"¥{:,d}".format(r[2]))
        tree.insert("","end",tags=i,values=r)
        if i & 1:
            tree.tag_configure(i,background="#CCFFFF")
        i+=1    
    
# 空のデータベースを作成して接続する
dbname = "database.db"
c = sqlite3.connect(dbname)
c.execute("PRAGMA foreign_keys = 1")

# rootフレームの設定
root = tk.Tk()
root.title("家計簿アプリ")
root.geometry("400x500")

# メニューの設定
frame = tk.Frame(root,bd=2,relief="ridge")
frame.pack(fill="x")
button1 = tk.Button(frame,text="入力")
button1.pack(side="left")
button2 = tk.Button(frame,text="表示")
button2.pack(side="left")
button3 = tk.Button(frame,text="終了")
button3.pack(side="right")

# 入力画面ラベルの設定
label1 = tk.Label(root,text="【表示画面】",font=("",16),height=2)
label1.pack(fill="x")

# 期間選択のラベルエントリーの設定
frame1 = tk.Frame(root,pady=15)
frame1.pack()
label2 = tk.Label(frame1,font=("",14),text="期間 ")
label2.pack(side="left")
entry1 = tk.Entry(frame1,font=("",14),justify="center",width=12)
entry1.pack(side="left")
label3 = tk.Label(frame1,font=("",14),text=" ~ ")
label3.pack(side="left")
entry2 = tk.Entry(frame1,font=("",14),justify="center",width=12)
entry2.pack(side="left")

# 表示ボタンの設定
button4 = tk.Button(root,text="表示",
                    font=("",16),
                    width=10,bg="gray",
                    command=lambda:select_sql(entry1.get(),entry2.get()))
button4.pack()

# ツリービューの作成
tree = ttk.Treeview(root,padding=10)
tree["columns"] = (1,2,3)
tree["show"] = "headings"
tree.column(1,width=100)
tree.column(2,width=75)
tree.column(3,width=100)
tree.heading(1,text="日付")
tree.heading(2,text="内訳")
tree.heading(3,text="金額")

# ツリービューのスタイル変更
style = ttk.Style()
# TreeViewの全部に対して、フォントサイズの変更
style.configure("Treeview",font=("",12))
# TreeViewのHeading部分に対して、フォントサイズの変更と太字の設定
style.configure("Treeview.Heading",font=("",14,"bold"))

# SELECT文の作成
sql = """
SELECT acc_date,item_name,amount
FROM acc_data as a,item as i
WHERE a.item_code = i.item_code
ORDER BY acc_date
"""
# ツリービューにアイテムの追加
i=0
for r in c.execute(sql):
    # 金額(r[2])を通貨形式に変換
    r = (r[0],r[1],"¥{:,d}".format(r[2]))
    tree.insert("","end",tags=i,values=r)
    if i & 1:
        tree.tag_configure(i,background="#CCFFFF")
    i+=1
# ツリービューの配置
tree.pack(fill="x",padx=20,pady=20)

# メインループ
root.mainloop()

今回はここまで!
いよいよ次回が最終回。最後に画面遷移の処理を定義する。

質問や記事の誤りがありましたらコメントお願いします。

2017/6/4追記:sqlite3は、日付型(date型)というデータ型がない。日付として例えばBETWEEN演算子などの処理を認識させるためには、
'YYYY-MM-DD'
'YYYY-MM-DD hh:mm:ss'
の形式で記述しなければならない。
入力形式にYYYY/MM/DDと入力されていたら、replaceメソッドで'/'を'-'に置き換える処理が必要になる。

前の記事
⑩ 複数のウィジェットを配置【python tkinter sqlite3で家計簿を作る】
次の記事
⑫ 画面遷移の処理を定義(最終回)【python tkinter sqlite3で家計簿を作る】