使用setuptools打包Python包与脚本时遇NameError问题求助
你遇到的这个情况很典型,核心问题出在脚本文件名和你的包名完全一致(都是package_name),再加上Python的模块搜索机制:
当你在项目根目录下运行bin/package_name.py时,Python会把当前工作目录(也就是项目根目录)加入到模块搜索路径sys.path的最前端。这时候,脚本里的from package_name import *并没有去加载你安装到site-packages里的那个包,而是导入了脚本自身(因为当前目录下的package_name.py(脚本)比site-packages里的包优先级更高)。
因为import操作本身是成功的(确实找到了名为package_name的模块),所以不会抛出ImportError;但你的脚本本身并没有定义那些你要使用的包内组件,当你尝试调用这些组件时,自然就会触发NameError。
这里有几种不同的解决方案,按推荐程度排序:
1. 重命名脚本(最稳妥的方案)
直接修改脚本的文件名,避免和包名冲突。比如把bin/package_name.py改成bin/package_name_cli.py或者bin/run_package_name.py,然后同步更新setup.py里的scripts参数:
setuptools.setup( name=<PROJECT>, packages=[<PACKAGE_NAME>], scripts=['bin/package_name_cli.py'], # 这里改成新的脚本名 python_requires='>=3', )
重新运行python3 setup.py install后,再执行新命名的脚本,就会正确导入site-packages里的包了。
2. 使用entry_points替代scripts(更现代的做法)
setuptools推荐使用entry_points来定义命令行脚本,这种方式会自动帮你生成一个包装脚本,完全避免文件名冲突的问题。
修改你的setup.py如下:
setuptools.setup( name=<PROJECT>, packages=[<PACKAGE_NAME>], python_requires='>=3', entry_points={ 'console_scripts': [ # 格式:命令名=包名.模块名:入口函数名 '<PACKAGE_NAME>=package_name.cli:main', ], }, )
然后在你的包目录下创建一个cli.py文件,把原来脚本里的逻辑放到main()函数中:
# package_name/cli.py from package_name import * def main(): # 原来脚本里的代码逻辑 pass if __name__ == '__main__': main()
安装后,你直接在命令行输入<PACKAGE_NAME>(就是你定义的命令名)就能运行脚本,Python会正确导入你的包,不会有任何冲突。
3. 调整脚本的导入路径(不推荐)
如果你坚持不想改文件名,可以在脚本开头调整sys.path,让Python优先搜索site-packages里的包:
# 在脚本的最顶部添加 import sys sys.path.pop(0) # 移除当前工作目录的优先级 from package_name import * # 后续代码...
不过这种方法不够优雅,而且如果你的项目结构有变化,可能会引发其他导入问题,所以只作为临时 workaround 用。
内容的提问来源于stack exchange,提问作者David Reed

