Python tkinter 设计pickle文件编辑器

在Python中, pickle是用于储存Python对象的模块。但pickle生成的文件是二进制类型, 不容易打开。
为此, 自己制作了一个小型pickle文件编辑器。
效果图

1.pickle 基础

  • pickle.load(file)

    从已打开的 file object 文件 中读取打包后的对象。

  • pickle.loads(data)

    重建并返回一个对象的封存表示形式 data 的对象层级结构。 与load()不同data 是bytes类型。

import pickle

with open('data.pickle', 'rb') as f:# 注意是rb模式
    data = pickle.load(f)
  • pickle.dump(obj, file, protocol=None)

    将打包好的对象 obj 写入已打开的 file 文件对象。protocol参数指定协议, 也就是写入文件的数据格式。

  • pickle.dumps(obj, protocol=None)

    将 obj 打包以后的对象作为 bytes 类型直接返回,而不是将其写入到文件。

import pickle
data = {
    'a': [1, 2.0, 3, 4+6j],
    'b': ("作者", b"CSDN"),
    'c': {None, True, False}
}
with open('data.pickle', 'wb') as f:# 注意是wb模式, 因为数据为二进制
    pickle.dump(data, f)

2.程序代码实现

1.导入tkinter等相关模块
import pickle,sys,os,re,pprint,subprocess
import pickletools
import tkinter as tk
import tkinter.ttk as ttk
import tkinter.messagebox as msgbox
import tkinter.filedialog as dialog
from tkinter.scrolledtext import ScrolledText
from io import StringIO
2.创建窗口及控件

创建窗口及控件的代码:

_TITLE="pickle数据文件编辑器"
_editors=[]
_pickle_file="*.pkl; *.pickle"
_FONT="宋体 12 normal"
_FILETYPE="pickle文件(%s)" % _pickle_file,_pickle_file
__version__='1.1.3'

class PklEditor:
    def __init__(self,master=None,filename=''):
        self.master=master or tk.Tk()
        self.master.title(_TITLE)
        self.master.protocol("WM_DELETE_WINDOW",self.ask_for_save)
        try:
            self.master.iconbitmap("pickle.ico")
        except:pass
        self.create_widgets(self.master)
        self.file_changed=False
        self.vars={}
        exec("from collections import *",self.vars)
        self.filename=''
        if filename:self.openfile(filename)
    def create_widgets(self,master):
        self.toolbar=tk.Frame(master)
        self.toolbar.pack(side=tk.BOTTOM)
        self.newbtn=ttk.Button(self.toolbar,text="新建",command=self.new)
        self.newbtn.pack(side=tk.LEFT)
        self.openbtn=ttk.Button(self.toolbar,text="打开",
                                command=self.ask_for_open)
        self.openbtn.pack(side=tk.LEFT)
        self.savebtn=ttk.Button(self.toolbar,text="保存",
                                command=self.save,state=tk.DISABLED)
        self.savebtn.pack(side=tk.LEFT)
        self.menubtn=ttk.Button(self.toolbar,text="菜单")
        self.menubtn.bind("<B1-ButtonRelease>",self.showmenu)
        self.menubtn.pack(side=tk.LEFT)
        menubtn=self.menubtn
        self.quitbtn=ttk.Button(self.toolbar,text="退出",
                                command=self.master.destroy)
        self.quitbtn.pack(side=tk.LEFT)

        self.text=ScrolledText(master,undo=True,font=_FONT,
                               width=70,height=22)
        self.text.pack(expand=True,fill=tk.BOTH)
        self.text.bind("<Key>",lambda event:self.master.after(80,self.text_change))
        self.text.focus_force()
        self.create_menu(self.text)
3.创建菜单
    def create_menu(self,master):
        self.menu=tk.Menu(master,tearoff=False)
        self.menu.add_command(label="剪切",
                         command=lambda:self.text_change() == master.event_generate("<<Cut>>"))
        self.menu.add_command(label="复制",
                         command=lambda:master.event_generate("<<Copy>>"))
        self.menu.add_command(label="粘贴",
                         command=lambda:self.text_change() == master.event_generate("<<Paste>>"))
        self.menu.add_separator()
        self.menu.add_command(label="代码检查",command=self.check_code,
                              state=tk.DISABLED)
        self.menu.add_command(label="用二进制模式打开",state=tk.DISABLED,
                              command=self.open_in_binarymode)
        self.menu.add_command(label="分析pickle文件内部结构",command=self.make_dis)
        master.bind("<Button-3>",self.showmenu)
    def showmenu(self,event):
        self.menu.post(event.x_root,event.y_root)
4.打开及保存文件

这是主要的打开和保存文件的代码。

    @classmethod
    def new(cls):
        _editors.append(cls())
    def ask_for_open(self):
        filename=dialog.askopenfilename(
            master=self.master,
            filetypes=[_FILETYPE,("所有文件",'*')])
        if os.path.isfile(filename):
            if self.filename!=filename and self.text.get("1.0",tk.END)[:-1]:
                new=PklEditor()
                new.openfile(filename)
            else:self.openfile(filename)
    def openfile(self,filename):
        try:
            with open(filename,"rb") as f:
                self.menu.entryconfig("用二进制模式打开",state=tk.NORMAL) #存在文件
                obj=pickle.load(f)
                self.text.delete("1.0",tk.END)
                contents=pprint.pformat(obj)
                self.text.insert(tk.INSERT,contents)
                self.import_modules(contents)
                self.filename=filename
                self.set_title()
                self.file_changed=False
                self.menu.entryconfig("代码检查",state=tk.NORMAL)
                self.savebtn['state']=tk.NORMAL
        except Exception as err:
            msgbox.showinfo(type(err).__name__,
                            "无法打开文件: "+str(err))
        self.text.focus_force()
    def ask_for_save(self,quit=True):
        if self.file_changed:
            retval=msgbox.askyesnocancel("文件尚未保存",
                                         "是否保存{}的更改?".format(
                                             self.filename or "当前文件"))
            if retval is not None:
                if retval==True:self.save()
            else:return 0  #0:cancel
        if quit:self.master.destroy()
    def save(self):
        if not self.filename:
            self.filename=dialog.asksaveasfilename(
                master=self.master,
                defaultextension='.pkl',
                filetypes=[_FILETYPE, ("所有文件",'*')])
            if not self.filename:return -1 # -1 标识未保存
        res = self._save()
        if res!=-1:self.menu.entryconfig("用二进制模式打开",state=tk.NORMAL)
        return res
    def _save(self):
        text=self.text.get("1.0",tk.END)[:-1]
        if not text:return
        self.import_modules(text)
        try:
            obj=eval(text,self.vars)
            f=open(self.filename,"wb")
            pickle.dump(obj,f)
            f.close()
            self.file_changed=False
        except Exception as err:
            msgbox.showinfo(type(err).__name__,
                            "无法保存文件{}: {}".format(
                            (", 您输入的数据格式有问题"
                            if type(err)==SyntaxError else ''),err))
            return -1
        else:self.set_title()
        def text_change(self,event=None):
        self.file_changed=True
        text=self.text.get("1.0",tk.END)[:-1]
        state = tk.NORMAL if text.strip() else tk.DISABLED
        self.menu.entryconfig("代码检查",state=state)
        self.savebtn['state']=state
        self.set_title()
5.其他功能

下面是一些其他功能(就不说了)

    def import_modules(self,contents):
        # 自动检测输入内容中的xxx.xxx文字
        # 如: 这样用户可直接输入re.compile, 引用re模块的compile方法
        pat=re.compile("([A-Z]+|[a-z]+|[0-9]+)\\.([A-Z]+|[a-z]+|[0-9]+)")
        for modname,*_ in re.findall(pat,contents):
            try:
                exec("import %s"%modname,self.vars)
            except ImportError:
                msgbox.showinfo('',"无法导入模块: %s" % modname)
    def check_code(self):
        contents=self.text.get("1.0",tk.END)[:-1]
        self.import_modules(contents)
        try:
            eval(contents,self.vars)
        except Exception as err:
            msgbox.showinfo(type(err).__name__,
                            "您的代码有错误:\n"+str(err))
        else:
            msgbox.showinfo('代码检查',"您的代码没有错误。")
    def set_title(self):
        if self.file_changed:
            self.master.title("%s - *%s*" % (_TITLE,self.filename))
        else:
            self.master.title("%s - %s" % (_TITLE,self.filename))
    def open_in_binarymode(self):
        # pynotepad.exe参见gitcode.net/qfcy_/python/-/tree/master/dist/pynotepad ,源代码参见文章blog.csdn.net/qfcy_/article/details/118399185
        if os.path.isfile("pynotepad.exe"):
            cmd = ("pynotepad.exe", self.filename)
        elif os.path.isfile("..\\pynotepad\\pynotepad.exe"):
            cmd = ("..\\pynotepad\\pynotepad.exe", self.filename)
        else:
            msgbox.showinfo('','缺少组件pynotepad.py或pynotepad.exe')
            return
        try:subprocess.Popen(cmd)
        except Exception as err:
            msgbox.showinfo(type(err).__name__,
                    "无法打开: "+str(err))
    def make_dis(self):
        if self.save() != -1:
            fout = StringIO()
            try:
                with open(self.filename,'rb') as f:
                    pickletools.dis(f,out = fout)
            except Exception as err:
                msgbox.showinfo(type(err).__name__,str(err))
                return
            fout.seek(0)
            result = fout.read()

            box = tk.Toplevel(self.master)
            text = tk.Text(box)
            text.pack(expand=True,fill=tk.BOTH)
            text.insert('1.0',result)
6.主程序

main()函数编写:

def main():
    exe = os.path.split(sys.executable)
    if exe[1] not in ('python.exe','pythonw.exe'):# 程序已打包为exe
        try:os.chdir(exe[0])
        except OSError:pass
    if len(sys.argv)>1 and sys.argv[1]!='': # 修复用''空参数调用程序的bug
        for arg in sys.argv[1:]:
            if os.path.isfile(arg):
                root=tk.Tk()
                editor=PklEditor(root,filename=arg)
                _editors.append(editor)
    else:
        _editors.append(PklEditor())
    tk.mainloop()

if __name__ == "__main__":main()
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qfcy_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值