【Python三种打包方式】分别使用PyInstaller & setupTools & 拷贝环境实现项目打包
文章目录
1、setupTools和PyInstaller打包的区别
pyinstaller
和
setuptools
打包有什么区别(来自
ChatGPT
的答案)
pyinstaller
和
setuptools
都是
Python
中常用的打包工具,但它们的主要目的和工作方式略有不同。
pyinstaller
是一个可以将Python
代码打包成一个可执行文件的工具,它会将你的Python
代码和所有依赖项打包成一个单独的可执行文件,让你的代码可以在没有Python
解释器的情况下运行。这使得你可以将你的 Python 应用程序发布给没有安装 Python 的用户,或者将它们放在没有 Python 环境的机器上。setuptools
则是一个用于构建Python
软件包的工具,它可以帮助你将你的Python
代码打包成一个可安装的软件包,你可以将这个软件包发布到 PyPI 或其他软件包管理器中。setuptools
还提供了许多用于定义软件包元数据、依赖项和其他设置的选项,以及用于打包软件包的命令行工具。- 因此,尽管
pyinstaller
和setuptools
两者都能将 Python 代码打包成一个独立的分发形式,但它们的主要目的略有不同。- 如果你想要将你的Python
应用程序打包成一个单独的可执行文件,可以选择pyinstaller
。- 如果你想要将你的Python
代码打包成一个可安装的软件包并将其发布到PyPI
或其他软件包管理器中,可以选择setuptools
。
2、使用setupTools打包本地项目
参考
- 【python】上传 Python 包到 pypi 官网
- 【Python】如何在PyPI上发布自定义软件包
- Packaging and distributing projects
- Python 项目代码写完了,然后怎么打包和发布?
0)项目准备 & 生成
requirements.txt
& 编写打包文件
a)项目准备
假设Python项目的目录结构如下:
其中
pdf_handler.py
代码为:
import pdfplumber
from PyPDF2 import PdfReader, PdfWriter
import os
import sys
pdf_path ="resume.pdf"#相对位置,不能使用绝对位置#提取PDF文字defextract_pdf_text():# 提取pdf指定页的文字with pdfplumber.open(pdf_path)as pdf:
page01 = pdf.pages[0]# 指定页码
text = page01.extract_text()# 提取文本print(f"第一页pdf的文本内容为:{text}",end='\n')#提取所有页pdf文字with pdfplumber.open(pdf_path)as pdf:print(f"所有页pdf的文本内容如下:")for page in pdf.pages:#遍历所有页
text = page.extract_text()# 提取当前页的文本print(text)#提取PDF表格defextract_pdf_table():with pdfplumber.open(pdf_path)as pdf:print(f"所有页pdf的表格内容如下:")for page in pdf.pages:# 遍历所有页
table = page.extract_table()# 提取当前页的文本print(table)#分割PDFdefsplit_pdf():#如果使用PdfFileReader会抛出异常:PyPDF2.errors.DeprecationError: PdfFileReader is deprecated and was removed in PyPDF2 3.0.0. Use PdfReader instead.
file_reader = PdfReader(pdf_path)#实例化pdf reader对象# getNumPages() 获取总页数会报错:PyPDF2.errors.DeprecationError: reader.getNumPages is deprecated and was removed in PyPDF2 3.0.0. Use len(reader.pages) instead.for page inrange(len(file_reader.pages)):
file_writer = PdfWriter()#实例化pdf writer对象# 将遍历的每一页对象添加到pdf writer对象中,使用file_reader.getPage(page)会报错:PyPDF2.errors.DeprecationError: reader.getPage(pageNumber) is deprecated and was removed in PyPDF2 3.0.0. Use reader.pages[page_number] instead.
file_writer.add_page(file_reader.pages[page])#PyPDF2.errors.DeprecationError: addPage is deprecated and was removed in PyPDF2 3.0.0. Use add_page instead.withopen(f'{os.path.join(saveDir,str(page)+".pdf")}','wb')as out:
file_writer.write(out)
其中
main.py
为主函数入口,代码为:
from pdf_task.pdf_handler import extract_pdf_text,extract_pdf_table,split_pdf
import os
if __name__ =='__main__':print("Hello world")
extract_pdf_text()
extract_pdf_table()
split_pdf()
os.system("pause")
这里创建两个完全一样的
pdf_task
,用来模拟
pypi_test
包中包含这两个模块。
b)生成
requirements.txt
接着在项目根目录下(
pypi_test
)使用
pipreqs
,生成关于整个包的第三方依赖文件
requirements.txt
。(参考python生成requirements.txt的两种方法)
pip install pipreqs -i https://pypi.tuna.tsinghua.edu.cn/simple
#生成requirements.txt命令如下
pipreqs .--encoding=utf8 --force #如果requirement.txt已存在,则
requirement.txt
生成内容如下:
pdfplumber==0.8.0
PyPDF2==3.0.1
setuptools==67.3.3
c)编写打包文件
接着分别创建并编写相应的打包文件:
README.md
:关于这个项目的具体描述(自定义)LICENSE
:对关于项目所有权即使用约束等问题的描述(自定义,参考https://choosealicense.com/
)pyproject.toml
:需要指定setuptools
的最低版本,脚本内容如下[build-system]requires =["setuptools>=42"]build-backend ="setuptools.build_meta"
setup.py
:使用setuptools
进行打包的脚本,内容包括如下几个部分,参考Python 项目代码写完了,然后怎么打包和发布?name: 你定义的包名,可以用字母、数字、下划线,需要确保唯一性。version: 项目的版本号。author: 你(作者)的名称。author_email: 你(作者) 的邮箱。description: 项目的简要描述。long_description_content_type:长描述内容的使用的标记类型,一般为 markdown 或者 rst。url: 你这个项目的主页地址,也可以直接链接到你这个项目的Github 地址上面去。include_package_data: 是否添加 py 以外的文件。package_data: 需要添加 Python 的额外文件列表。packages: 直接用 setuptool 找到你项目所有相关的包列表。这里直接使用find_packages()自动寻找classifiers: 附加说明,比如这里写的就是使用于 Python3 版本,使用的是 MIT 协议,独立于 OS。python_requires: python 版本要求。
我编写的setup.py
脚本如下:#参考 https://juejin.cn/post/7053009657371033630, https://zhuanlan.zhihu.com/p/527321393#!/usr/bin/env pythonfrom os import pathfrom setuptools import setup, find_packageshere = path.abspath(path.dirname(__file__))withopen(path.join(here,'requirements.txt'),'r', encoding='utf-8')as f: all_reqs = f.read().split('\n')withopen(path.join(here,'README.md'),'r', encoding='utf-8')as f: long_description = f.read()install_requires =[x.strip()for x in all_reqs if'git+'notin x]setup( name='pypi_test',# 必填,项目的名字,用户根据这个名字安装,pip install SpiderKeeper-new version='1.0.0',# 必填,项目的版本,建议遵循语义化版本规范 author='wangxiaoxi',# 项目的作者 description='pypi测试',# 项目的一个简短描述 long_description=long_description,# 项目的详细说明,通常读取 README.md 文件的内容 long_description_content_type='text/markdown',# 描述的格式,可选的值: text/plain, text/x-rst, and text/markdown author_email='[email protected]',# 作者的有效邮箱地址 url='https://github.com/test',# 项目的源码地址 license='MIT', include_package_data=True, package_data={'src':["resources/"]}, packages=find_packages(),# 必填,指定打包的目录,默认是当前目录,如果是其他目录比如 src, 可以使用 find_packages(where='src') install_requires=install_requires,# 指定你的项目依赖的 python 包,这里直接读取 requirements.txt# 分类器通过对项目进行分类,帮助用户找到项目 classifiers=['License :: OSI Approved :: MIT License','Operating System :: OS Independent','Programming Language :: Python :: 3',], python_requires=">=3.8")
编写好以上所有文件之后,目录结构如下:
1)打包成源码 & 二进制可安装软件包(
whl
)并引用
- 先升级一下
setuptools
和wheel
的版本:pip install --upgrade setuptools wheel
- 接着在根目录(
pypi_test
),使用如下命令打包源码:python setup.py sdist
此时会在根目录下产生一个dist
文件夹,里面有打包好的源码压缩包pypi_test-1.0.0.tar.gz
,解压好后即为根目录下的python
源码。 - 接着在根目录(
pypi_test
),使用如下命令打包源码:python setup.py bdist_wheel
此时会在dist
文件夹中生成一个该项目的二进制可安装文件pypi_test-1.0.0-py3-none-any.whl
;并且生成一个build
文件夹,其中lib
包含整个项目两个模块的python
源码,不包括静态资源。 - 如果想同时打包源码,并且打包二进制可安装软件包,可以直接使用如下命令:
python setup.py sdist bdist_wheel
打包后的目录结构如下:
如果要想引用这个包,可以直接
pip install .\pypi_test-1.0.0-py3-none-any.whl
,
接着在新项目中创建一个
python_test
去直接引用这个项目中的
pdf_task
,
pdf_task2
两个模块。
from pdf_task.pdf_handler import extract_pdf_text
extract_pdf_text()# 或许会报找不到静态资源的错误:FileNotFoundError: [Errno 2] No such file or directory: 'D:\\programSoftware\\python\\anaconda\\envs\\spider_env\\lib\\site-packages\\pdf_task\\resume.pdf',# 可以将resume.pdf放到"site-packages/pdf_task/'中即可解决
如果不想使用这个包,可以通过
pip uninstall pypi_test
进行卸载,**其中
pypi_test
是在
setup.py
中配置的项目名**。
2)上传whl到PyPI
先在https://pypi.org/account/register/注册一个
pypi
账号:
接着安装
twine
:
pip install twine
最后在
pypi_test
目录下,将
setupTools
打包好的
dist
文件上传到
pypi
上:
twine upload dist/*
其中可能存在的问题:
- 报错1:上传的
dist
文件夹中包含除whl
之外的文件夹(spider_env) PS D:\桌面\pypi_test> twine upload dist/*Uploading distributions to https://upload.pypi.org/legacy/ERROR InvalidDistribution: Unknown distribution format:'pypi_test-1.0.0'
解决方法:删除掉dist
文件夹中多余的文件夹。- 报错2:该项目名已被别人使用,参考HTTPError: 403 Client Error: The user allowed to upload to project · GitHub
ERROR HTTPError:403 Forbidden from https://upload.pypi.org/legacy/ The user 'wangxiaoxi' isn't allowed to upload to project 'pypi_test'. See https://pypi.org/help/#project-name for more information.
解决方法:删除掉原来的whl
文件,修改setup.py
中的项目名并重新打包,重新上传。这里将项目名修改为wangwangwang_pypi_test
。
成功完成上传后,可以通过控制台中的链接访问:
3)可能存在的问题
模块名不能是
python
关键字(比如
main
),否则打包虽然成功,但是不能直接引用。
3、使用PyInstaller打包本地项目
参考
- Pyinstaller打包文件太大的解决方案_python_脚本之家
- 使用pyQt5 + agora + leanCloud实现基于学生疲劳检测的在线课堂_学生上课疲劳监测
1)PyInstaller将主函数打包成可执行文件
关于
pyinstaller
打包命令参数如下:
--distpath <path>: 打包到哪个目录下
-w: 指定生成 GUI 软件,也就是运行时不打开控制台
-c: 运行时打开控制台
-i <Icon File>: 指定打包后可执行文件的图标
--clean: 在构建之前清理PyInstaller缓存并删除临时文件
这里依然使用上面的项目举例子,但和
setuptools
打包不同的是,这里**只对
pdf_task
单个模块中的
main.py
入口函数进行打包**(其中
main.py
在
pdf_task
根目录下),由于不包含GUI桌面,因此使用保留控制台输出,打包命令命令如下:
(spider_env) PS D:\桌面\pypi_test> pyinstaller --distpath D:/pypi_package/Release/--clean pdf_task/main.py
打包后会在当前目录(
pypi_test
)下生成一个
build
文件夹,并在
distpath
路径下生成发布版(
Release
)的打包文件。
2)导包和静态文件引入问题
Note:
- 在打包时要注意将
main.py
放在模块的根目录(这里是pdf_task
)下,并且python
文件在import
导入包内的其他模块时,必须要加上根目录名:比如这样在执行Release
中的可执行文件时,会抛出No module named 'pdf_handler'
的错误:from pdf_handler import extract_pdf_text,extract_pdf_table,split_pdfimport osif __name__ =='__main__':print("Hello world") extract_pdf_text() extract_pdf_table() split_pdf() os.system("pause")
需要修改成如下导入方式才不会报pdf_handler
没找到的错误(**前提是pdf_task
是一个Python包,其中必须包含一个空的__init__.py
**,否则还会报错):from pdf_task.pdf_handler import extract_pdf_text,extract_pdf_table,split_pdfimport osif __name__ =='__main__':print("Hello world") extract_pdf_text() extract_pdf_table() split_pdf() os.system("pause")
- 如果直接执行上面
Release
文件夹下的main.exe
会报如下错误> Note:如果控制台一闪而过,则使用带GUI界面的命令>> pyinstaller --distpath D:/pypi_package/Release/ -w --clean pdf_task/main.py>
> ,这样可以在界面上查看报错信息。> > 如果想定位报错的信息,进而引入缺失的静态文件(配置文件或者静态代码库)的话,可以在>> python>
> 中使用>> raise Exception()>
> 来定位。Traceback (most recent call last): File "pdf_task\main.py", line 6,in<module> File "pdf_task\pdf_handler.py", line 17,in extract_pdf_text File "pdfplumber\pdf.py", line 71,inopenFileNotFoundError:[Errno 2] No such fileor directory:'D:\\桌面\\pypi_test\\src\\Release\\main\\pdf_task\\resume.pdf'
解决方法是将模块中的静态文件原封不动地拷贝到Release/main
中(及拷贝整个pdf_task
并删掉所有不必要的py
文件):这样重新执行Release
文件夹下的main.exe
就没有问题了。
3)其他问题
- 如果项目没有GUI界面,但在
pyinstaller
打包时不小心使用-w
选项,导致在程序启动后并没有关闭的按钮可供操作,这里假设我执行的是job_scheduler.exe
,在windows10
中如果要想结束这个程序,可以如下操作(参考windows环境下,CMD控制台查看进程、结束进程相关命令 | 码农家园):C:\Users\THINKPAD>tasklist | findstr job_scheduler.exejob_scheduler.exe 42180 Console 1170,928 KC:\Users\THINKPAD>tasklist /f /t /im job_scheduler.exe错误: 无效参数/选项 - '/f'。键入 "TASKLIST /?" 以了解用法。C:\Users\THINKPAD>taskkill /f /t /im job_scheduler.exe成功: 已终止 PID 42180(属于 PID 10888 子进程)的进程。
如果能在任务管理器中找到也可以。 windows10
开机自启动exe
参考Win10系统下设置软件(.exe可执行程序)开机自启方法_RobVisual- 如果想将
exe
可执行文件发布到gitee
上,可以参考官方教程:什么是 Release(发行版) - Gitee.com
4、拷贝环境并编写执行脚本
还是以上面
pypi_test
中的
pdf_task
模块为例,如果我想让别人在同样操作系统上,在解压好压缩包后直接调用
pdf_task/main.py
方法,可以将
python
环境直接拷贝到项目的根目录下,并编写
main.py
的启动脚本即可。
- 直接将
python
虚拟环境spider_env
中的所有文件拷贝到根目录下的py38
中; - 编写启动脚本,执行即可运行
main.py
;py38\python.exe pdf_task\main.pypause
项目结构如下:
不同于
setuptools
和
pyInstaller
打包,直接拷贝
Python
环境的好处是可以保证原项目结构的完整性,但缺点是对不同系统的移植性会比较差。
版权归原作者 王小希ww 所有, 如有侵权,请联系我们删除。