Python 3.x | 史上最详解的 导入(import)「建议收藏」

Python 3.x | 史上最详解的 导入(import)「建议收藏」win10+Python3.6.3一、理解一些基本概念1、包、模块模块module:一般情况下,是一个以.py为后缀的文件。其他可作为module的文件类型还有”.pyo”、”.pyc”、”.pyd”、”.so”、”.dll”,但Python初学者几乎用不到。module可看作一个工具类,可共用或者隐藏代码细节,将相关代码放置在一个module以便让代码更好用、易懂,让cod…

大家好,又见面了,我是你们的朋友全栈君。

如需转载请注明出处。
win10+Python 3.6.3

一旦使用多层文件架构就很容易遇上import的坑!哈哈。

一、理解一些基本概念

1、模块、包
**模块 module:**一般情况下,是一个以.py为后缀的文件。其他可作为module的文件类型还有”.pyo”、”.pyc”、”.pyd”、”.so”、”.dll”,但Python初学者几乎用不到。
module 可看作一个工具类,可共用或者隐藏代码细节,将相关代码放置在一个module以便让代码更好用、易懂,让coder重点放在高层逻辑上。
module能定义函数、类、变量,也能包含可执行的代码。module来源有3种:
①Python内置的模块(标准库);
②第三方模块;
③自定义模块。

包 package: 为避免模块名冲突,Python引入了按目录组织模块的方法,称之为 包(package)。包 是含有Python模块的文件夹。
这里写图片描述
当一个文件夹下有   init   .py时,意为该文件夹是一个包(package),其下的多个模块(module)构成一个整体,而这些模块(module)都可通过同一个包(package)导入其他代码中。

其中   init   .py文件 用于组织包(package),方便管理各个模块之间的引用、控制着包的导入行为。
该文件可以什么内容都不写,即为空文件(为空时,仅仅用import [该包]形式 是什么也做不了的),存在即可,相当于一个标记。
但若想使用from pacakge_1 import *这种形式的写法,需在  init  .py中加上:   all    = [‘file_a’, ‘file_b’] #package_1下有file_a.py和file_b.py,在导入时   init   .py文件将被执行。
但不建议在   init   .py中写模块,以保证该文件简单。不过可在   init   .py导入我们需要的模块,以便避免一个个导入、方便使用。

其中,   all   是一个重要的变量,用来指定此包(package)被import *时,哪些模块(module)会被import进【当前作用域中】。不在   all   列表中的模块不会被其他程序引用。可以重写  all  ,如   all    = [‘当前所属包模块1名字’, ‘模块1名字’],如果写了这个,则会按列表中的模块名进行导入。

在模糊导入时,形如from package import *,*是由__all__定义的。

精确导入,形如 from package import *、import package.class。

   path   也是一个常用变量,是个列表,默认情况下只有一个元素,即当前包(package)的路径。修改   path   可改变包(package)内的搜索路径。

当我们在导入一个包(package)时(会先加载   init   .py定义的引入模块,然后再运行其他代码),实际上是导入的它的   init   .py文件(导入时,该文件自动运行,助我们一下导入该包中的多个模块)。我们可以在   init   .py中再导入其他的包(package)或模块 或自定义类。

2、sys.modules、命名空间、模块内置属性
2.1 sys.modules
官方解释:链接
sys.modules 是一个 将模块名称(module_name)映射到已加载的模块(modules) 的字典。可用来强制重新加载modules。Python一启动,它将被加载在内存中。
当我们导入新modules,sys.modules将自动记录下该module;当第二次再导入该module时,Python将直接到字典中查找,加快运行速度。

它是个字典,故拥有字典的一切方法,如sys.modules.keys()、sys.modules.values()、sys.modules[‘os’]。但请不要轻易替换字典、或从字典中删除某元素,将可能导致Python运行失败。

import sys
print(sys.modules)#打印,查看该字典具体内容。

2.2 命名空间
如同一个dict,key 是变量名字,value 是变量的值。

  • 每个函数function 有自己的命名空间,称local namespace,记录函数的变量。
  • 每个模块module 有自己的命名空间,称global namespace,记录模块的变量,包括functions、classes、导入的modules、module级别的变量和常量
  • build-in命名空间,它包含build-in function和exceptions,可被任意模块访问。

某段Python代码访问 变量x 时,Python会所有的命名空间中查找该变量,顺序是:

  1. local namespace 即当前函数或类方法。若找到,则停止搜索;
  2. global namespace 即当前模块。若找到,则停止搜索;
  3. build-in namespace Python会假设变量x是build-in的函数函数或变量。若变量x不是build-in的内置函数或变量,Python将报错NameError。
  4. 对于闭包,若在local namespace找不到该变量,则下一个查找目标是父函数的local namespace。

例:namespace_test.py代码

def func(a=1):
	b = 2
	print(locals())#打印当前函数(方法)的局部命名空间
	'''
	locs = locals()#只读,不可写。将报错!
	locs['c'] = 3
	print(c)
	'''
	return a+b
func()
glos = globals()
glos['d'] = 4
print(d)

print(globals())#打印当前模块namespace_test.py的全局命名空间

内置函数locals()、globals()返回一个字典。区别:前者只读、后者可写。

命名空间 在from module_name import 、import module_name中的体现:from关键词是导入模块或包中的某个部分。

  1. from module_A import X:会将该模块的函数/变量导入到当前模块的命名空间中,无须用module_A.X访问了。
  2. import module_A:modules_A本身被导入,但保存它原有的命名空间,故得用module_A.X方式访问其函数或变量。

2.3 模块内置属性

  1.    name   直接运行本模块,   name   值为   main   ;import module,   name   值为模块名字。
  2.    file   当前 module的绝对路径
  3.    dict   
  4.    doc   
  5.    package   
  6.    path   

3、绝对导入、相对导入
这里写图片描述
3.1 绝对导入:所有的模块import都从“根节点”开始。根节点的位置由sys.path中的路径决定,项目的根目录一般自动在sys.path中。如果希望程序能处处执行,需手动修改sys.path。
例1:c.py中导入B包/B1子包/b1.py模块

import sys,os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))#存放c.py所在的绝对路径

sys.path.append(BASE_DIR)

from B.B1 import b1#导入B包中子包B1中的模块b1

例2:b1.py中导入b2.py模块

from B.B1 import b2#从B包中的子包B1中导入模块b2

3.2 相对导入:只关心相对自己当前目录的模块位置就好。不能在包(package)的内部直接执行(会报错)。不管根节点在哪儿,包内的模块相对位置都是正确的。
b1.py代码

#from . import b2 #这种导入方式会报错。
import b2#正确
b2.print_b2()

b2.py代码

def print_b2():
	print('b2')

运行b1.py,打印:b2。

在使用相对导入时,可能遇到ValueError: Attempted relative import beyond toplevel package
解决方案:参考这篇文章,链接

3.3 单独导入包(package):单独import某个包名称时,不会导入该包中所包含的所有子模块。
c.py导入同级目录B包的子包B1包的b2模块,执行b2模块的print_b2()方法:
c.py代码

import B
B.B1.b2.print_b2()

运行c.py,会报错。

解决办法
B/   init   .py代码

from . import B1#其中的.表示当前目录

B/B1/   init   .py代码

from . import b2

此时,执行c.py,成功打印:b2。

3.4 额外
①一个.py文件调用另一个.py文件中的类。
如 a.py(class A)、b.py(class B),a.py调用b.py中类B用:from b import B
②一个.py文件中的类 继承另一个.py文件中的类。如 a.py(class A)、b.py(class B),a.py中类A继承b.py类B。

from b import B
class A(B):
pass

二、Python运行机制:理解Python在执行import语句(导入内置(Python自个的)或第三方模块(已在sys.path中))时,进行了啥操作?

step1:创建一个新的、空的module对象(它可能包含多个module);
step2:将该module对象 插入sys.modules中;
step3:装载module的代码(如果需要,需先编译);
step4:执行新的module中对应的代码。

在执行step3时,首先需找到module程序所在的位置,如导入的module名字为mod_1,则解释器得找到mod_1.py文件,搜索顺序是:
当前路径(或当前目录指定sys.path)—–>PYTHONPATH—–>Python安装设置相关的默认路径。

对于不在sys.path中,一定要避免用import导入 自定义包(package)的子模块(module),而要用from…import… 的绝对导入 或相对导入,且包(package)的相对导入只能用from形式。

1、“标准”import,顶部导入
这里写图片描述
有上述基础知识,再理解这个思维导图,就很容易了。在运用模块的变量或函数时,就能得心应手了。

2、嵌套import

2.1 顺序导入-import
这里写图片描述

PS:各个模块的Local命名空间的独立的。即:
test模块 import moduleA后,只能访问moduleA模块,不能访问moduleB模块。虽然moduleB已加载到内存中,如需访问,还得明确地在test模块 import moduleB。实际上打印locals(),字典中只有moduleA,没有moduleB。

2.2 循环导入/嵌套导入-import
这里写图片描述
形如from moduleB import ClassB语句,根据Python内部import机制,执行细分步骤:

  1. 在sys.modules中查找 符号“moduleB”;
  2. 如果符号“moduleB”存在,则获得符号“moduleB”对应的module对象;
    从的   dict__中获得 符号“ClassB”对应的对象。如果“ClassB”不存在,则抛出异常“ImportError: cannot import name ‘classB’”
  3. 如果符号“moduleB”不存在,则创建一个新的 module对象。不过此时该新module对象的   dict   为空。然后执行moduleB.py文件中的语句,填充的   dict   

总结:from moduleB import ClassB有两个过程,先from module,后import ClassB。
这里写图片描述

当然将moduleA.py语句 from moduleB import ClassB改为:import moduleB,将在第二次执行moduleB.py语句from moduleA import ClassA时报错:ImportError: cannot import name ‘classA’

解决这种circular import循环导入的方法:
例比:安装无线网卡时,需上网下载网卡驱动;
安装压缩软件时,从网上下载的压缩软件安装程序是被压缩的文件。
方法1—–>延迟导入(lazy import):把import语句写在方法/函数里,将它的作用域限制在局部。(此法可能导致性能问题)
方法2—–>将from x import y改成import x.y形式
方法3—–>组织代码(重构代码):更改代码布局,可合并或分离竞争资源。
合并—–>都写到一个.py文件里;
分离–>把需要import的资源提取到一个第三方.py文件中。
总之,将循环变成单向。

3、包(package)import
在一个文件下同时有   init   .py文件、和其他模块文件时,该文件夹即看作一个包(package)。包的导入 和模块导入基本一致,只是导入包时,会执行这个   init   .py,而不是模块中的语句。
而且,如果**只是单纯地导入包【形如:import xxx】**,而包的   init   .py中有没有明确地的其他初始化操作,则:此包下的模块 是不会被自动导入的。当然该包是会成功导入的,并将包名称放入当前.py的Local命名空间中。
这里写图片描述
[D:youcaihua\test\PkgDemo\mod.py]文件
[D:youcaihua\test\PkgDemo\pkg1\pkg1_mod.py]文件
[D:youcaihua\test\PkgDemo\pkg2\pkg2_mod.py]文件,三个文件同样的代码:

def getName():
	print(__name__)

if __name__ == '__main__':
	getName()

[D:youcaihua\test\test.py]文件

import PkgDemo.mod#1
print(locals(),'\n')
import PkgDemo.pkg1#2
print(locals(),'\n')
import PkgDemo.pkg1.pkg1_mod as m1#3
print(locals(),'\n')
import PkgDemo.pkg2.pkg2_mod#4
PkgDemo.mod.getName()#5
print('调用mod.py----', locals(), '\n')
m1.getName()#6
PkgDemo.pkg2.pkg2_mod.getName()#7

执行 #1 后,将PkgDemo、PkgDemo.mod加入sys.modules中,此时可调用PkgDemo.mod的任何类、或函数。当不能调用包PkgDemo.pkg1或pkg2下任何模块。但当前test.py文件Local命名空间中只有 PkgDemo

执行 #2 后,只是将PkgDemo.pkg1载入内存,sys.modules会有PkgDemo、PkgDemo.mod、PkgDemo.pkg1 三个模块。但PkgDemo.pkg1下的任何模块 都没有自动载入内存,所以在此时:PkgDemo.pkg1.pkg1_mod.getName()将会出错。当前test.py的Local命名空间依然只有PkgDemo。

执行 #3 后,会将pkg1_mod载入内存,sys.modules会有PkgDemo、PkgDemo.mod、PkgDemo.pkg1、PkgDemo.pkg1.pkg1_mod四个模块,此时可执行PkgDemo.pkg1.pkg1_mod.getName()。由于使用了as,当前Local命名空间将另外添加m1(作为PkgDemo.pkg1.pkg1_mod的别名)、当然还有PkgDemo。

执行 #4 后,会将PkgDemo.pkg2、PkgDemo.pkg2.pkg2_mod载入内存,sys.modules中会有PkgDemo、PkgDemo.mod、PkgDemo.pkg1、PkgDemo.pkg1.pkg1_mod、PkgDemo.pkg2、PkgDemo.pkg2.pkg2_mod六个模块,当然:当前Local命名空间还是只有PkgDemo、m1。

#5#6#7当然都可正确执行。

三、How to avoid Python circle import error?如何避免Python的循环导入问题?

代码布局、(架构)设计问题,解决之道是:将循环变成单向。采用分层、用时导入、相对导入(层次建议不要超过两个)

注意:在命令行执行Python xx.py、与IDE中执行,结果可能不同。

如需转载请注明出处。
参考:
官方规范

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/160297.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • linux目录结构详解_简述linux系统中的目录结构

    linux目录结构详解_简述linux系统中的目录结构前言平常linux系统用的也不少,那么linux下的每个目录都是用来干什么的,小伙伴们有仔细研究过吗?让我们来了解下吧Linux系统目录结构登录系统后,在当前命令窗口下输入命令:[root@

  • 用Python教训盗号骗子

    用Python教训盗号骗子文章目录前言抓包分析代码编写测试效果后记前言近日,本人闲来无事在QQ空间浏览好友动态,突然一张熟悉的图片进入了我的视野,没错,就是它,又是那一张图片。在好奇心的驱使下,我扫了上图中码子,打开一个网站,凭借老夫多年的经验,这网站一定是钓鱼网站。本想就这么算了,可是实在是太无聊了,想要搞一下这个盗号骗子,于是乎就有了这篇文章。抓包分析代码编写思路:利用random随机产生QQ号…

  • 墙裂推荐4款js网页烟花特效

    以下是几款网页特效和一款软件:http://keleyi.com/keleyi/phtml/jstexiao/2.htmhttp://keleyi.com/keleyi/phtml/html5/14

    2021年12月22日
  • 基于Windows的安全帽识别算法「建议收藏」

    基于Windows的安全帽识别算法「建议收藏」现如今越来越重视安全生产,各个企业也都采取各种措施保障员工的安全生产从而保障了企业的利益。但是依然存在着在岗工人不佩戴安全帽从事危险作业,未佩戴安全帽作业而造成的伤亡时有发生。安全帽佩戴管理成为一大难点,为降低管理难度提高在岗人员安全意识,可在各种生产现场部署安全帽识别仪实时视频检测预警在岗工人是否按照要求做好安全防范措施作业。真正做到安全生产信息化管理,做到事前预防事中常态监测,事后规范管理。…

  • bootstrap分页css样式,修改bootstrap-table中的分页样式

    bootstrap分页css样式,修改bootstrap-table中的分页样式使用bootstrap-table时,使用$(“”)选择器没办法选中下方的分页button按钮,可能跟它是动态生成的有关吧。最终找到与之对应的js(bootstrap-table.js中搜索了data-toggle),将class类更换成自己定义的一个class,完成了期望的效果varpageNumber=[Utils.sprintf(”,this.options.paginationV…

  • 人性的弱点

    1.与人相处的基本技巧永远不要尖锐地斥责别人斥责百害无一利,控制自己或则试着帮助他真诚地赞美他人激发他人的强烈需求2.如何让他人喜欢你真诚地关心他人微笑待人记住他人的名字善于倾听

    2021年12月30日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号