平时的 Python 学习过程比较枯燥,想要保持兴趣和提高,就得上手亲自做一些小项目
本次小项目是利用 Python 自带的 tkinter 模块构造简单的 GUI,利用PyMySQL 对数据库进行操作 ,数据存储利用 json模块,这些都是比较基础的东西,做的过程可以逐渐熟悉上手
此工具可应用于多个数据库存在相同的表名等情况下,进行批量操作
开发环境如下:
- Python 3.6.6
- MySQL 8.0
- PyMySQL 0.9.2 (pip即可)
- IDE:Geany
先来看看项目完成的界面
主界面
添加键界面
功能说明
1、左上方是 SQL 语句输入栏,输入语句后点执行按键,执行按键会从本地存储的 json 文件获取数据库信息,连接数据库,然后执行语句,再关闭数据库
2、主界面有 2 个清屏按键,分别为语句清屏和 Info 清屏;语句清屏,顾名思义是将 SQL 语句清除, Info 清屏是将下方的执行信息栏的报错清空
3、退出程序按键,退出该程序
4、添加按键,点击后弹出添加界面,有 6 个输入栏,其中一个带有默认值, 6 栏全部输入信息才能点击确认,否则出现提示框提示将信息填写完整,取消即退出该界面;点击确认后,新的数据库信息将保存到本地 json 文件中,同时将已保存的数据库信息清空,提示更新键更新
5、删除按键,点击后,删除已保存的数据库框中选中的数据库,同时本地 json 文件会删除相应的信息
6、更新按键,点击后,获取本地 json 文件数据,同时将数据更新到上方的框中
7、执行信息栏,在程序报错时,栏中将出现相对应的报错信息
!由于是练手项目,基本功能不影响的情况存在未知BUG,以后在实际应用中修改BUG,再重构优化
话不多说,根据实际开发过程中,下面依开发思路进行分解
1、先根据需求对GUI界面进行分解、排版
- 需要3个标签对信息框介绍——3个Label
- 需要7个按键实现不同的功能——7个Button
- 需要3个框,分别用来输入SQL语句、展示数据库信息、展示操作中发生的必要信息——2个scrolledtext、1个Listbox
我的布局图
PS:实际可以更简单,不需要分这么多块,但也是第一开发,经验不足, Frame可有可无,方便分块,看实际情况
此时应有
from tkinter import *
from tkinter import scrolledtext
def main():
root = Tk()
# Frame
frmA1 = Frame()
frmA2 = Frame()
frmA3 = Frame()
frmB1 = Frame()
frmB2 = Frame()
frmB3 = Frame()
frmC = Frame()
# Label
label_1 = Label(frmA1)
label_2 = Label(frmB1)
label_3 = Label(frmC)
# scrolledtext
scrt_1 = scrolledtext.ScrolledText(frmA2)
scrt_2 = scrolledtext.ScrolledText(frmC)
# Listbox
scrollbar = Scrollbar(frmB2)
listbox = Listbox(frmB2)
# Button
button_1 = Button(frmA3)
button_2 = Button(frmA3)
button_3 = Button(frmA3)
button_4 = Button(frmB3)
button_5 = Button(frmB3)
button_6 = Button(frmB3)
button_7 = Button(frmC)
# 窗口布局
frmA1.grid()
frmA2.grid()
frmA3.grid()
frmB1.grid()
frmB2.grid()
frmB3.grid()
frmC.grid()
label_1.grid()
label_2.grid()
label_3.grid()
scrt_1.grid()
scrt_2.grid()
scrollbar.grid()
listbox.grid()
button_1.grid()
button_2.grid()
button_3.grid()
button_4.grid()
button_5.grid()
button_6.grid()
button_7.grid()
root.mainloop()
if __name__ == '__main__':
main()
将所有需要的控件写出来后,需要对各个控件进行重命名、属性的添加,布局的调整,宽度和高度没有设置好以及grid调用时,一般都会出现不整齐的现象,这些都要需要花时间去慢慢调整,下面是完成后的代码
# 主界面
root = Tk()
root.title('数据库批量处理器')
root.resizable(0, 0)
# Frame
frm_width = 380
frm_height1 = 200
frm_height2 = 50
frmA1 = Frame(width=frm_width, height=frm_height2-5)
frmA2 = Frame(width=frm_width, height=frm_height1)
frmA3 = Frame(width=frm_width, height=frm_height2)
frmB1 = Frame(width=frm_width, height=frm_height2-5)
frmB2 = Frame(width=frm_width, height=frm_height1)
frmB3 = Frame(width=frm_width, height=frm_height2)
frmC = Frame(width=frm_width*2, height=frm_height1+30)
# Label
infotext = '执' + 18*' ' + '行' + 18*' ' + '信' + 18*' ' + '息' + 18*' ' + '栏'
input_label = Label(frmA1, text='SQL 语 句 输 入 栏', width=54, height=2, anchor='center', relief='groove', foreground='red', bd=3)
db_label = Label(frmB1, text='已 保 存 的 数 据 库', width=54, height=2, anchor='center', relief='groove', foreground='red', bd=3)
info_label = Label(frmC, text=infotext, width=90, height=2, foreground='red', relief='groove', bd=3)
# scrolledtext
input_text = scrolledtext.ScrolledText(frmA2, width=51, height=15, wrap=WORD)
info_text = scrolledtext.ScrolledText(frmC, width=105, height=13, wrap=WORD, state='normal')
# Listbox
scr = Scrollbar(frmB2, width=16)
db_list = Listbox(frmB2, width=51, height=11, yscrollcommand=scr.set)
scr.config(command=db_list.yview)
# Button
button_width = 16
button_height = 2
exe_button = Button(frmA3, text='执 行', width=button_width, height=button_height, foreground='blue', command=exe_input, relief='groove')
clear1_button = Button(frmA3, text='语 句 清 屏', width=button_width, height=button_height, foreground='blue', command=clear_input1, relief='groove')
quit_button = Button(frmA3, text='退 出 程 序', width=button_width, height=button_height, foreground='blue', command=quitcommand, relief='groove')
add_button = Button(frmB3, text='添 加', width=button_width, height=button_height, foreground='blue', command=create_add_frame, relief='groove')
del_button = Button(frmB3, text='删 除', width=button_width, height=button_height, foreground='blue', relief='groove', command=del_db)
update_button = Button(frmB3, text='更 新', width=button_width, height=button_height, foreground='blue', relief='groove', command=update_db_info)
clear2_button = Button(frmC, text='Info 清 屏', width=button_width, height=button_height, foreground='blue', command=clear_input2, relief='groove')
# 窗口布局
frmA1.grid(row=0, column=0)
frmA2.grid(row=1, column=0)
frmA3.grid(row=2, column=0)
frmB1.grid(row=0, column=1)
frmB2.grid(row=1, column=1)
frmB3.grid(row=2, column=1)
frmC.grid(row=3, column=0, columnspan=3)
input_label.grid(row=0, column=0, padx=1, pady=1)
db_label.grid(row=0, column=1, padx=1, pady=1)
info_label.grid(row=0, column=0)
input_text.grid(row=0, column=0)
info_text.grid(row=1, columnspan=2)
db_list.grid(row=0, column=0)
scr.grid(row=0, column=1, ipady=80)
exe_button.grid(row=0, column=0, ipadx=1, pady=1)
clear1_button.grid(row=0, column=1, padx=1, pady=1)
quit_button.grid(row=0, column=2, padx=1, pady=1)
add_button.grid(row=0, column=0, padx=1, pady=1)
del_button.grid(row=0, column=1, padx=1, pady=1)
update_button.grid(row=0, column=2, padx=1, pady=1)
clear2_button.grid(row=0, column=1, pady=1)
2、对各Button的功能实现
# 主窗口方法
# 获取本地json数据
json_data = []
def get_json_data():
try:
with open('DB_data.json') as f:
for line in f:
json_data.append(json.loads(line))
return json_data
except:
info_text.insert(END, 'Error: 请检查是否有添加的数据库信息及文件!\n')
# 退出主界面
def quitcommand():
root.quit()
# 清空语句
def clear_input1():
input_text.delete('0.0', END)
# 清空信息
def clear_input2():
info_text.delete('0.0', END)
# 删除listbox中选中的数据库信息,以及本地数据库信息文件的更改
def del_db():
index = db_list.curselection()[0]
db_list.delete(index)
with open('DB_data.json', 'r') as f:
dataList = f.readlines()
del(dataList[index])
with open('DB_data.json', 'w') as f_w:
for line in dataList:
f_w.write(line)
# 获取json文件进行信息的更新
def update_db_info():
get_json_data()
if not db_list.size():
for i in range(len(json_data)):
string = '第' + str(i+1) + '个数据库' + ' ------ ' + 'DBname: ' + json_data[i]['DBname'] + ' ------ ' +'Port: ' + json_data[i]['port'] + '\n'
db_list.insert(END, string)
# 获取json文件数据库的信息,连接,执行语句,关闭连接
def exe_input():
get_json_data()
for data in json_data:
sql = input_text.get('0.0', END)
try:
conn = pymysql.connect(host=data['host'], user=data['user'], password=data['password'], port=int(data['port']), db=data['DBname'], charset=data['charset'])
cursor = conn.cursor()
cursor.execute(sql)
conn.commit()
info_text.insert(END, '操作完成')
except Exception as e:
conn.rollback()
error_info = ('发生错误:' + str(e) + '\n')
info_text.insert(END, error_info)
finally:
cursor.close()
conn.close()
这里我们需要了解Python操作数据库的方法,以及文件操作的知识、json的基本使用,因此我们还要import相应的包
PS:
python 2.7 使用 MySQLdb 进行数据库操作
python 3.x 使用 pymysql 进行数据库操作
3、添加界面实现
# 创建添加二级窗口
def create_add_frame():
# 二级窗口方法
def cancelCommand():
add.destroy()
def confirmCommand():
data = {'host':host_entry.get(), 'user':user_entry.get(), 'password':ps_entry.get(), 'port':port_entry.get(), 'DBname':db_entry.get(), 'charset':charset_entry.get()}
count = 0
for v in data.values():
if v != '':
count += 1
if count == 6:
with open('DB_data.json', 'a', encoding='utf-8') as f:
json.dump(data, f)
f.writelines('\n')
messagebox.showinfo('已完成', '数据库信息已添加!保存的数据将清空,请点击更新键更新!')
db_list.delete(0,END)
add.destroy()
else:
messagebox.showinfo('提示', '请将6个参数填写完整!')
add = Toplevel()
add.title('添加新的数据库')
add.resizable(0, 0)
add.wm_attributes('-topmost',1) # 将add窗口置于root前
# Label
label_width = 20
label_height = 1
host_label = Label(add, text='Host:', width=label_width, height=label_height)
user_label = Label(add, text='User:', width=label_width, height=label_height)
ps_label = Label(add, text='Password:', width=label_width, height=label_height)
port_label = Label(add, text='Port:', width=label_width, height=label_height)
db_label = Label(add, text='DBname:', width=label_width, height=label_height)
charset_label = Label(add, text='Charset:', width=label_width, height=label_height)
# Entry
entry_width = 30
host_entry = Entry(add, width=entry_width, bd=3)
user_entry = Entry(add, width=entry_width, bd=3)
ps_entry = Entry(add, width=entry_width, bd=3)
port_entry = Entry(add, width=entry_width, bd=3)
db_entry = Entry(add, width=entry_width, bd=3)
charset_entry = Entry(add, width=entry_width, bd=3)
host_entry.focus()
charset_entry.insert(END, 'utf8')
# Button
bt_width = 10
bt_height = 2
confirm_button = Button(add, text='确认', width=bt_width, height=bt_height, foreground='blue', command=confirmCommand)
cancel_button = Button(add, text='取消', width=bt_width, height=bt_height, foreground='blue', command=cancelCommand)
# 窗口布局
host_label.grid(row=0, column=0)
user_label.grid(row=1, column=0)
ps_label.grid(row=2, column=0)
port_label.grid(row=3, column=0)
db_label.grid(row=4, column=0)
charset_label.grid(row=5, column=0)
host_entry.grid(row=0, column=1)
user_entry.grid(row=1, column=1)
ps_entry.grid(row=2, column=1)
port_entry.grid(row=3, column=1)
db_entry.grid(row=4, column=1)
charset_entry.grid(row=5, column=1)
confirm_button.grid(row=6, column=0)
cancel_button.grid(row=6, column=1)
添加界面会出现messagebox的使用,需要import,各功能都测试过使用正常,出现的BUG基本不影响使用,编码时间2天完成,以后如有时间,将对此工具进行改进
各部分代码全都展示完整,如果对pymysql和json的基本操作不熟,可以继续往下看,对代码中细节有疑议或有更好方法的、需要源码练习的,请留言,相信我们会交谈甚欢 φ(≧ω≦*)
1、pymysql的基本使用
import pymysql
# 创建连接,除了port是int类型,其他都是str类型
conn = pymysql.connect(host=localhost, user=root, password='123456', port=3306, db=spiders, charset='utf8')
# sql语句
sql = 'SELECT acctid, money FROM bank'
try:
# 创建游标
cursor = conn.cursor()
# 执行sql语句
cursor.execute(sql)
# 对于增删改操作,需要此方法传递数据变化
conn.commit()
except:
# 如果执行出错,需要对数据回滚,回复原样
conn.rollback()
finally:
# 关闭数据库连接,否则占用后台资源
cursor.close()
conn.close()
2、json的基本用法
json虽然易用,但由于第一次使用,还是踩了不少坑
刚开始我是这样保存数据的
with open('x.json', 'a', encoding='utf-8') as f:
json.dump(data, f, indent=4)
利用文本形式打开,是这样
{
'a':'a',
'b':'b'
}
2个数据时
{
'a':'a',
'b':'b'
}{
'c':'c'.
'd':'d'
}
是不是看着很整齐? 这都是indent=4的功劳
取出数据
with open('x.json', 'r', encoding='utf-8') as f:
json.load(f)
你会发现,x.json中只有一个数据时,运行良好,不会报错,但存在多个时,就会出现 extra data 的报错
json进行文件操作有4个方法
1、dump()
2、dumps()
3、load()
4、loads()
有什么区别吗?
1、dump() 将dict转为str形式,并保存到文件中
2、dumps() 将dict转为str形式
3、load() 将str转为dict形式,并从文件中取出
4、loads() 将str转为dict形式
使用dumps
a = {"123":"123"}
json.dumps(a) ——> '{"123":"123"}'
使用loads
b = '{"123":"123"}'
json.loads(b) ——> {"123":"123"}
所以当json文件存在多个数据时,它无法进行转换,你可以简单认为json.load()只能对只拥有一个数据的json文件进行处理,那存在多个数据json文件就无法操作了?聪明的你已经想到先对数据择行分别取出,并分别进行转换
但这样的你要怎么分行处理呢?
{
'a':'a',
'b':'b'
}{
'c':'c'.
'd':'d'
}
第一行就一个 {?
这样格式化的数据是因为我们设置了indent=4,如果我们没有设置这样的参数,实际的保存效果是这样的:
{"host": "localhost", "user": "root", "password": "123456", "port": "3306", "DBname": "spiders", "charset": "utf8"}
因此,当我们需要存取多个json数据时,一定不要设置indent参数,并且在存入时加入\n进行分行
所以最后的获取函数应该这样写
data = []
with open('x.json') as f:
for line in f:
data.append(json.loads(line))
Thank You For Watching...
小知识:如果想运行时想不出现cmd窗口,可以将文件名x.py改成x.pyw哦