最近学了python反射,要知道在java中,反射是可以做很多有意思的事,把java中那套设计思想拿过来,可以把最近学到的一些细节整合为一个界面程序
效果图:
看起来像一个计算器,gif中操作了两个文件:
test0.py
test1.py
看懂了,就能解释为什么gif中看起来像计算器了,因为这两个文件的main方法中都是对数据进行操作,而gif中的操作实现的是利用反射运行test0.py与test1.py中的main方法。
是不是很简单,只要知道反射谁都可以做出来,而该程序最大的特点是可以读取任何python文件,规范是:
1、所有要被程序加载的python文件必须放在与程序同级目录下的source文件夹中(如下图:reflex.py是程序文件,要被加载的python文件test0.py等均放于source文件夹中)
2、每一个要被程序加载的python文件中都必须拥有main()方法,该main方法的作用相当于__name__=='__main__'语句,将会被程序直接反射调用,如test0.py与test1.py(这个设计可能很糟糕,因为直接写死了只能反射main方法。之所以这么设计,主要是觉得根据单一职责原则来讲,一个python文件也应该只有一种实现(java中带过来的思想,已成强迫症),所以在main方法中直接调用本程序的唯一实现,就会很合理,这样说可能有点懵,举个例子:比如,写一个快速排序与一个冒泡排序,应该将其分别放于两个python文件中,而不是全部写在一个文件中,一个文件中包含多种实现会显得职责混乱,使得代码不易读)
当了解了上面的规范后,新增一个python文件:(quick_sort.py 实现快速排序)
第一步:在source文件夹中创建quick_sort.py
第二步:编写排序算法,并添加main方法实现对排序算法的调用
#quick sort
def quick_sort(list,start,end):
left=start
right=end
base=list[(right-left)//2+left]#标准值,大于该值放右边,小于该值放左边
while left<=right:
while left<=right and list[right]>base:#找到右边有小于标准值时停止循环
right-=1
while left<=right and list[left]<base:#找到左边有大于标准值时停止循环
left+=1
if left<=right:#交换
temp=list[right]
list[right]=list[left]
list[left]=temp
left+=1
right-=1
if left>start:#递归排序标准值左边的数组
quick_sort(list,start,right)
if right<end:#递归排序标准值右边的数组
quick_sort(list,left,end)
return list
def main(list):
list=list[1:-1].split(',')#由于界面传入的值为str类型,需要转list
print(quick_sort(list,0,len(list)-1))
第三步:在反射程序中调用该算法
如gif所示,该反射程序完全可以调用刚写的排序程序。
写得好无聊,接下来看看源码,这个就不想一步一步的说了,大多数都是有注释的,直接贴吧(这个源码没有任何需要格外下载的第三方库,直接贴到自己电脑上就能运行,但记得一定要创建一个source文件夹(原因上面有说)):
import os
import tkinter as tk
import tkinter.messagebox
from tkinter import ttk
from tkinter.filedialog import askdirectory
class Main_Win:
#1、创建窗口
def __init__(self,master):
self.win_root=master
self.canvas=None#画布
self.dir_path="%s\\source"%os.getcwd()#默认文件夹路径,当前目录下的source文件夹
self.file_name=tk.StringVar()#文件名
self.attr_value_list=[]#方法的参数集(列表),如f(a,b,c)参数集为[a,b,c]
self.source_text=None#源码显示框
#2、初始化窗口布局
def init_win(self):
#设置窗口标题
self.win_root.title('反射 '+self.dir_path)
#设置窗口大小并居中显示
w=400
h=338
center_window(self.win_root,w,h)
#创建画布设置窗口的大小宽x高+偏移量
bg_color='beige'
self.canvas=tk.Canvas(self.win_root,width=w,height=h-18,bd=0,bg=bg_color,highlightthickness=0)
self.canvas.pack()
#防止用户调整尺寸
self.win_root.resizable(0,0)
#############添加组件#############
#文件名列表选择框
file_name_list_combobox=ttk.Combobox(self.win_root,textvariable=self.file_name)
self.combobox_set_values(file_name_list_combobox,self.get_file_name_list(),0,self.get_file_name)
#修改目录路径按钮
select_dir_button = tk.Button(self.win_root, text ="修改目录",font=('Arial', 12), command =lambda:self.select_dir(file_name_list_combobox))
#源码显示框以及滚动条
scroll = tk.Scrollbar(self.win_root, orient = 'horizontal')
scroll.pack(side = 'bottom', fill = 'x')
self.source_text = tk.Text(self.win_root,font=('Arial', 8),bg=bg_color,xscrollcommand = scroll.set, wrap = 'none')
scroll.config(command = self.source_text.xview)
#反射执行main函数按钮
run_button=tk.Button(self.win_root,text='运行',font=('Arial',12),command =lambda:self.run_function())
#############添加组件到画布中#############
self.canvas.create_window(w-60,30,width=100,height=30,window=select_dir_button)
self.canvas.create_window(103,70,width=200,height=30,window=file_name_list_combobox)
self.canvas.create_window(w//2,203,width=w,height=236,window=self.source_text)
self.canvas.create_window(w-60,70,width=50,height=30,window=run_button)
#############进行消息循环#############
self.win_root.mainloop()
#3、回调函数
def combobox_set_values(self,combobox,list,default_selected,callback):
combobox["values"]=tuple(list)
combobox.current(int(default_selected)) #默认选择
combobox.bind("<<ComboboxSelected>>",callback) #绑定事件
def get_file_name_list(self):
files=['选择文件',]
if self.dir_path:
files=files+os.listdir(self.dir_path)
return files
def get_file_name(self,event):
file_name=self.file_name.get()
self.source_text.delete('1.0','end')
if '.py' in file_name:
file_path=self.dir_path+'\\%s'%file_name
file=open(file_path,mode='r+',encoding='utf-8')
for text in file:
self.source_text.insert('end',text)
def select_dir(self,combobox):
self.dir_path = askdirectory(initialdir =self.dir_path)
self.win_root.title('python测试工具 '+self.dir_path)
self.combobox_set_values(combobox,self.get_file_name_list(),0,self.get_file_name)
def run_function(self):
file_name=self.file_name.get()
if '.py' in file_name:
file_name=file_name[0:-3]
try:
model=__import__(r'source.'+file_name,fromlist=True)#反射添加模块
f=getattr(model,'main',None)#反射获得方法
if f:
parameter_count = f.__code__.co_argcount#获得参数个数
if parameter_count:
list=[]
parameter_namelist=f.__code__.co_varnames#反射获得所有变量的名字
for i in range(parameter_count):
list.append(parameter_namelist[i])
self.set_attr_values(list,f)
else:
f()
else:
self.error_print('main()不存在')
except:
self.error_print(file_name+'加载失败!\n\n请确保模块在程序同级目录下的\nsource文件夹中')
def set_attr_values(self,list,f):
self.attr_value_list=list
win_up = tk.Toplevel(self.win_root)
win_up.title('请输入参数:')
i=0
for i in range(len(list)):
tk.Label(win_up, text=str(list[i])).place(x=10, y=10+40*i)
self.attr_value_list[i]=tk.StringVar()# 将输入的值给变量
entry_attr = tk.Entry(win_up,textvariable=self.attr_value_list[i])#输入框
entry_attr.place(x=130, y=10+40*i)
btn_comfirm = tk.Button(win_up, width=10,font=('Arial',12),text='提交', command=lambda:self.submit_deal(f,win_up))
btn_comfirm.place(x=150, y=10+40*(i+1))
center_window(win_up,300,45+40*(i+1))
def submit_deal(self,f,destroy_win):
for i in range(len(self.attr_value_list)):
attr_value=self.attr_value_list[i].get()#获取输入参数
if attr_value=='':#如果输入值为空,关闭窗口,退出本方法
destroy_win.destroy()
return
self.attr_value_list[i]=attr_value
f(*self.attr_value_list)
destroy_win.destroy()
def error_print(self,err_text):
tkinter.messagebox.showinfo(title='提示', message=str(err_text))
#屏幕居中
def center_window(win,w, h):
#获取屏幕宽、高
ws = win.winfo_screenwidth()
hs = win.winfo_screenheight()
#计算x、y位置
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
win.geometry('%dx%d+%d+%d' % (w, h, x, y))
def main():
root=tk.Tk()
win=Main_Win(root)
win.init_win()
if __name__=='__main__':
main()
:要问自己做这个东西有什么意义,没意义,只是针对这几天对python反射以及tkinter模块的学习进行的一个总结,而写这个软件时最吸引我的地方就是“设计”,它实现了类似于一个框架的功能,以后不管是写哪方面的实现代码,只要遵循了上面的两条规范,就可以在这个软件中实现对其代码的调用。。。。