当我们在使用pyinstaller打包发布包含celery任务的项目时,如果出现项目打包成功了,但是运行的时候只要开启celery进程报错:exe:maximum recursion depth exceeded while calling a Python object…,即使通过sys.setrecursionlimit设置系统最大递归深度也解决不了问题时,莫慌,我教你怎么解决。
ps:为防止某些大聪明照搬下面代码编译出现问题,我先将注意事项交代在前,项目根目录下最好别在dist文件夹下存放代码文件,因为下面的解决方式中,构建编译文件时会自动覆盖这个文件夹下的所有文件,如果你的项目根目录下有这两个文件夹,请修改为其他名称!!!
OK,我们正式进入主题。首先,在你的项目根目录(与入口相同目录)下新建build.py(该文件名称可自由命名)文件,如下:

build.py文件中输入以下代码:
#!/usr/bin/env python
# cython: language_level=3
# -*- coding: utf-8 -*-
# @Author : LuBowen
# @Number : 20210509
# @FileName :build
# @Time :2023/9/7 13:37
# @Software: PyCharm Community Edition
# @Version : Python3
# ====================================
from subprocess import call
from distutils.core import setup
from Cython.Build import cythonize
import os
import shutil
from tqdm import tqdm
# 用于编译的工程根目录绝对路径(此处替换为你的项目根目录-即入口文件所在目录的绝对路径)
project_dir = "D:/projectsFiles/vision"
# 非编译文件绝对路径-包含的文件将不会被编译,并会在项目编译完成后直接复制到编译后的项目目录中(如果你的项目中存在不需要编译的python文件或者其他文件,请将其绝对路径放到下面的列表中,这些文件将不会被编译且会在项目编译完成后自动复制到编译后的项目包/文件夹中)
include_files = [
"D:/projectsFiles/vision/.env",
"D:/projectsFiles/vision/DockerFile",
"D:/projectsFiles/vision/r.txt",
]
# noinspection PyMissingOrEmptyDocstring
def collect_file(project_path, direct_name=None, file_end=('pyd', 'o'), include=()):
project_path_direct_name = os.path.basename(project_path)
if not direct_name:
direct_name = project_path_direct_name
base_path = f'{project_path}/dist/{direct_name}'.replace('\\', '/')
for root, dirs, files in os.walk(project_path):
root_format = str(root).replace('\\', '/')
if base_path in f'{root_format}/{direct_name}':
continue
if os.path.abspath('./build').replace('\\', '/') in root_format:
continue
if os.path.abspath('./dist').replace('\\', '/') in root_format:
continue
if '__pycache__' in root_format:
continue
files = tqdm(files)
for file in files:
files.set_description(f'Collecting compiled file:{root_format}')
if str(file).split('.')[-1] in file_end or f'{root_format}/{file}' in include:
current_file = f'{root_format}/{file}'
target_direct = root_format.replace(str(project_path), base_path, 1)
if not os.path.exists(target_direct):
os.makedirs(target_direct)
shutil.copy(current_file, target_direct)
# noinspection PyMissingOrEmptyDocstring
def clear_file(project_path, file_end=('pyd', 'o', 'c', 'pyc'), direct_name=None, exclude=()):
project_path_direct_name = os.path.basename(project_path)
if not direct_name:
direct_name = project_path_direct_name
base_path = f'{project_path}/dist/{direct_name}'.replace('\\', '/')
for root, dirs, files in os.walk(project_path):
root_format = str(root).replace('\\', '/')
if base_path in f'{root_format}/{direct_name}':
continue
if os.path.abspath('./build').replace('\\', '/') in root_format:
continue
if os.path.abspath('./dist').replace('\\', '/') in root_format:
continue
files = tqdm(files)
for file in files:
files.set_description(f'Clear:{root_format}')
if f'{root_format}/{file}' in exclude:
continue
if str(file).split('.')[-1] in file_end:
current_file = f'{root_format}/{file}'
os.remove(current_file)
# noinspection PyMissingOrEmptyDocstring
def compiling(project_path, direct_name=None, exclude=()):
compile_file_set = set()
project_path_direct_name = os.path.basename(project_path)
if not direct_name:
direct_name = project_path_direct_name
base_path = f'{project_path}/dist/{direct_name}'.replace('\\', '/')
for root, dirs, files in os.walk(project_path):
root_format = str(root).replace('\\', '/')
if base_path in f'{root_format}/{direct_name}':
continue
if os.path.abspath('./build').replace('\\', '/') in root_format:
continue
if os.path.abspath('./dist').replace('\\', '/') in root_format:
continue
if '__pycache__' in root_format:
continue
target_path = root_format.replace(str(project_path), base_path, 1)
if not os.path.exists(target_path):
os.makedirs(target_path)
files = tqdm(files)
for file in files:
files.set_description(f'Collecting python file:{root_format}')
if str(file).endswith('.py') and f'{root_format}/{file}' not in exclude:
compile_file_set.add(f'{root_format}/{file}')
print(f'Collected python file number:{len(compile_file_set)}')
setup(ext_modules=cythonize(compile_file_set))
# noinspection PyMissingOrEmptyDocstring
def pyinstaller_cmd_para(server_file, app_name):
#"""
# 如果编译后运行时,报错:ModuleNotFoundError: No module named '模块名'
# 先在项目环境中导入:pip install 模块名
# 然后将
# '--hidden-import', '模块名',
# 加入到cmd中,注意看示例!
#"""
cmd = ['pyinstaller',
'-y',
f'{server_file}.py',
'--name', f'{app_name}',
'--hidden-import', 'celery.app.events',
'--hidden-import', 'celery.backends.redis',
'--hidden-import', 'celery.backends',
'--hidden-import', 'celery.fixups',
'--hidden-import', 'celery.app.amqp',
'--hidden-import', 'kombu.transport.redis',
'--hidden-import', 'celery.fixups.django',
'--hidden-import', 'celery.concurrency.gevent',
'--hidden-import', 'celery.apps.worker',
'--hidden-import', 'engineio.async_gevent',
'--hidden-import', 'gevent',
'--hidden-import', 'psycopg2',
'--hidden-import', 'pymysql',
'--hidden-import', 'celery.app.log',
'--hidden-import', 'celery.worker.components',
'--hidden-import', 'celery.worker.autoscale',
'--hidden-import', 'celery.worker.consumer',
'--hidden-import', 'celery.app.control',
'--hidden-import', 'celery.events.state',
'--hidden-import', 'celery.worker.strategy',
'--hidden-import', 'skimage',
]
call(cmd)
# noinspection PyMissingOrEmptyDocstring
def build(entry_file, app_name):
pyinstaller_cmd_para(entry_file, app_name)
compiling(project_path=project_dir, direct_name=app_name)
collect_file(project_path=project_dir, direct_name=app_name, include=include_files)
clear_file(project_path=project_dir, direct_name=app_name)
print('completed!')
if __name__ == '__main__':
#此处两个参数为:入口文件名称,编译后的项目名称
build('startup', 'startup')
# 终端输入指令:python build.py build_ext --inplace开始编译
保存上面的代码文件,开启终端,进入到项目根目录,输入python build.py(请将此python文件名称替换为你自己命名的文件名) build_ext --inplace回车开始编译,如:

等待编译完成,如下:

显示如下表示完成:

根目录下会出现build/文件夹和dist/文件夹,其中dist文件夹下就是打包完成的项目了,如下:



至此,项目打包完成!