在Window程序中嵌入Python

最近有个项目,需在 Windows 中调用 Python,执行脚本功能。功能已完成,但接下来就是 Python 环境部署的问题。主要有2种选择:

  • 程序安装包不自带 Python 环境,由用户在自己机子上安装Python后,再将其配置在程序的 Python 路径中;
  • 程序安装包自带 Python 环境,用户机子可不用安装;

第一种好处是程序会小些,但对用户而言不友好;第二种则程序变大,但比较完整,对用户友好;若增加的大小在可接受范围内,则果断采取第二种。

1. embeddable 包探索

经过种种探索, Python官网 提供了 embeddable 版本,为最小可运行环境,解压完只有 12.9 M,完美适应第二种的需求。

进一步研究,embeddable 版本是将 python 运行所需要的库全部编译为 .pyc 文件,并且把一些常用的库压缩入一个 zip 文件里,这样做有2点好处:

  • 提升了性能:pyc 为编编译过的字节码,可直接由解释器运行;
  • 减少了大小:zip 压缩;

若只是简单地提供了 python 运行环境,至此功能已完全满足。

然而,python 的强大与流行,还在于有大量强大的第三方包,而 embeddable 中不包含其它第三方包,也不包含 pip 等 package 管理工具。

2. embeddable 包扩展

经研究,embeddable 可进一步扩展。如下:

  • 增加 lib/site-packages 文件夹,同完整安装包一样,用于放第三方包,可在此放入 pip 等需要的 package;
  • 修改 python36._pth,加入 "./Lib/site-packages" ,如此 python 解释器就能找到我们添加的 lib/site-packages 包了;
    python36._pth

    研究 python36._pth 可知,embeddable 版本的 python 解释器是读取这里路径,因此也可以自定义 zip 包后,在此加入路径配置。

3. site-packages 预编译(可选)

一般而言,site-packages的包一般是源码,若想进一步提高性能,可预先将 .py 编译为 .pyc 文件,步骤如下(由 python 脚本来处理):

  • 编译 /lib 里的所有文件
import compileall
compileall.compile_dir('Lib/',force=True) 
  • 编译完成后,删除 .py 的源文件;
import os
import shutil

def delete_file(folder):
    for file in os.listdir(folder):
        file_path=os.path.join(folder,file)     
        try:
            if file.endswith('.py'):
                    if os.path.isfile(file_path):
                        os.unlink(file_path)            
            if os.path.isdir(file_path):
                    delete_file(file_path)
        except Exception as e:
            print(e)

delete_file('Lib')  
  • 编译完的 .pyc 文件在 __pycache__ 文件夹,并且文件名带 .cpython-36.pyc 的后缀,需将其拷贝至 __pycache__ 外,并去除后缀中的 .cpython-36;
import os
import shutil
from pathlib import Path

def copy_cache_files(folder):
    for file_name in os.listdir(folder):
        file_path=os.path.join(folder,file_name)    
        print(file_name)
        print(file_path)
        try:
            if os.path.isdir(file_path):
                if file_name==('__pycache__'):
                     parent_dir=Path(file_path).parent
                     print(parent_dir)
                     files=os.listdir(file_path)
                     for pyc_file in files:
                         print(pyc_file)
                         source_file=os.path.join(file_path,pyc_file)
                         dest_file=os.path.join(parent_dir,pyc_file)
                         if dest_file.endswith('cpython-36.pyc'):
                            dest_file=dest_file.replace('cpython-36.pyc','pyc')
                         print(source_file)
                         print(dest_file)
                         shutil.move(source_file,dest_file)
                     os.rmdir(file_path)
                else:
                    copy_cache_files(file_path)
        except Exception as e:
            print(e)

copy_cache_files('Lib')
  • 删除 __pycache__ 文件夹;
import os
import shutil

def delete_cache(folder):
    for file in os.listdir(folder):
        file_path=os.path.join(folder,file)     
        try:
            if file=='__pycache__':
                shutil.rmtree(file_path)
            elif os.path.isdir(file_path):
                    delete_cache(file_path)
        except Exception as e:
            print(e)


delete_cache('Lib')

可先这些脚本保存为不同的 .py 文件,最好注明先后运行的脚本,存放于 embeddable 的目录下。启动 embeddable 的脚本解释器 python.exe ,按步骤输入对应的脚本文件,回车运行即可。为保险起见,可每运行一步,查看 site-packages 的变化。

当然,也可以不预先编译 .pyc ,因为 .pyc 文件会比 .py 大,且python 解释器首次运行,会自动将 .py 编译为 .pyc 文件。

4. 总结

综上,总结一下:

  • 使用 python 官网的 embeddable 版本;
  • 若需要,可新增 zip 包,修改 python36._pth 配置;
  • 若需要,可增加 Lib/site-packages 文件夹,修改 python36._pth 配置;
  • 若需要,可把 Lib/site-packages 里的 .py 文件预先编译为 .pyc 文件;

你可能感兴趣的:(在Window程序中嵌入Python)