与JavaScript中的eval函数类似,python中的eval函数也可以将字符串转换为代码执行。这一特性十分适合编写一个支持表达式输入的计算器。今天老师布置一个课后作业,设计一个计算器,设计要求是用控制台或GUI编写计算器程序,支持运算优先级,识别正负号,并且可以利用上次计算结果继续运算。于是我就想到了python语言,脑子里蹦出一个eval函数。进行其实后来才知道老师的本意是要我们做一个有限状态机,分析出计算机的各个状态,然后编程实习。但他事先又没说,于是我就曲解了。写下两行代码就初步实现了:
while True:
print(str(eval(input("输入表达式:\n")))
end
运行结果如下:
但是这样做有一个致命缺点,eval函数可以解析任何字符串作为命令,这就意味着可以导入os库后进行文件操作,这样的权限是万万不可以给的,况且也没有进行异常处理,输入一个非法表达式就会报错退出。
因此,上正则表达进行语法检查,判断新表达式 / 继续运算,造函数,强行装逼。
从2行代码深入浅出就得到了一下100行。
程序定义了2个全局变量:str型exprs,用于存储输入的字符串,sum保存上次计算结果
BracketUnbalanceCheck(str_in)函数用来检测括号的平衡
CyntaxErrorCheck(str_in)函数进行语法检测,判断是否包含 a-zA-Z<>,.?~!@#$%"这些字符,函数内使用了正则表达式来匹配
然后针对继续上次结果计算还是使用新表达计算,分别编写了两个函数:ContinueCompute()和NewCompute().
在main()中实现字符串的输入,调用上述两个语法检查函数进行合法性检查,再根据表达式的第一位是否为运算符,分配给ContinueCompute()或NewCompute()进行计算输出。在这两个函数中,还使用了try except语句进行了异常检测,主要防止0除。
至此,一个“玩不坏”的程序就完成了。
我们自嘲说,自己用的程序只要几行就可以了,但是写给用户用,就需要数十倍的规模,因为他们总能碰到奇形怪状的问题。
完整的代码如下:
#!/usr/bin/env python
# -*- coding: cp936 -*-
#**********calculator.py***********
#功能描述:支持+-×/,指数运算(^),
# 支持包含括号的长表达式输入
#作者:南理工 电光学院
# 沈国锋
#******2016/10/29 夜, 于30舍********
#*********CopyRight(c) @2016********
import re
exprs=''
sum=0
def PrintLicense():
print("\
calculator.py,Code With Python3.4\n\
**********控制台计算器***********\n\
·支持+-×/运算,指数运算(^),\n\
·支持包含括号的长表达式输入\n\
·自动识别连续运算或以新表达式运算\n\
·完备的异常信息提示与处理\n\
********2016/10/29 夜, 于30舍*****\n\
********CopyRight(c) 2016********\n\
")
#BracketUnbalanceCheck(str_in):括号平衡检测函数,若不平衡,打印提示信息并返回1
#输入参数:str_in 需要检查的字符串
#返回值:括号不平衡返回1,否则返回-1
def BracketUnbalanceCheck(str_in):
num=0
for i in str_in:
if i=='(':
num+=1
elif i==')':
num-=1
if num!=0:#括号不平衡
print("错误01:输入的括号不平衡,请重新输入\n")
return 1
else:#括号平衡
return -1
#CyntaxErrorCheck(str_in):语法检测函数,检测是否包含 a-zA-Z<>,.?~!@#$%";:这些字符
#输入参数:需要检查的字符串
def CharErrorCheck(str_in):
if re.findall(r'[&a-zA-Z<>,?~!@#$%";:]',str_in):#正则表达式校验输入字符串的合法性
print('错误02:表达式包含非法字符,请重新输入\n')
return 1
else:
return -1
#InputExpression()函数,要求输入表达式,然后进行括号平衡检查和
#字符串合法性的初步检查,不合法则要求继续输入,直到正确为止
#然后通过判断表达式开始的符号是不是运算符,来判断继续运算还是开始新式运算
#无输入值,直接操作全局变量exprs
#返回字符串'continue'和'new',分别代表继续计算和另起新式
def InputExpression():
global exprs
while not(CharErrorCheck(exprs)==-1 and BracketUnbalanceCheck(exprs)==-1):#直到得到一个合法的表达式
exprs=input("继续输入表达式:\n")
m=re.match(r'^[\^/\*+\-\\]\w*',exprs)#匹配输入字符串的第一个字符是否是运算符,判断继续运算还是开始新计算
if m:
#print("continue")
return 'continue'
else:
#print('new')
return 'new'
#函数ContinueCompute():接着上次的值继续计算。例如输入+1*2
#无输入输出值,直接操作全局变量exprs和sum
def ContinueCompute():
global exprs
global sum
exprs=exprs.replace('^','**') #将^替换为**
exprs=str(sum)+exprs
try:
sum=eval(exprs)
print(exprs)
print("="+str(sum))
except SyntaxError:
print("错误03:[终极错误]续上次结果计算时出现语法错误,程序返回\n")
except ZeroDivisionError:
print("错误05:除数不能为0,程序返回\n")
#函数NewCompute():开始一个新的表达式计算,例如(1+2)*2
#没有输入输出值,直接修改全局变量exprs和sum
def NewCompute():
global exprs
global sum
#print('当前字符串'+exprs)
exprs=exprs.replace('^','**') #将^替换为**
try:
sum=eval(exprs)
print(exprs)
print("="+str(sum))
except SyntaxError:
print("错误04:[终极错误]使用新表达式进行计算时出现语法错误,程序返回\n")
except ZeroDivisionError:
print("错误05:除数不能为0,程序返回\n")
def main():
global exprs
'''sum=0
exprs=input("输入表达式:")
re.sub(r'^\^$', '**', exprs) #将^替换为**
while CharErrorCheck(exprs) or BracketUnbalanceCheck(exprs):#直到得到一个合法的表达式
exprs=input("输入表达式:\n")
while True:
try:
s=eval(exprs)
print(str(s))
except SyntaxError:
print("语法错误")
'''
PrintLicense()
while True:
exprs=input("输入表达式:")
flag=InputExpression()
if flag=='new':
NewCompute()
else:
ContinueCompute()
end
main()
为了在没有python环境的机器上也能够运行这个代码,还需要打包成exe。
目前python中常用的打包程序有pywin32和Pyinstaller,据网上说,后者更加方便。所以就使用这个啦。
按照pyinstaler可以使用pip,pip是python的一个包安装工具,按照python时会默认安装,就在python/scripts目录下,可以找到一个pip.exe文件。打开命令行程序,用cd命令转换到该目录后,键入
pip install pyinstaller
程序会自动下载所需的包,不出一分钟就安装成功了。
安装后pyinstaller也在scripts目录下
使用pyinstaller的命令参数如下:
F, --onefile | produce a single file deployment (see below). |
-D, --onedir | produce a single directory deployment (default). |
-K, --tk | include TCL/TK in the deployment. |
-a, --ascii | do not include encodings. The default (on Python versions with unicode support) is now to include all encodings. |
-d, --debug | use debug (verbose) versions of the executables. |
-w, --windowed, --noconsole | |
| (Windows only) |
-c, --nowindowed, --console | |
| (Windows only) |
-s, --strip | the executable and all shared libraries will be run through strip. Note that cygwin's strip tends to render normal Win32 dlls unusable. |
-X, --upx | if you have UPX installed (detected by Configure), this will use it to compress your executable (and, on Windows, your dlls). See note below. |
-o DIR, --out=DIR | |
| directory. If not specified, and the current directory is Installer's root directory, an output subdirectory will be created. Otherwise the current directory is used. |
-p DIR, --paths=DIR | |
| set base path for import (like using PYTHONPATH). Multiple directories are allowed, separating them with the path separator (';' under Windows, ':' under Linux), or using this option multiple times. |
--icon=<FILE.ICO> | |
| file.ico to the executable's resources. (Windows only) |
--icon=<FILE.EXE,N> | |
| n-th incon in file.exe to the executable's resources. (Windows only) |
-v FILE, --version=FILE | |
| (Windows only) |
-n NAME, --name=NAME | |
| name |
翻译成中文就是:
-F 制作独立的可执行程序
-D 制作出的档案存放在同一个文件夹下(默认值)
-K 包含TCL/TK(对于使用了TK的,最好加上这个选项,否则在未安装TK的电脑上无法运行)
-w 制作窗口程序
-c 制作命令行程序(默认)
-X 制作使用UPX压缩过的可执行程序(推荐使用这个选项,需要下载UPX包,解压后upx.exe放在Python(非PyInstaller)安装目录下,下载upx308w.zip)
-o DIR 指定输出SPEC文件路径(这也决定了最后输出的exe文件路径)
--icon=[ICO文件路径] 指定程序图标
-v [指定文件] 指定程序版本信息
-n [指定程序名] 指定程序名称
一个典型的命令是
pyinstaller -F calculator.py
这时的图标是默认的python大蟒蛇,修改图标需要使用icon参数。
使用icon参数设置图标:
在win平台下,需要使用多个不同分辨率的ico图片,包括128×128 64 48 32 16,注意分辨率顺序从大到小,否则不能正常显示。制作ico文件可以使用脚本工具png2ico来实现。命令如下:
1. png2ico myicon.ico icon_128x128.png icon_64x64.png icon_48x48.png icon_32x32.png icon_16x16.png
最后得到的exe文件: