python多级目录import_深入理解Python中import机制
大型项目中为了维护方便,通常使用模块化开发,模块化的过程中,就会涉及到各种包或者模块的相互导入,即使是对于有多个项目的Python开发者来说, import 也会让人困惑!本文带你深入了解python中 import 的内在机制,从而避免import导入引发的异常。
概念
模块(module)
任何 .py 文件都可以称为模块
包(package)
可以将多个模块放入一个包中,就像电脑中的文件夹,但与文件夹的区别是,package包含 __init__.py 文件
Python import 的搜索路径
当我们执行 python xx.py 时,python是如何帮我们正确定位包所在的目录呢?其实系统是按照以下顺序来寻找的:
1.系统内置模块,比如os, sys模块2.入口文件所在的目录,比如main.py所在的目录3.Python环境变量,也就是我们平时pip install后的包所在的目录,如Anaconda下的site-packages目录
在Python中,如果遇到了import错误,我们可以通过以下命令查看搜索路径:
import sysprint(sys.path)
结果:
sys.path: ['/Users/root/Python/project','/Users/root/anaconda3/lib/python36.zip','/Users/root/anaconda3/lib/python3.6','/Users/root/anaconda3/lib/python3.6/lib-dynload','/Users/root/.local/lib/python3.6/site-packages','/Users/root/anaconda3/lib/python3.6/site-packages','/Users/root/anaconda3/lib/python3.6/site-packages/Sphinx-1.5.6-py3.6.egg','/Users/root/anaconda3/lib/python3.6/site-packages/aeosa','/Users/root/anaconda3/lib/python3.6/site-packages/mdr-0.0.1-py3.6-macosx-10.7-x86_64.egg']
可以看到,其中第一个目录是我们运行的文件所在目录,其它都是Python环境变量中的目录
执行 python xx.py 发生了什么?
直接被Python解释器运行的文件,称之为程序的 入口文件 ,在Python中,入口文件有且仅有一个
我们经常使用以下语句:
# in main.pyif __name__=="__main__": run()
__name__=="__main__" 这个语句就是检测当前脚本是否被当作入口文件使用,如果被当做入口文件使用,那么就运行 if __name__=="__main__": 下面的代码块,如果只是当做模块被其他的模块 import 进去使用,那就不应该运行这部分代码块。
因此,判断一个文件是否被python解释器当做入口文件对待,是可以打印脚本的 __name__ 变量的。
# in main.pyprint(__main__)
入口文件和 import 路径有什么关系呢?
如果我们把 xx.py 当做入口文件,那么Python解释器,就会将 xx.py 所在的目录,加入到sys.path中,作为import时搜索的根目录。简言之,你用 python filename.py 执行哪个文件,Python解释器就会将哪个文件所在目录加入 import 的搜索目录。
新问题:在模块化的项目中,如何让Python解释器搜索到所有的模块呢?
这引入了一个项目结构合理性的问题: 入口文件的目录层级应该是顶层的 ,也就是入口文件目录层级不应该低于任何模块或者包,一个常见的目录结构通常如下:
$ tree./project├── package│ ├── __init__.py│ ├── sub_pkg1│ │ ├── __init__.py│ │ ├── module_X.py│ │ └── module_Y.py│ └── sub_pkg2│ ├── __init__.py│ └── module_Z.py└── main.py(入口文件)
如果我们执行 python main.py ,那么main.py就会被当做入口文件,所在目录 project 就会被加入import的搜索路径,那么我们将能够搜索 package1 和 package2 中的模块;现在假设我们直接运行 python module_X.py ,此时入口文件变成了 module_X.py ,那么我们 import 搜索的根目录为 sub_pkg1 ,当我们试图去 import sub_pkg2 中的文件时,就会引发异常。
上述问题引入了一个准则:除了入口文件之外,其他文件都不应该通过python xx.py来运行,那如果我们想运行某个模块怎么办?
答案是:通过 python -m sub_pkg1.module_X 来运行!
python -m 的意思是将module当做模块来运行,同时 sub_pkg1.module_X 是导入路径,这样子就不会找不到模块了。
上述解决方案引入一个比较好的实践:
如果在一个大型项目中,你经常使用 cd 命令跳转到各种不同层级的目录中 Python xx.py 运行某个模块,那你可能得尝试使用 Python -m 了,也就是我们最好在项目的根目录下完成所有的模块的测试。当然,完善的项目,应该有专门的测试目录如 tests , 这个以后有机会再讲。
Python中的相对导入和绝对导入
在Python中,存在相对导入和绝对导入两种import机制,但无论是绝对导入还是相对导入,都需要一个参照物,不然「绝对」与「相对」的概念就无从谈起。绝对导入的参照物是项目的根文件夹,相对导入参照物是当前模块,当我们使用相对导入时,需要给出相对于当前模块,想导入模块所在的位置。
# 相对导入from . import foolfrom .package import foolfrom ..module import spam# 绝对导入,项目根目录是appfrom app.package import fool
相对导入可以避免硬编码带来的维护问题,例如我们改了某一顶层包的名字,那么其子包所有的导入就都不能用了。除非我们手动修改顶层包名。 但是存在相对导入语句的模块,不能直接使用 python xx.py 的方式运行,否则会有异常:
ValueError: Attempted relative import in non-package
•如果是绝对导入,一个模块只能导入自身的子模块或和它的顶层模块同级别的模块及其子模块•如果是相对导入,一个模块必须有 包结构 (意味着有 __init__.py )且只能导入它的顶层模块内部的模块 所以,如果一个模块被直接运行,Python会将该模块当做 顶层模块(top level) ,不再当做一个包来对待,因此不存在层次结构,所以找不到其他的相对路径,所以如果直接运行python xx.py ,而xx.py有相对导入就会报错
看下面例子:
$ tree./project├── package│ ├── __init__.py│ ├── sub_pkg1│ │ ├── __init__.py│ │ ├── module_X.py│ │ └── module_Y.py│ └── sub_pkg2│ ├── __init__.py│ └── module_Z.py└── main.py(入口文件)
module_X.py
from . import module_Yprint "X __name__", __name__
module_Y.py
print "Y __name__", __name__
当我们直接运行 python sub_pkg1/module_X.py 的时候,会报错
ValueError: Attempted relative import in non-package
当我们这样运行的时候 python -m sub_pkg1.module_X , 才能正常运行
Y __name__ sub_pkg1.moduleYX __name__ __main__
为什么会这样?简单地说,直接运行 .py 文件和 import 这个文件有很大区别。Python 解释器判断一个 py 文件属于哪个 package 时并不完全由该文件所在的文件夹决定。它还取决于这个文件是如何 load 进来的( 直接运行 or import )。
有两种方式加载一个 py 文件:
•作为 top-level 脚本 作为 top-level 脚本指的是直接运行脚本,比如 python xx.py。有且只能有一个 top-level 脚本,就是最开始执行的那个(比如 python xx.py 中的 xx.py)。•作为 module 作为 module 是指,执行 python -m xx,或者在其它 py 文件中用 import 语句来加载,那么它就会被当作一个 module。
当一个 py 文件被加载之后,它会被赋予一个名字,保存在 __name__ 属性中。如果是 top-level 脚本,那么名字就是 __main__ 。如果是作为 module,名字就是把它所在的 packages/subpackages 和文件名用 . 连接起来。
例如,moduleX 被 import 进来,它的名字就是 package.subpackage1.moduleX。如果 import 了 moduleA,它的名字是 package.moduleA。如果直接运行 moduleX 或 moduleA,那么名字就都是 __main__ 了。
所以上面的 module_X 的 __name__ 是 __main__ , 因为他是直接运行的, module_Y 的 __name__ 是 sub_pkg1.module_Y ,因为他是被import 来使用的。
常见几种import需求
module_X.py
# module_X导入module_Y# 相对导入from . import module_Y# 绝对导入from package.sub_pkg1 import module_Y
module_X.py
# module_X导入module_Z# 相对导入from ..sub_pkg2 import module_Z# 绝对导入from package.sub_pkg2 import module_Z
main.py
# main.py导入module_Yfrom package.sub_pkg1 import module_Y
特别需要注意的是,虽然上述模块导入路径是对的,除了 main.py 之外,都不可以通过 python xx.py 的方式运行,而是通过前面讨论过个 python -m 方式运行。
相对导入和绝对导入的优缺点
相对导入可以避免硬编码,对于包的维护是友好的。其缺点是可读性较差,让人很难清楚地了解到资源所在的位置。
绝对路径导入由于其直观往往是大家的首选。只要看一下导入语句你就能知道资源是从什么位置导入的。再者,就算当前import语句的位置发生了变化,此绝对路径导入的资源依然有效。实际上,官方也推荐使用绝对路径导入。
《Python之禅》中提到:
明确 由于 隐晦
纵观一些优秀的开源项目,绝对导入的使用也是更为普遍的。我个人通常以绝对导入为主,相对导入为辅,只会在同一个模块层级中使用一些相对导入,如: from .fool import spam ,很少会使用 from ...fool import spam 这样让人迷惑的相对导入。
解决import错误的调试思路
1.区分是文件夹还是包(有无 __init__.py )?
2.非入口文件是否是使用python -m运行的?
3.入口文件的层级,是否高于任何包或者模块?
python多级目录import_深入理解Python中import机制相关推荐
- python多级目录import_你真的会用Python模块与工具包吗?
在开发过程中,我们无法把所有代码.资源都放在同一个文件中.因此,模块导入在编码中是很常见的.无论是C++.Java,还是Python.Go. 可以把不同功能.不同模块进行分离,当使用的时候,可以通过i ...
- python安装目录结构_1.5 python安装目录介绍《Python基础开发入门到精通》
第一章 Python的概述与环境安装 本章所讲内容: 1.1 Python介绍 1.2 Python2与Python3的比较 1.3 Python3的安装 1.4 Python环境变量配置 1.5 P ...
- python if _name_==_main__如何理解Python中的if __name__ == ‘__main__’
转自:https://blog.csdn.net/yjk13703623757/article/details/77918633/ 对内容进行了部分筛选 1. 摘要 通俗的理解__name__ == ...
- python参数传递方法_深入理解python中函数传递参数是值传递还是引用传递
python 的 深入理解python中函数传递参数是值传递还是引用传递 目前网络上大部分博客的结论都是这样的: Python不允许程序员选择采用传值还是传 引用.Python参数传递采用的肯定是&q ...
- python怎么导入包-如何理解Python中包的引入
Python的from import *和from import *,它们的功能都是将包引入使用,但是它们是怎么执行的以及为什么使用这种语法呢? 从一模块导入全部功能 from import * me ...
- python iterable对象_如何理解Python中的iterable对象
转载请注明出处:https://www.jianshu.com/u/5e6f798c903a [^*] 表示注脚,在文末可以查看对应连接,但简书不支持该语法. 首先,容器和 iterable 间没有必 ...
- 为什么一个程序中变量只能定义一次_#带你学Python# 从简单程序出发理解Python基本语法
欢迎回来. 通过上一篇文章,我们第一次触摸了Python,学会了如何用各种不同的方式运行Python的解释器.也介绍了很多工具和开发环境,是不是跃跃欲试了? 到这里,别的python教程就会从数据类型 ...
- cmd进入到python安装目录下_在python中安装basemap
在python中安装basemap 1. 确保python环境安装完毕且已配置好环境变量 2. 安装geos: pip install geos 3. 下载.whl文件: (1)pyproj‑1.9. ...
- python装饰器原理-深刻理解python装饰器
我们要完全理解python装饰器,不是很容易,主要归结有如下困难: 1. 关于函数"变量"(或"变量"函数)的理解 2. 关于高阶函数的理解 3. 关于嵌套函数 ...
最新文章
- Spring Cache-缓存概述及使用
- ae的渲染引擎:cineware
- JZOJ 5419. 【NOIP2017提高A组集训10.24】筹备计划
- java swing事件机制_java SWing事件调用的两种机制
- [C++11]基于范围的for循环
- 写SQL语句需要注意的点
- 云计算与虚拟化了解二三事
- linux中grep的例子,Linux下grep命令使用实例
- python程序设计课程设计二级减速器_二级减速器的课程设计
- 解决雷神笔记本风扇声音太响太吵问题
- 生态保护重要性评价之防风固沙重要性评价
- c语言程序设计作业心得,C语言程序设计实习心得体会
- MySQL基本优化方案
- 网间进程的标识和端口分配机制
- 用户标签体系的意义及设计方法
- html导航跳转,css实现导航切换的实例代码
- [docker]privileged参数
- 使用Cardme读取安卓IOS导出的vcf格式通讯录
- JavaScript字符串常用方法
- java log4j配置日志分隔