关于Python打包(Pyinstaller与Nuitka)

Published: 2017-09-04

Tags: Python Rust

本文总阅读量

(一)Pyinstaller 打包

前两天用Python做了一个小工具,调用一个DLL文件,获取公司平板设备标识码,然后将标识码复制到粘贴板

用Python做挺好做的,使用 ctypes 调用DLL,使用 pyperclip 包操作粘贴板

#!/bin/env python
# -*- coding: utf-8 -*-

import tkinter.ttk as ttk
import tkinter as tk
import pyperclip
import ctypes
import sys


def get_x3id():

    '''获取X3唯一标识'''

    lib= ctypes.CDLL('PcInfo.dll')
    r = lib.GetHylinkDeviceId()
    value = ctypes.c_char_p(r).value
    x3id = bytes2str(value)
    print("x3id: ", x3id, type(x3id))
    return x3id


def bytes2str(message):

    '''
    convert string to bytes, py2, py3
    '''

    if sys.version_info < (3, 4):
        print("py2")
        return message
    else:
        print("py3")
        return message.decode("utf-8")


class Application(ttk.Frame):

    def __init__(self, master=None):
        ttk.Frame.__init__(self, master) 
        self.textBox()
        self.button()
        self.grid()
        self.start()


    def start(self):

        '''校验是否在X3上使用'''

        lib= ctypes.CDLL('PcInfo.dll')
        v = lib.IsHylinkProduct()

        if v == -1:
            self.outputText.insert(tk.INSERT, '')
            self.outputText.see("end")
            msg = "\n 请在X3上使用本工具\n"
            self.b1.config(state=tk.DISABLED)
        else:
            self.x3id = get_x3id()
            msg = "\n 标识码: " + self.x3id + '\n'

        self.outputText.insert(tk.INSERT, msg)
        self.outputText.see("end")


    def copy_and_close(self):

        pyperclip.copy(self.x3id)
        spam = pyperclip.paste()

        app.quit()


    def button(self):

        self.button_msg = tk.StringVar()

        self.button_msg.set('复制到粘贴板并关闭')

        self.b1 = ttk.Button(self, textvariable=self.button_msg, command=self.copy_and_close)
        self.b1.grid(row=1, column=0, columnspan=2, ipadx=60, ipady = 12, padx=(10, 0), pady=(0, 20))
        # ipadx=70, ipady = 12


    def textBox(self):

        self.outputText = tk.Text(self, width=40, height=5)
        self.outputText.grid(row=0, column=0, columnspan=2, padx=(30, 30), pady=(30, 20))


app = Application()
app.mainloop()

因为python2与python3在编码上边有些差异,所以使用 bytes2str 函数做了一下转换

Python程序要给别人使用,所以需要打个包,不然其它没有python环境的电脑是无法使用的

就这样,我用 pyinstaller 对程序进行打包,生成了一个exe文件

这个程序7M大小,测试过程中,启动速度有点慢,7.36秒才能加载完成,使用起来等待还挺明显的

最开始我以为是GUI拖慢的速度,使用界面,然后点击“复制到粘贴板并关闭”,其实没有界面也是没有关系的

于是我把GUI的代码去掉,做了个无GUI版本的,证实我的猜测是错误的,打包大小还是那么大,运行的速度也还是那么慢,影响性能的瓶颈是因为使用了pyinstaller打包

(二)Nuitka 打包

在网上无意间发现了这个工具。我在虚拟机中安装了32位的python,并且安装了vs2008,它把python代码转换成python调用C API的形式,然后使用C编译器进行编译,官网说,它比Cython快一点点,最终会生成可执行程序

nuitka --standalone --windows-disable-console .\get_x3id.py
  • standalone 参数是把需要的库都放在可执行程序的当前文件夹,便于分发
  • windows-disable-console 默认启动会从cmd启动,带有一个黑窗口,使用这个参数就可以去掉了

这次生成的exe文件打开速度快很多,基本上1s就可以打开,相比于pyinstaller,它的速度快了很多,但是它生成的不是单一的exe文件,分发的时候需要打包整个文件夹

(三)番外,Rust重构

在我发现 Nuitka 之前,我为了能让这个小工具速度快些费了一些心思,可是pyinstaller着实性能有点差...

于是我想着用一门性能好点的语言重写一下,反正就两个函数,加载DLL、复制内容到粘贴板

前阵学习Rust,一直在看书,还没写过什么东西,正好拿来练手

extern crate clipboard;
use clipboard::ClipboardProvider;
use clipboard::ClipboardContext;

extern crate libloading;
use libloading::{Library, Symbol};

use std::ffi::CStr;
use std::str;

type GetHylinkDeviceId = unsafe fn() -> *const i8;

fn copy_to_clipboard(x3id: String) {
    let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap();
    println!("{:?}", ctx.get_contents());
    ctx.set_contents(x3id);
}

fn get_x3id() -> String {

    let library_path = "PcInfo.dll";
    println!("Loading add() from {:?}", library_path);

    let lib = Library::new(library_path).unwrap();

    let x3id = unsafe {

        let func: Symbol<GetHylinkDeviceId> = lib.get(b"GetHylinkDeviceId").unwrap();

        let c_buf: *const i8 = func();
        let c_str: &CStr = unsafe { CStr::from_ptr(c_buf) };
        let str_slice: &str = c_str.to_str().unwrap();
        let str_buf: String = str_slice.to_owned();
        str_buf

    };
    x3id
}

fn main() {

    // 从DLL中获取X3标识码
    let x3id: String = get_x3id();
    println!("{:?}", x3id);

    // 将标识码复制到粘贴板
    copy_to_clipboard(x3id)

}

Rust 写起来比C/C++舒服很多,不过感觉还是没有python来的顺畅,可能是因为不熟练的缘故

使用cargo创建的工程,生成单独的exe文件,大小也很良心,除了外部自己的dll,只有1.58M

最后留个坑,不知道cargo如何把自己的dll打包到exe里面,这样分发使用就更方便了


<这里以后来填坑>