再更新
修好的XJImporter脚本发在别的博客里了:https://blog.csdn.net/weixin_44733774/article/details/126737346#XJImporter_42
打包后的程序总算能正常运行了
更新
如果项目是要打包运行的话那别用我下面的XJImporter脚本
好不容易把自己的一个整烂活给搞完,然后一打包,hjh程序根本跑不了,然后分析了一会儿发现,多半是因为导入错误,于是就把我所有用到XJImporter的代码全部删掉改成相对路径导入,一改就是几十分钟,巨恶心,而且还一语成谶,当时说什么“理想的python项目应该会导致项目根目录下堆满一堆的test脚本”结果还真中了。(应该是有别的更好的python的项目管理方法,例如打包成pyc?以后再了解吧
目前我暂时不想分析打包运行失败的原因,因为这一搞估计又是一天,乏了乏了,有心情再搞
原文
额,python什么都好,就一点不好,那就是项目的管理很让人头疼。场景如下:
我有一个项目,名为[Test],项目结构如下:
其中,Module_A需要导入模块Tool,但Tool.py并不与Module_A.py处于同一目录,也就是Module_A如果作为脚本运行的话根本没法直接导入模块Tool,只能作为模块将Tool导入。这就意味着Module_A的调试不能在Module_A.py中进行,而要在Tool所在路径下创建py脚本,然后将Module_A导入才能进行调试。这就涉及到py的“模块”和“脚本”的概念
以下是对脚本和模块的基本区分:
- 能双击直接运行的py文件视为脚本。
- 不能双击运行的py文件视为模块。
- 脚本可以作为模块被其他py文件导入,但脚本不一定能作为模块。
- 模块不能直接运行,也就是模块一定不会是脚本。
以下是个人支离破碎的理解:
- 如果py文件中使用了相对路径导入,那么这个py文件将无法作为脚本直接运行,只能作为模块被导入。
from . import XXX
from .XXX import classX
- 跨目录导入也视为相对路径导入,一样不能作为脚本直接运行。
如模块导入语句from XXX import classX
,但模块XXX不在当前目录,并且没通过sys.path
添加XXX所在路径。 - 运行py项目时,有且仅有一个脚本(也就是主脚本)在运行,其他所有和主脚本关联的py文件统统作为模块,这些模块在通过相对路径导入其他模块时当前路径为主脚本所在路径,而不是当前模块所在路径。
- 因为模块中的代码不能直接测试,所以
if __name__=='__main'
这条语句对于模块来说存在意义为零。 - 由第3、4点得知,一个 “理想中的python项目” ,很多py文件都是作为模块而存在,不能双击运行,如果要对该模块进行测试的话往往要在项目的根路径创建py脚本将模块导入进行测试。能够想象到为了测试若干个模块,项目根路径下堆满大量的测试脚本的情形。
- 模块文件是脚本文件的特化,也是脚本文件的阉割版。暂时看不出模块文件的优势在哪,只觉得这玩意儿增加了调试的难度…,调试一个模块文件还得跑去对应目录创建测试脚本进行模块的测试…
因此更多人的做法是,修改sys.path,往其中加入自己心仪的路径,以便一个脚本文件能够顺利的被其他py文件导入,但这样做往往会加入绝对路径,平添项目的管理难度。
为了解决这个破问题(在一个py文件中导入其他相对路径下的模块后,该py文件即能作为脚本直接运行,也能作为模块被其他py文件导入),XJ写了一个类:
XJImporter
import sys
import os
class XJImporter:#专治各种不服
'''
模块导入,用于导入模块,尤其是相对路径下的导入。
'''
def __init__(self,context):
'''
context为上下文环境,在创建对象时直接传入globals()即可
'''
self.__context=context
def Import(self,module,args=None):
'''
module为模块名所在路径(不需要.py后缀),支持相对路径:
导入上一级名为M的模块,那么module='../M'
导入目录A下的名为M的模块,那么module='A/M'
args为从module中导入的变量名或者变量名列表:
如果args为空,那么仅导入模块module
如果args不为空,那么将导入模块module中的变量
例子:
Import('M'):导入模块M。【类似于import M】
Import('M','info'):导入模块M中名为info的变量。【类似于from M import info】
Import('M',('info','func')):导入模块M中名为info和func的变量。【类似于from M import info,func】
Import('M','*'):导入模块M中所有内容。【类似于from M import *】
Import('../M'):导入上级目录中的模块M
Import('A/M'):导入A目录下的模块M
注:
①、请不要传入各种奇奇怪怪的值,我可没做好各种极端情况的预防工作
②、import失败必然会抛出异常,这没得洗的。请注意自己的代码规范
③、经常出现“鸡与蛋的先后问题”。请将该文件复制到要跨目录导入的脚本所在的目录下
'''
absolutePath=os.path.split(self.__context['__file__'])[0]#调用该函数的文件所在的路径(绝对路径)
relativePath,__module=os.path.split(module)#模块所在目录(相对路径)、模块名
path=os.path.join(absolutePath,relativePath)#模块所在路径(绝对路径)
sys.path.append(path)#将路径临时加入到系统列表中
context={
}
if(args):
if(type(args)==str):
exec(f'from {
__module} import {
args}',context)
else:
exec(f'from {
__module} import {
",".join(args)}',context)
else:
exec(f'import {
__module}',context)
self.__context.update(context)
sys.path.pop()#移除临时加入的路径
这里就不贴测试代码和测试截图了,废精力,这里直接贴一个github项目地址,可以下载下来试着运行一下:https://github.com/Ls-Jan/Python_ModuleImporter/tree/main