用python的tkinter写一个简单的概率计算器
最近刚学python, 肯定还是有很多不足的地方,欢迎大神指正~
2020年4月4日晚,一位小伙正在苦逼地赶着他的概率论作业。看着用过了一次又一次的公式,他陷入了沉思:每个公式只需要两三个参数,然后进行对应的计算就能拿到结果——这不正好适合编程解决吗?再用tkinter编辑个ui界面发给同学用,岂不是救众生于水火,功德无量吗?就这样,他幻想着同学们投来崇拜的目光,眼(zui)角流着幸福的泪(kou)水,错过了交作业的最后一点时间……
一.编写计算文件
工欲善其事,必先写工具。最重要的部分当然是怎么正确地把结果算出来。
我目前学的部分并没有多少,所以我写了二项分布、泊松分布、指数分布、正态分布四个部分。其中正态分布的概率密度函数不能直接积分,所以我用了第三方的库scipy来计算积分。这个库安装还挺麻烦的,需要先安装numpy+mkl,需要的小伙伴可以自行百度。只要不是编程小白,应该都能看懂下面的代码,就不多解释了。
# base.py
E = 2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178
PI = 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825
from scipy.integrate import quad #导入积分函数
#计算阶乘
def far(n):
s = 1
for i in range(1, n+1):
s *= i
return s
#计算最大公因数
def fractor(a, b):
if b:
temp = a % b
return fractor(b, temp)
else:
return a
#约分
def splify(a, b):
x = fractor(a, b)
return [int(a/x), int(b/x)]
#排列公式
def pmt(m, n):
s = 1
for i in range(n-m+1, n+1):
s *= i
return s
#组合公式
def cmb(m, n):
return pmt(m, n) // far(m)
#二项分布
def Binomial(p, n):
dic = {}
for i in range(n + 1):
dic[i] = cmb(i, n) * p**i * (1-p)**(n-i)
return dic
#泊松分布
def Poisson(l, k):
return l**k * E**(-1*l) / far(k)
#指数分布
def Exponential(la, x):
return 1 - E**(-1 * la * x)
#标准正态分布
def Nor(x):
coefficient = 1 / ((2 * PI)**(1/2))
f = lambda t : E**(-1 * t*t / 2)
v, err = quad(f, -1000, x) # err为误差
return coefficient * v
#正态分布
def Normal(mu, sigma, x):
z = (x - mu) / sigma
return Nor(z)
if __name__ == "__main__":
pass
二.编写窗口程序
其实有了上面那一部分对于我来说已经够用了,但一来刚学了tkinter想练练手,二来谁不想在同学面前装个B 露一手呢?先是编写根窗口,我设置了四个选项
from tkinter import *
root = Tk()
root.geometry("400x350")
root.title("概率计算器 v1.0内测版")
Button(root, text="二项分布", width=20, height=3).pack(pady=10)
Button(root, text="泊松分布", width=20, height=3).pack(pady=10)
Button(root, text="指数分布", width=20, height=3).pack(pady=10)
Button(root, text="正态分布", width=20, height=3).pack(pady=10)
root.mainloop()
效果呢就是这个样子(有点丑,凑合着看吧):
OK,然后在再写四个子窗口。我最开始想的是给每一个部分写一个函数来启动子窗口,但写了两个发现代码重复率很高,于是就把重合代码封装成了一个类。需要区分的是二项分布直接输出一个分布表,我用了参数hei区分,而正态分布需要接收三个参数,用n_flag区分。
class App:
def __init__(self, app, t, l1, l2, hei=1, n_flag=False):
app.title(t)
text = Text(app, width=30, height=hei)
text.grid(row=0, column=0, columnspan=6 if n_flag else 4, padx=15, pady=10)
Label(app, text=l1).grid(row=1, column=0, padx=10, pady=10, sticky=E)
Label(app, text=l2).grid(row=1, column=2, padx=10, pady=10, sticky=E)
#用StringVar()对象储存参数值
v1 = StringVar()
v2 = StringVar()
e1 = Entry(app, textvariable=v1, width=5)
e1.grid(row=1, column=1, padx=5, pady=10, sticky=W)
e2 = Entry(app, textvariable=v2, width=5)
e2.grid(row=1, column=3, padx=5, pady=10, sticky=W)
#区别对待正态分布
if n_flag:
v3 = StringVar()
Label(app, text="x:").grid(row=1, column=4, padx=10, pady=10, sticky=E)
e3 = Entry(app, textvariable=v3, width=5)
e3.grid(row=1, column=5, padx=5, pady=10, sticky=W)
然后就可以实现以下效果:
再为各个部分写上计算按钮
#这部分需要导入第一部分写的计算模块,我命名为了base.py
#接上文
def b_cho():
text.delete("1.0", END)
cho_v1, cho_v2 = v1.get(), v2.get()
if cho_v1 and cho_v2:
dict = base.Binomial(float(cho_v2), int(cho_v1))
text.insert(INSERT, "X P\n")
s = 0
for e in dict:
text.insert(INSERT, str(e)+" "+str(dict[e])+"\n")
s += dict[e]
text.insert(INSERT, f"s {s}")
else:
text.insert(INSERT, "请先输入数值!")
def p_cho():
text.delete("1.0", END)
cho_v1, cho_v2 = v1.get(), v2.get()
if cho_v1 and cho_v2:
text.insert(INSERT, str(base.Poisson(float(cho_v1), int(cho_v2))))
else:
text.insert(INSERT, "请先输入数值!")
def e_cho():
text.delete("1.0", END)
cho_v1, cho_v2 = v1.get(), v2.get()
if cho_v1 and cho_v2:
text.insert(INSERT, str(base.Exponential(float(cho_v1), float(cho_v2))))
else:
text.insert(INSERT, "请先输入数值!")
def n_cho():
text.delete("1.0", END)
cho_v1, cho_v2, cho_v3 = v1.get(), v2.get(), v3.get()
if cho_v1 and cho_v2:
text.insert(INSERT, str(base.Normal(float(cho_v1), float(cho_v2), float(cho_v3))))
else:
text.insert(INSERT, "请先输入数值!")
if t == "二项分布":
Button(app, text="计算", width=10, command=b_cho).grid(row=3, column=0, columnspan=4, padx=30, pady=10)
elif t == "泊松分布":
Button(app, text="计算", width=10, command=p_cho).grid(row=3, column=0, columnspan=4, padx=30, pady=10)
elif t == "指数分布":
Button(app, text="计算", width=10, command=e_cho).grid(row=3, column=0, columnspan=4, padx=30, pady=10)
elif t == "正态分布":
Button(app, text="计算", width=10, command=n_cho).grid(row=3, column=0, columnspan=6, padx=30, pady=10)
最后将子窗口通过函数关联到根窗口就大工告成了
def BINOMIAL():
bin = Toplevel()
App(bin, "二项分布", "个数:", "单个概率:", hei=15)
bin.mainloop()
def POISSON():
poi = Toplevel()
App(poi, "泊松分布", "λ:", "k:")
poi.mainloop()
def EXPONENTIAL():
exp = Toplevel()
App(exp, "指数分布", "λ:", "x:")
exp.mainloop()
def NORMAL():
nor = Toplevel()
App(nor, "正态分布", "μ:", "σ:", n_flag=True)
nor.mainloop()
#需要在root窗口里的Button组件设置command参数
写完了!
看看成果(狗头)
错了再来
from tkinter import *
import base #第一部分写的那个
class App:
def __init__(self, app, t, l1, l2, hei=1, n_flag=False):
app.title(t)
text = Text(app, width=30, height=hei)
text.grid(row=0, column=0, columnspan=6 if n_flag else 4, padx=15, pady=10)
Label(app, text=l1).grid(row=1, column=0, padx=10, pady=10, sticky=E)
Label(app, text=l2).grid(row=1, column=2, padx=10, pady=10, sticky=E)
v1 = StringVar()
v2 = StringVar()
e1 = Entry(app, textvariable=v1, width=5)
e1.grid(row=1, column=1, padx=5, pady=10, sticky=W)
e2 = Entry(app, textvariable=v2, width=5)
e2.grid(row=1, column=3, padx=5, pady=10, sticky=W)
if n_flag:
v3 = StringVar()
Label(app, text="x:").grid(row=1, column=4, padx=10, pady=10, sticky=E)
e3 = Entry(app, textvariable=v3, width=5)
e3.grid(row=1, column=5, padx=5, pady=10, sticky=W)
def b_cho():
text.delete("1.0", END)
cho_v1, cho_v2 = v1.get(), v2.get()
if cho_v1 and cho_v2:
dict = base.Binomial(float(cho_v2), int(cho_v1))
text.insert(INSERT, "X P\n")
s = 0
for e in dict:
text.insert(INSERT, str(e)+" "+str(dict[e])+"\n")
s += dict[e]
text.insert(INSERT, f"s {s}")
else:
text.insert(INSERT, "请先输入数值!")
def p_cho():
text.delete("1.0", END)
cho_v1, cho_v2 = v1.get(), v2.get()
if cho_v1 and cho_v2:
text.insert(INSERT, str(base.Poisson(float(cho_v1), int(cho_v2))))
else:
text.insert(INSERT, "请先输入数值!")
def e_cho():
text.delete("1.0", END)
cho_v1, cho_v2 = v1.get(), v2.get()
if cho_v1 and cho_v2:
text.insert(INSERT, str(base.Exponential(float(cho_v1), float(cho_v2))))
else:
text.insert(INSERT, "请先输入数值!")
def n_cho():
text.delete("1.0", END)
cho_v1, cho_v2, cho_v3 = v1.get(), v2.get(), v3.get()
if cho_v1 and cho_v2:
text.insert(INSERT, str(base.Normal(float(cho_v1), float(cho_v2), float(cho_v3))))
else:
text.insert(INSERT, "请先输入数值!")
if t == "二项分布":
Button(app, text="计算", width=10, command=b_cho).grid(row=3, column=0, columnspan=4, padx=30, pady=10)
elif t == "泊松分布":
Button(app, text="计算", width=10, command=p_cho).grid(row=3, column=0, columnspan=4, padx=30, pady=10)
elif t == "指数分布":
Button(app, text="计算", width=10, command=e_cho).grid(row=3, column=0, columnspan=4, padx=30, pady=10)
elif t == "正态分布":
Button(app, text="计算", width=10, command=n_cho).grid(row=3, column=0, columnspan=6, padx=30, pady=10)
def BINOMIAL():
bin = Toplevel()
App(bin, "二项分布", "个数:", "单个概率:", hei=15)
bin.mainloop()
def POISSON():
poi = Toplevel()
App(poi, "泊松分布", "λ:", "k:")
poi.mainloop()
def EXPONENTIAL():
exp = Toplevel()
App(exp, "指数分布", "λ:", "x:")
exp.mainloop()
def NORMAL():
nor = Toplevel()
App(nor, "正态分布", "μ:", "σ:", n_flag=True)
nor.mainloop()
root = Tk()
root.geometry("400x350")
root.title("概率计算器 v1.0内测版")
Button(root, text="二项分布", width=20, height=3, command=BINOMIAL).pack(pady=10)
Button(root, text="泊松分布", width=20, height=3, command=POISSON).pack(pady=10)
Button(root, text="指数分布", width=20, height=3, command=EXPONENTIAL).pack(pady=10)
Button(root, text="正态分布", width=20, height=3, command=NORMAL).pack(pady=10)
root.mainloop()
真丑(闭嘴)
三.打包 (翻车了) (已解决)(解决了一半)
用pyinstaller顺利打包,本以为万事大吉,但当我满怀激动地点开exe文件
!!!!!!!!!
我的内心:what the f**k
于是我上网一搜,发现是pyinstaller的常见错误只需要balabala~balabala就可以了(抱歉我没看懂)
于是我又尝试了cx-freeze、py2exe都没有成功(枯)
希望有高人能指点一下
——————————————2020年4月5日20:00更新——————————————
经过高人的指点(没错高人就是我自己),已经解决打包问题(但是在其他电脑上依旧无法运行,再次求高人指点)
我查了一些资料用以下方法解决让exe文件可以在自己电脑上运行(pyinstaller):
1.将第一部分写的base.py复制到python的\Lib\site-packages文件夹下
因为这个模块是自己写的,所以pyinstaller不会自动导入这个模块
我的绝对路径放出来供大家参考:C:\Users\我\AppData\Local\Programs\Python\Python38\Lib\site-packages
2.将python目录下的\Lib\site-packages\numpy\DLLs下的文件复制到打包目录
这样pyinstaller就能正常打包了
3.在打包目录打开cmd或powershell打包
输入pyinstaller 第二部分写的那个.py -w
进行打包,可能需要按一两次回车
4.将第2步所有文件中的7个文件移入新dist文件夹中
包括:
libopenblas.dll
mkl_avx.dll
mkl_avx2.dll
mkl_avx512.dll
mkl_core.dll
mkl_def.dll
mkl_intel_thread.dll
如果嫌麻烦或者仍然运行不了可以全部移入
5.双击dist文件夹下的exe文件运行
仍然存在的问题:
在其他电脑上出现异常
四.拓展
目前来说功能比较单一,后续学了新内容的话我会慢慢加上去(flag已立)
——————————————————分割线——————————————————
首先感谢你能看到这里吧。本人大学生一枚,非计算机相关专业。这是我第一篇博客,可能写得有点烂。以后我会继续写博客跟踪我的学习状态,同时跟大家分享一些我的学习心得。有问题欢迎下方留言!