一、打包命令:(假设你的入口函数叫main.py)
pyinstaller -F main.py
最好直接cd 到main.py所在目录,然后再执行这条命令。这样打包后和main的同级目录就会又一个叫build和dist的文件夹,然后反倒dist文件夹下,你会看到生成一个名叫: main.exe(window 平台生成exe), linux平台没有后缀,直接就是main
二、需要明白的点:
三、需要打包除.py之外的文件需要使用如下命令:
pyinstaller -F main.py \
--add-data=${PARENT_DIR}/data/*.csv:data \
--add-data=${PARENT_DIR}/config/*.yaml:config \
--add-data=${PARENT_DIR}/readme:.
对以上命令有如下的说明:
我的${PARENT_DIR}表示我的项目目录,因为我用shell写的,你不想这么写,你直接替换成具体路径就行。比如--add-data=~/project/data/*.csv:data \ 。~/project/代表你的项目目录。
--add-data里面冒号之前的路径用绝对路径(该路径是目前你想拷贝进exe里面的这些文件当前是放在你机器上的具体那个目录);且可以使用通配符“*”,方便你一次拷贝多个文件,当然也可以具体到文件。冒号之后用的是相对路径(相对的是生成exe之后,执行的时候你发现老是提示在/tmp/xxxx下面No such file or directory,相对的就是这个/tmp/xxxx/,而且这个xxxx可能每次都不一样,他就是一个exe运行时候的临时目录)。怎么个相对法呢?待我给你细细再解释一下:
(1)首先你要理解--add-data=${PARENT_DIR}/data/*.csv:data这句话的意思,它指的是把你项目路径下的/data里面的所有csv文件拷贝到/tmp/xxxx/data目录里面,但是/tmp/xxxx不用你写。
(2)在运行pyinstaller时候写的这个--add-data的作用跟你直接pyinstaller -F main.py啥都不加,往最后编译完生曾的main.spec文件里面的,Analysis->data数组添加(源地址,目的地址)这种元组对的作用是一样的。不过我建议还是你直接用--add-data比较爽。
四、我假设你已经编译完成了,生成了build和dist文件夹,并且dist下面生成了main.exe 或者main文件
(1)这时候你肯定很兴奋,急着想运行main.exe或者main,你运行的方式如下:
对于window平台而言,你要么是直接双击了main.exe,要么你打开了cmd命令行,然后输入了main.exe回车运行的。对于linux平台而言,你肯定用的是:
./main
(2)你运行了之后,发现,楼主真坑,我怎么照着你说的一运行,报了/tmp/xxxx/下面No such file or directory。这种错实在是太常见了,但是你一定要搞懂,造成你这个错的原因是什么:
~/project
|------data
| |----1.csv
| |----2.csv
| |----3.xlsx
|------config
| |---config.yaml
|------main.py
|------readme
|------Dockefile
你发现报了/tmp/xxxx/config/config.yaml no such file or directory这样的错误。我想你肯定在想,这到底是没有在exe里面给我创建config这个目录呢,还是没有把config.yaml给我拷贝进去。我告诉你,肯定是你地址写错,没给你拷贝到这个目录,而不是没给你创建config这个目录,这个目录肯定创建了,只要你在运行pyinstaller的时候写了:
--add-data=~/project/config/config.yaml:config
这个语句只要没报错,打包完成了,你./main运行的时候才告诉找不到文件或者目录,那一定不是没给你创建目录。一般来说你在打包的时候没报错,而是运行才报错,十有八九就是你自己把冒号后面地址写错了,导致它给你把config.yaml文件后拷贝到别的目录去了,而不是拷贝到了/tmp/xxxx/config里面,所以才找不到。所以一定要回根据main.py和config.yaml的相对关系,写对冒号后面的路径。比如你写成下面这样肯定是错的:
--add-data=~/project/config/config.yaml:.
写成下面的也是错的:
--add-data=~/project/config/config.yaml:../config
正确是的就是x下面这几个等价写法:
--add-data=~/project/config/config.yaml:config
或者
--add-data=~/project/config/config.yaml:./config
或者
--add-data=~/project/config/config.yaml:./config/
if __name__=='__main__':
with open("config/config.yaml") as f:
lines = f.read()
代码中的的”config/config.yaml“就是你错误的本源。这是为什么呢?这是因为你在代码中入宫这样写的话,真正运行的时候main.exe会把这个相对路径认为你要读你当前正在执行main.exe的这个目录下的config/config.yaml文件(加入你已经把打包后的main.exe移动了别的目录,而不是在~/project/下面了,如果你还在在~/project/下面执行的./main或者main.exe,你可能侥幸地认为好像能正确运行,但是一旦当你把这个main.exe移动到别的目录去运行的时候,你就发现你一运行,它就告诉你找不到config/config.yaml文件)。归根揭底我就总结下面几条:
# -*- coding:utf-8 -*-
import os
#在main里面写地址的时候参考下面的这种方式,你只要写好PROJECT_PATH,其它都根据他来拼接就行了:
PROJECT_DIR = os.path.dirname(__file__)
DATA_DIR = os.path.join(PROJECT_DIR,"data")
CONFIG_DIR = os.path.join(PROJECT_DIR,"config")
# -*- coding:utf-8 -*-
import os
#在main里面写地址的时候参考下面的这种方式,你只要写好PROJECT_PATH,其它都根据他来拼接就行了:
PROJECT_DIR = os.path.dirname(__file__)
DATA_DIR = os.path.join(PROJECT_DIR,"data")
CONFIG_DIR = os.path.join(PROJECT_DIR,"config")
if __name__=='__main__':
#绝对路径地址是~/project/config/config.yaml,其实在main.exe运行的时候,它就会根据你add-data配置#的地址映射关系去/tmp/xxxx/config/下面去找看有没有config.yaml,其实相当于是做了地址替换:
#~/project/config/config.yaml -> ~/tmp/xxxx/config/config.yaml
#这些你也就明白,为啥--add-data后面的源地址要写到具体文件级别,而目的地址只要到文件夹级别就行。就是因为它在运行的时候要做地址替换,把~/project/config/替换成~/tmp/xxxx/,然后去~/tmp/xxxx/下面找~/project/config/下面的那个config.yaml。
with open(os.path.join(CONFIG_DIR,"config.yaml")) as f:
lines = f.read()
五、总结起来就是:
1、代码里的相对路径是相对于你启动main.exe那个路径而言的。
2、绝对路径是根据你--add-data的配置进行替换的。
3、所以你如果真心想最后打包成一个可执行文件,我建议你代码里面都用绝对路径。
可能你狗屎运,代码用的相对路径,把main.exe移动到/example/运行的时候报错说找不到config/config.yaml,你认为自己灵机一动,直接在/example/下面建了个config文件夹,然后把config.yaml拷贝到config里面了,发现不报错了,你可乐坏了,觉得自己成功了,但是悲剧就是你一辈子main.exe走到哪里,你就要把config/config.yaml拷贝到哪里,它们永远脱离不了。那你还想只打一个文件呢,你发现自己就是个loser,捡了芝麻,丢了西瓜,你目的就没实现,你只是借了自己的小聪明,认为自己成功了而已。