用python的tkinter写一个简单的概率计算器(适合新手练习)

用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()

效果呢就是这个样子(有点丑,凑合着看吧):

用python的tkinter写一个简单的概率计算器(适合新手练习)
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)

然后就可以实现以下效果:
用python的tkinter写一个简单的概率计算器(适合新手练习)
再为各个部分写上计算按钮

#这部分需要导入第一部分写的计算模块,我命名为了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参数

写完了!

看看成果(狗头)
用python的tkinter写一个简单的概率计算器(适合新手练习)
错了再来

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()

用python的tkinter写一个简单的概率计算器(适合新手练习)
真丑(闭嘴)

三.打包 (翻车了) (已解决)(解决了一半)

用pyinstaller顺利打包,本以为万事大吉,但当我满怀激动地点开exe文件
用python的tkinter写一个简单的概率计算器(适合新手练习)
用python的tkinter写一个简单的概率计算器(适合新手练习)
!!!!!!!!!
我的内心:what the f**k
于是我上网一搜,发现是pyinstaller的常见错误只需要balabala~balabala就可以了(抱歉我没看懂)
于是我又尝试了cx-freeze、py2exe都没有成功(枯)
希望有高人能指点一下
——————————————2020年4月5日20:00更新——————————————
经过高人的指点(没错高人就是我自己),已经解决打包问题(但是在其他电脑上依旧无法运行,再次求高人指点)
用python的tkinter写一个简单的概率计算器(适合新手练习)
我查了一些资料用以下方法解决让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下的文件复制到打包目录

用python的tkinter写一个简单的概率计算器(适合新手练习)
这样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文件运行

仍然存在的问题:
在其他电脑上出现异常
用python的tkinter写一个简单的概率计算器(适合新手练习)

四.拓展

目前来说功能比较单一,后续学了新内容的话我会慢慢加上去(flag已立)

——————————————————分割线——————————————————
首先感谢你能看到这里吧。本人大学生一枚,非计算机相关专业。这是我第一篇博客,可能写得有点烂。以后我会继续写博客跟踪我的学习状态,同时跟大家分享一些我的学习心得。有问题欢迎下方留言!

上一篇:wireshark使用教程及TCP三次握手实例


下一篇:C#JSON解析而不是R