pyinstaller 打包单文件终极解决方案( No such file or directory)

一、打包命令:(假设你的入口函数叫main.py)

pyinstaller -F main.py

最好直接cd 到main.py所在目录,然后再执行这条命令。这样打包后和main的同级目录就会又一个叫build和dist的文件夹,然后反倒dist文件夹下,你会看到生成一个名叫: main.exe(window 平台生成exe), linux平台没有后缀,直接就是main

二、需要明白的点:

  1. pyinstaller会把main.py执行的时候能引用到的所有py文件(不包括其它任何文件)都给你按照你当前代码目录的组织方式给你打包到main.exe里面。
  2. pyinstaller不会打包除了.py文件之外的其他任何文件,如.yaml、.csv、readme、xlsx、jpg等等其它文件。
  3. 如果要打包除了.py之外的其它文件,需要我们打包的时候加上--add-data把它们添加进去。
  4. 使用了pyinstaller成功打包后,你不再受限于本机环境。你不要再考虑python环境有没有安装?你可能还在想是不是只能在打包的目录下才能运行起来?移动个位置就运行不起来了?更不要说换个机器运行了。这些问题你统统不需要担心了,因为pyinstaller已经把我们依赖的python环境,依赖的第三版包,还有我们代码里面涉及到的资源都拷贝进到main.exe里面了,而且做了地址替换(你不要想我那些文件不是只有我自己电脑上有,而且我代码里面引用的地址都是我电脑的文件的绝对路径,脱离当前目录,甚至脱离了本机,还能运行?你啥都不用想,我告诉你可以!!!!)
  5. 关于上面第四点的地址替换,我后文还会详细展开讨论,因为理解这个极其重要,请见下文。

三、需要打包除.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/代表你的项目目录。

  1. --add-data可以写多个,“\”表示换行,防止命令放在一行过长。

--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。这种错实在是太常见了,但是你一定要搞懂,造成你这个错的原因是什么:

  • 你--add-data里面的源地址或者目的地址写的确实不对。源地址我说了,就是用文件在你本机当前的绝对路径就行,我相信大部分肯定出错出在了冒号后面,这你就要好好理解我说的,程序真正运行的时候是拷贝到/tmp/xxxx/下面运行的,你main.py其实就在这个下面:运行的时候/tmp/xxxx/main.py,main.py就放在这里,那你自己想像,根据在项目中其它文件和main.py的相对位置关系,你来确定,你那些资源文件都要拷贝到那个相对路径。比如你的目录是这样的:
~/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/
  • 你代码里面绝对有地方使用了相对地址访问文件。这一点太重要了!!!!!!比如你可能在main.py里面写了这么一句话:
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文件)。归根揭底我就总结下面几条:

  1. 代码里面全部用绝对地址访问文件,不要用上面这种相对地址。一个很好的办法就是:
    # -*- 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")
  2. main.exe运行的时候对于你之前写的那种相对地址,它是是运行main.exe这个目录下去找文件的。就像上面的例子,假如你把main.exe移动到/example/下了,然后在它里面运行main.exe ,那么它去找config/config.yaml文件的时候,它就认为这个文件放在/example/config/config.yaml里面,这显然是不对的,因为你--add-data参数写的是把它放到和main.py同级别的/tmp/xxxx/config/下面了,所以在/example/下满肯定没有啊,搞得你以为你还没把config.yaml这个文件拷贝进去,其实你已经拷贝进去了,只是你代码里面是相对路径,main.exe运行的时候对相对路径的解读就是在那个目录下启动main.exe(这个例子是/example/),它就去这个目录下找你代码里写的相对路径地址有没有,没有就报错。这是很多人错误的原因!!!!!!!
  3. 假如你代码里面都用的绝对路径,而且你也把--add-data里面地址写对了。你知道吧,我敢保证,你无论在那个目录运行main.exe,都不会错。这是因为main.exe在运行的时候对于绝对路径的解读就是地址替换。这也是一开始说我要详细讨论的地方:比如:
    # -*- 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()
    
  4. 绝对路径地址是~/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。

 五、总结起来就是:

1、代码里的相对路径是相对于你启动main.exe那个路径而言的。

2、绝对路径是根据你--add-data的配置进行替换的。

3、所以你如果真心想最后打包成一个可执行文件,我建议你代码里面都用绝对路径。

可能你狗屎运,代码用的相对路径,把main.exe移动到/example/运行的时候报错说找不到config/config.yaml,你认为自己灵机一动,直接在/example/下面建了个config文件夹,然后把config.yaml拷贝到config里面了,发现不报错了,你可乐坏了,觉得自己成功了,但是悲剧就是你一辈子main.exe走到哪里,你就要把config/config.yaml拷贝到哪里,它们永远脱离不了。那你还想只打一个文件呢,你发现自己就是个loser,捡了芝麻,丢了西瓜,你目的就没实现,你只是借了自己的小聪明,认为自己成功了而已。

本文绝对是pyinstaller觉悟仅有的解决方案,你99%的找不到文件的问题都可以解决,只要你真的读懂了本文的内容。本来想弄成收费文章的,想想还是便宜一下你们这对白嫖程序员。那就帮忙点个赞吧,谢谢~。

你可能感兴趣的:(程序设计,windows,pyinstaller,python打包,pyinstaller,-F,linux)