用 python+tkinter+有道云API 写一个自用的背单词软件

目录

导语

程序功能

需要用的工具

需要安装的python包

程序代码

功能类函数Functions

键盘响应

窗体设置

Sql连接

Gui程序打包

结语

参考文献


导语

最近在学英语,新单词需要反复背,所以就想写一个可以自己存储单词的应用。在开始写之前搜索了一下有没有人已经做好了,只查到一篇文章有比较相关的思路,但是功能只有随机抽取单词,以及需要从外部构建好CSV表格导入进去,不够方便,所以得自己写了。刚好tkinter也一直没有学过,就趁这次的机会边学边写。

程序功能

程序主要分为三个功能区,分别是:

Search模块:用于检索词库中的单词

Upload模块:用于向词库加入新单词

Next模块:用于背单词,由Next和Answer两个按键共同实现

最后呈现的功能如图所示:

需要用的工具

python 3.6

postgreSQL(用于存储词库,也可以用excel,但是响应速度慢,个人不建议)

需要安装的python包

tkinter:构建python中的Gui窗体

sqlalchemy:用于sql的连接

playsound:播放单词发音

urllib.request:从有道的API接口获取单词发音

pyinstaller:将py文件打包成exe

程序代码

程序的整体结构如图所示

功能类函数Functions

类Functions 是程序的功能集合,由Add_Words(新增单词),Next_Random(随机抽取单词)Play_Sound(播放单词发音)Search_Word(检索单词)Show_Answer(显示单词释义)五个子函数构成。

这部分遇到的几个问题汇总如下:

  1. 单词发音获取:有道API获取单词发音十分方便,直接通过API接口加单词访问,参数type表示美音(0)/英音(1),audio表示对应单词。本来希望通过爬虫直接实现连网播放,尝试未果,最后还是老老实实地将单词下载到本地文件夹,每次查询单词的时候调用playsound包实现MP3的发音播放。接口也支持词组,但是词组里有空格,在访问网页时,需要将词组中的空格替换为‘%20’.
  2. 发音包的选择:在实现发音功能的时候尝试了许多包,playsound, pyaudio, playgame等,许多包运行出错,playsound运行成功但是响应速度大概需要肉眼可见的0.5秒。这边可以再试一下其他的包效果是否会更好。
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怎么用,再加上自己的强迫症,所以遇到了很多细节设置问题,找到了一些比较有用的网页:

  1. Button的字体设置:和Text可以直接在语句中设置字体不同,Button需要通过调用tkinter.font包实现字体设置;
  2. Button事件的参数传递:Button可以用command参数绑定对应的功能函数。在功能函数参数传递方面,我直接用global变量略去了参数传递的问题,有看到文章采用的是lambda函数进行传递,也可以尝试这种方法。
  3. 控件的布局:布局这部分花了最多时间来满足强迫症的需求。tkinter主要有三种布局方式,分别是pack, grid以及place。
  • pack也就是把控件作为组合进行摆放,但是控件组合(frame)内部位置不好调整,倒腾了很久还是放弃了;
  • place的参数是空间的像素坐标,设置起来比较麻烦,灵活度低;
  • grid也就是按照网格进行定位:把整个界面划分为若干块,再通过row和column两个参数确定控件的位置。可以参考这篇文章,解释得比较readable。 
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('<KeyPress>', proc.eventhandler)
    # =============================================================
    root.mainloop()  # 进入消息循环

Sql连接

我将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('数据上传完成')

Gui程序打包

程序打包成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. 珠玉在前 

  • 5
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值