目录
导语
程序功能
需要用的工具
需要安装的python包
程序代码
功能类函数Functions
键盘响应
窗体设置
Sql连接
Gui程序打包
结语
参考文献
最近在学英语,新单词需要反复背,所以就想写一个可以自己存储单词的应用。在开始写之前搜索了一下有没有人已经做好了,只查到一篇文章有比较相关的思路,但是功能只有随机抽取单词,以及需要从外部构建好CSV表格导入进去,不够方便,所以得自己写了。刚好tkinter也一直没有学过,就趁这次的机会边学边写。
程序主要分为三个功能区,分别是:
Search模块:用于检索词库中的单词
Upload模块:用于向词库加入新单词
Next模块:用于背单词,由Next和Answer两个按键共同实现
最后呈现的功能如图所示:
python 3.6
postgreSQL(用于存储词库,也可以用excel,但是响应速度慢,个人不建议)
tkinter:构建python中的Gui窗体
sqlalchemy:用于sql的连接
playsound:播放单词发音
urllib.request:从有道的API接口获取单词发音
pyinstaller:将py文件打包成exe
程序的整体结构如图所示
类Functions 是程序的功能集合,由Add_Words(新增单词),Next_Random(随机抽取单词),Play_Sound(播放单词发音),Search_Word(检索单词)和Show_Answer(显示单词释义)五个子函数构成。
这部分遇到的几个问题汇总如下:
class Functions():
def __init__(self):
self.count = 0 # 用于单词计数
self.sql = Connect('English_words') # 数据库连接
self.url = 'http://dict.youdao.com/dictvoice?type=0&audio=' # 有道云单词发音API
data = self.sql.load('word_list')
self.word_list = list(set(data['单词'])) # 词库单词列表
def Next_Random(self):
'''
从词库中随机抽取单词
'''
global text, word, counter, url, ans
self.count += 1
word = choice(self.word_list) # 随机选择一个词
text.configure(text=word) # 在text控件中显示单词
counter.configure(text='第' + str(self.count) + '个单词') # 在counter控件中显示计数
ans.config(text='')
root.update_idletasks()
try: # 尝试播放单词发音
self.Play_Sound()
except:
pass
def Search_Word(self):
'''
在词库中检索单词并返回释义、发音
'''
global key, word
word = key.get().lower() # 从key控件中获取输入
text.configure(text=word) # 在text控件中显示单词
try: # 若词库中有该词,则显示释义并播放声音,否则 提示未收录
self.Show_Answer()
self.Play_Sound()
except:
ans.config(text='未收录')
def Show_Answer(self):
"""
显示单词释义
"""
global word, ans
l = self.sql.load('word_list where 单词 = \'' + word + '\'') # 从词库中检索单词
# 单词有可能因为词性不同,在词库中出现多次,因此每次根据单词在词库中按照where语句进行茶盅,并显示多层释义
if len(l) == 1: # 若只有单层释义,直接拼接词性词义等信息
c = l.iloc[0]
s = c['词性'] + ' ' + c['词义']
if c['补充信息'] is not None:
s = s + '\n' + '\n'.join(c['补充信息'].split(';'))
else: # 否则,依次拼接词性词义等信息
s = ''
for k, c in l.iterrows():
s = s + str(k + 1) + '. ' + c['词性'] + ' ' + c['词义']
if c['补充信息'] is not None:
s = s + '\n ' + '\n '.join(c['补充信息'].split(';'))
s += '\n'
ans.configure(text=s)
root.update_idletasks()
def Play_Sound(self):
"""
播放单词发音
"""
global word
playsound.playsound(u'D:\\python\\py3\\interest\\English_words\\sound\\' + word + u'.mp3')
def Add_Words(self):
"""
向词库增加新单词
"""
global e1, e2, e3, e4, text, word, counter, ans
word = e1.get().lower() # 为方便大小写输入,将词库中单词统一为小写字母
gender = e2.get()
meaning = e3.get()
info = e4.get()
if word is '':
messagebox.showinfo("提示:", "请先输入内容!")
return
data = pd.DataFrame({'单词': [word], '词性': [gender], '词义': [meaning], '补充信息': [info]})
if ' ' in word: # 根据HTML访问规则,将词组中的空格符号进行替换
urlword = word.replace(' ', '%20')
else:
urlword = word
urllib.request.urlretrieve(url + urlword,
r'D:\python\py3\interest\English_words\sound\%s.mp3' % word) # 从接口获取发音并存在本地文件夹
self.sql.upload(data, 'word_list', if_exists='append')
e1.delete(0, END) # 上传后自动情况输入框文本
e2.delete(0, END)
e3.delete(0, END)
e4.delete(0, END)
data = self.sql.load('word_list')
self.word_list = list(set(data['单词']))
return
这一模块是使得可以用键盘按键控制程序,方便使用。主要用到的是tkinter的bind语句功能。
class Running_process():
def __init__(self, func):
self.func = func
def eventhandler(self, event):
"""
:param event:键盘事件
:return: 响应键盘事件
"""
if event.keysym == 'Up':
self.func.Add_Words()
e1.focus_set()
elif event.keysym == 'Down':
self.func.Next_Random()
elif event.keysym == 'Return':
self.func.Search_Word()
elif event.keysym == 'space':
self.func.Show_Answer()
窗体设置由函数main实现,也是程序运行的主函数。本文用到的tkinter控件主要有Button,Text,Label和Enrty,网上有许多关于这几个控件的参数和功能的基本介绍,可以在使用前先了解。
写这一部分的时候,由于边写边学tkinter怎么用,再加上自己的强迫症,所以遇到了很多细节设置问题,找到了一些比较有用的网页:
def main():
'''
word: 当前检索单词
root: TK窗体
counter: 计数器显示控件
text: 单词文本显示控件
ans: 单词释义显示控件
key: 单词检索输入控件
e1, e2, e3, e4: 新单词输入控件
:return: 窗体构建与功能实现
'''
global e1, e2, e3, e4, ans, count, text, counter, key, root
# ======窗体基本设置=========================================
root = Tk()
root.iconbitmap('rainbow.ico')
root.title('彩虹单词本')
root.geometry("850x350")
root.resizable(0, 0)
# ======调用类==============================================
func = Functions()
proc = Running_process(func)
# ======背单词模块===========================================
counter = Label(root, fg='red', anchor='se', font=('Arial', 13))
text = Label(root, text='开始背单词吧', font=('Arial', 15, 'bold'), width=20,
height=10, wraplength=280)
ans = Label(root, text='', font=('Arial', 13), width=30, wraplength=280,
justify='left', height=10, anchor=W)
Button(root, text="Next", bg='#43A102', fg='white', font=font.Font(family='Helvetica', size=10, weight="bold"),
width=15, command=func.Next_Random).grid(row=7, column=0)
Button(root, text="Answer", bg='#A2B700', fg='white', font=font.Font(family='Helvetica', size=10, weight="bold"),
width=15, command=func.Show_Answer).grid(row=7, column=1)
counter.grid(row=1, column=0)
text.grid(row=3, column=0, rowspan=4)
ans.grid(row=3, column=1, rowspan=4, sticky=W, columnspan=2)
Button(root, text="Sound", bg='#EED205', fg='white', font=font.Font(family='Helvetica', size=10, weight="bold"),
width=15, command=func.Play_Sound).grid(row=7, column=2)
# =======搜索单词模块========================================
Label(root, text="搜索单词:", anchor=E).grid(row=0, column=0)
key = Entry(root)
Button(root, text="Search", bg='#FF8C05', fg='white', font=font.Font(family='Helvetica', size=10, weight="bold"),
width=15, command=func.Search_Word).grid(row=0, column=2)
key.grid(row=0, column=1)
# =======上传单词模块========================================
Label(root, text="请输入单词:").grid(row=3, column=3)
Label(root, text="请输入词性:").grid(row=4, column=3)
Label(root, text="请输入词义:").grid(row=5, column=3)
Label(root, text="请输入补充信息:").grid(row=6, column=3)
e1 = Entry(root)
e2 = Entry(root)
e3 = Entry(root)
e4 = Entry(root)
upload = Button(root, bg='#FDD283', fg='white', font=font.Font(family='Helvetica', size=10, weight="bold"),
text="Upload", width=15, command=func.Add_Words).grid(row=7, column=3, columnspan=2)
e1.grid(row=3, column=4)
e2.grid(row=4, column=4)
e3.grid(row=5, column=4)
e4.grid(row=6, column=4)
# ========键盘响应模块========================================
btn = Button(root, text='button')
btn.bind_all('', proc.eventhandler)
# =============================================================
root.mainloop() # 进入消息循环
我将sql连接单独写成了Sql_Connect.py文件,使用的时候直接调用,并通过upload或者load语句与数据库进行交互,响应速度比较快,几乎可以忽略不计。
from sqlalchemy import create_engine
import pandas as pd
import io
import datetime
from string import Template
class Connect(object):
def __init__(self,database):
self.database=database
self.pg_username="postgres"
self.pg_password='******'
self.pg_port="5432"
self.pg_host="localhost"
self.engine = create_engine('postgresql+psycopg2://' + self.pg_username + ':' + self.pg_password + '@' + self.pg_host + ':' + str(
self.pg_port) + '/' + database)
print('连接数据库成功')
# query_sql = 'select * from $arg1'
# query_sql = Template(query_sql) # template方法
def load(self,file):
query_sql = Template('select * from $arg1')
df = pd.read_sql_query(query_sql.substitute(arg1=file),self.engine) # 配合pandas的方法读取数据库值
return df
# 配合pandas的to_sql方法使用十分方便(dataframe对象直接入库)
def upload(self,file,table_name, if_exists='fail'):
if type(file)==str:
df=pd.read_excel(file)
print('获取本地表完成')
else:
df=file
df['update_time']=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
string_data_io = io.StringIO()
df.to_csv(string_data_io, sep='|', index=False)
pd_sql_engine = pd.io.sql.pandasSQL_builder(self.engine)
table = pd.io.sql.SQLTable(table_name, pd_sql_engine, frame=df,
index=False, if_exists=if_exists)
table.create()
string_data_io.seek(0)
# string_data_io.readline() # remove header
with self.engine.connect() as connection:
with connection.connection.cursor() as cursor:
copy_cmd = "COPY %s FROM STDIN HEADER DELIMITER '|' CSV" % table_name
cursor.copy_expert(copy_cmd, string_data_io)
connection.connection.commit()
print('数据上传完成')
程序打包成exe文件可以方便日常使用,无需每次都打开pycharm。可以用cxfreeze或者pyinstaller,cxfreeze我没有运行成功,pyinstaller打包成功,但是exe打开需要肉眼可见的5-10秒,搜了一下似乎没有好的解决方法。
完成pyinstaller的安装后,通过CMD进入程序所在目录,并输入语句:pyinstaller -F -w ****.py,再回车后即可自动打包。
程序中若有设置icon,打包完成后有可能无法使用,并提示:failed to execute script. 解决方法可参考: pyinstaller打包
完整版程序已发布在Github: https://github.com/Trista2017/English_words/tree/master
2020-04-20 新增 根据A4纸背单词法 新增模块
2020-04-20 新增 新增单词已掌握按键,sql_connect中新增delete函数
2020-04-20 新增 新增日志记录
2020-04-19 新增 LabelFrame的使用(玄学调位置= =)
1. 珠玉在前