|
笔者在CSDN上发现关于这方面的知识过于零碎,分布在各篇博客中,因此整理了一下知识点一、基本概念module(模块):一个python文件就是一个模块,例如torch中的nn模块,模块里可以包含类、函数,例如nn.functional类和nn.Conv2d函数,我们之后不区分模块和文件的说法。package(包):一个包含多个模块并且拥有__init__.py文件的文件夹,例如torch或者torch中的nn;package比module多了一个__path__变量。(在python3中没有__init__.py的文件夹也会被认为是package)工作目录:当前工作目录=执行python文件的命令行路径=os.getcwd(),cwd是CurrentWorkingDirectory的缩写,下图的C:\Users\我的名字叫红\Desktop\import就是命令行路径,D:/anaconda/python.exe是python解释器路径,最后一项是脚本路径 4.模块搜索路径:解释器搜索模块和包的路径,sys.path列表, 其他知识点:访问一个子模块、函数、类都可以用"."来进行在考虑python设计逻辑的时候一定要想到“嵌套调用”,一个文件可以被其他文件调用每个程序主要有一个执行的文件,一般称为main.py,后边称之为执行脚本/文件 在执行一个程序的过程中cwd是不变的(但是可以通过os.chdir(path)修改),整个程序共享一个cwd,也共享一个sys.path为了方便下面的讲解,我们以这个文件结构为例子,每一个文件里都有函数f(当时起名的时候没起好,不想改了),请注意文件的结构是一棵树,我们要思考的是叶子节点之间如何交流##文件结构示意图-**项目根目录**-`A`文件夹-`A`-`__init__.py`-`A1.py`-`A2.py`-`B`-`B1.py`-`A1.py`-`B`文件夹-`B1.py`-`C1.py`文件-`README.md`二、读取文件核心:读取文件的相对路径永远是相对于工作目录而言的(也可以使用绝对路径)以下均为在根目录下运行代码,右图为根目录,可以顺利读取一个常见的误区是把相对路径设置为相对于当前执行脚本的,我们在读取同一级别的A2.py时使用"./A2.py"路径会报错因为open读取文件的相对路径是相对于工作目录(cwd)的,因此在写文件代码的时候,就要事先想好最终的总执行目录是什么,而且用户在运行整个程序的时候也要cd到特定的工作目录。因为工作目录(cwd)是唯一的,由所有文件共享,所以读取文件的相对路径是唯一的。三、sys.pathsys.path是一个列表变量,解释器搜索模块和包的路径存放在sys.path列表中,供import使用sys.path包含三部分内容,是有顺序的,因为搜索路径的过程是按顺序的:当前执行脚本的父目录路径,自动添加到列表开头python的path环境变量代码中使用sys.path.append后添加的(可选),append到结尾 其中第一部分只有一项,当我们执行的脚本(通常是run.py、main.py)调用了其他脚本(通常是utils.py)时,也不会自动添加其他的路径如下图所示,在根目录下执行脚本,当前脚本的父目录被自动append到sys.path列表中,与工作目录无关文件A2.py导入同级别的A1.py,在被导入的文件A1.py中执行:因为sys.path是全局共享的,所以当我们在被导入的A1.py文件中执行sys.path.append时,我们会看到sys.path列表结尾中多了被导入的A1.py文件中添加的“test_import"四、import核心:import从sys.path这个列表中按顺序搜索路径,导入的库/模块必须在搜索路径里我们上文提到sys.path第一项是被执行脚本(通常是run.py、main.py)的父目录路径,因此import部分的代码一定尽量相对于这个父目录路径来写,尤其是被main.py调用的utils.py等模块的import语句。如果实在不能相对于这个父目录路径来写,可以使用sys.path.append(),因为append到列表末尾,因此优先级最低,不会影响其他导入。sys.path.append()的地址是相对于工作目录的import在执行的时候会(1)在sys.modules字典中搜索该模块。(2)如果没搜索到,导入该模块,自动执行被导入模块的代码,并将其添加到sys.modules字典中。我们刚才在第三部分中使用importA1的时候就自动执行了A1模块中的sys.append("test_import")代码。因此python中经常使用if__name__=="__main__":来避免执行被导入模块的所有代码,从而只导入需要的class和function。被import的模块作为一个变量,缓存起来,因此只执行一次。通过"."来访问的对象可以是函数,例如:torch.nn.functional.relu,也可以是模块,例如:torch.nn,也可以是类,例如: nn.Moduleimport有两种形式:(1)importxxx:模块或包,例如importtorch,importtorch.nn.functional (2)fromxximportxx:可以是模块、包、函数、类。例如fromtorchimportnn,fromtorch.nn.functional importrelu(函数),fromtorch.nnimportConv2d(类)import仅仅是把模块添加到命名空间里了,但并没有添加到sys.path中,因此下面的代码搜不到nnimportpackageimport一个package(例如torch)会自动执行__init__.py的代码,但不会加载其他代码。在A/A1.py中importApackage,执行了print的代码,如下图此时我们执行A.A1.f(),会报错,这是因为A1并没有被加载到A包下面有两种解决方案:添加importA.A1,这样A1被加载到A包下在__init__.py中添加from.importA1。导入A1后,通过A就可以访问到A1了。这也是常用的库中的方式,不必importtorch.nn,直接importtorch就可以直接使用torch.nn。这样做的缺点是刚刚导入torch的时候需要花很长时间初始化。第二种解决方案的另一种写法:__all__=["A1.py"] ,变量__all__指定了初始化导入的模块,不希望用户导入其他模块。但是尽管A2.py没有被__all__包括在内,用户依然能使用”fromAimportA2“导入它这里解释一下为什么torch.nn模块下既有函数(Conv2d)又有其他模块(functional):nn模块也有__init__.py,在init代码中导入了nn模块下的某些子模块中的函数,因此nn可以直接调用函数torch.nn源码如下:nnfrom.modulesimport*(包含Conv,loss等等部分)点相对导入最令人困惑的是使用"."和".."的相对导入(这里简称为点相对导入),这也是package里最常使用的导入方式,因为一个package里各个模块的相对位置是长期固定的。首先我们来看同一package内的两个同级别模块A1和A2,这里的“.”指的是相对于该模块的路径而言。例如:torch.nn.functional,在functional.py中执行from. importConv2d,也就是导入了当前工作目录(nn)下的nn.Conv2d;执行from..importnn.Conv2d,也就是导入了父目录下的torch.nn.Conv2d。由于".."和“.”对应的模块名是唯一的,所以不用指明,此时".."后添加模块名相当于自动在中间补上一个点,例如from..nn importConv2d,起到了两个作用,相当于(1)访问到torch(2)fromtorch.nnimportimportConv2d我们一般会在A2中直接执行importA1,没有问题,因为sys.path中有A2的父目录,也就是A1的父目录。但是执行from.importA1并运行A2的时候报错:ImportError:attemptedrelativeimportwithnoknownparentpackage这个问题是因为,我们压根就不应该直接执行package里的文件,如果我们在这个package之外调用A2.py,虽然from.importA1这段代码执行了,但不会报错。至于为什么,参考资料2里有详细说明。我们可以简单认为,当在package外使用package时,会自动将"."前补充上package的名字,而在package里执行时,文件不知道"."前补充什么名字,因此报错“noknownparent"那如果我们非要直接执行A2.py呢?把from.importA1暂时换成importA1就好了。另一种解决方案是按模块方式来执行该代码:python-m模块路径。我们再来看看其他情况作为练习:刚才的两个文件同属于一个包下同一级别,结构如下左图,我们来看看不同包下同一级别的情况,见右图,使用B1.py访问A包下的A1.py 这时直接执行以下三种代码会遇到相同的错误:attemptedrelativeimportwithnoknownparentpackage这时无论我们添加sys.path.append(".")("..")("./A")("./A/A")都无济于事,因为说到底,这类点相对导入就和sys.path没什么关系,只和“包”有关于是我们尝试另一种形式的相对导入,上下两种方法都可以,借此,我们能访问到./A/A下的文件,但是注意不能同时使用,因为有重名的问题,fromA.A的时候程序会优先从"./A"展开搜索,由于找不到"./A"下的A.A,会报错:更换二者顺序后如上图,同样报错:这里建议只添加根目录(工作目录)sys.path.append("."),然后全部使用相对于根目录的地址,避免名字空间的重复。五、总结可以把文件的结构视作一颗树,如果你想使用main.py的父目录(sys.path的第一项)访问到尽可能多的模块,那main.py最好在根节点下一级,能够通过A.B.C.D的方式访问到所有模块。package内可以用相对导入;如果只有一个被执行文件main.py,而且package是你自己私有的话,那package内部也可以根据相对main.py的位置进行导入子模块。文件的结构也可以比作国家-地区-个人的层级查找,两国之间的两个人(python脚本)沟通必须要依赖于国家(上级文件夹),上级能访问同级和下级(使用点“."或者直接import),而个人是没有权力要求国家的,个人的通讯权力仅限于自己的sys.path,必须使用sys.path.append来赋予其访问权限。当国家需要个人的时候(main.py调用utils.py),个人也就有了与最顶层的国家共享sys.path的权力,因此借用这个根也就可以访问其他国的人。总结:读取文件是相对于工作目录,sys.path包含脚本(main.py)的父目录,import使用sys.path或者"."的相对导入方式。工作目录和sys.path是在整个程序中共享的。为了正确读取文件,需要写代码的时候想好工作目录是什么,并cd到正确的工作目录下执行python文件为了正确import模块,需要相对于main.py的路径写import语句为了正确sys.append,需要相对于工作目录写相对地址本人编程水平不高,如果有什么错误欢迎指正。下面是几个优秀的链接,在次推荐参考资料:1.模块管理-预备知识-CSDNPython入门技能树2.【python】关于import你需要知道的一切!一个视频足够了_哔哩哔哩_bilibili3.【Python】`__init__.py`文件详解_pythoninit文件-CSDN博客4.【Python】python包相对导入问题及解决方案_cythonattemptedrelativeimportwithnoknownpar-CSDN博客
|
|