初识模块
什么是模块
模块就是一系列功能的集合体,分为三大类:
1、内置的模块
2、第三方模块
3、自定义的模块
一个Python文件本身就是一个模块。例如:文件名为
m.py
,其模块名则为m
Ps:模块分为四种类型:
1、使用Python编写的
.py
文件2、已被编译为共享库或
DLL
的C或C扩展3、把一系列模块组织到一起的文件夹。(注:文件夹下有个
__init__
.py文件)
为什么要有模块
1.内置与第三方模块可以拿来就用,无需定义,这种拿来主义可以极大的提升自己的开发效率。
2.自定义的模块可以将程序各部分常用的功能提取出来放到一个模块中为大家共享使用,好处是减少了代码冗余,程序组织结构更加清晰。
怎么使用模块
如果是自定义模块需要先创建一个.py
文件:
#!/usr/bin/env python3# -*- coding: utf-8 -*-# 这是一个模块print("模块foo --->")x = 1def get(): print(x)def change(): global x x = 0
然后再到执行程序中去导入使用:
#!/usr/bin/env python3# -*- coding: utf-8 -*-# 对于内置模块来说import datetimeprint(datetime.datetime.now())# 对于自定义模块来说import foo # 文件名为foo.py,模块名为fooprint(foo.x)# ==== 执行结果 ===="""2020-05-21 22:29:28.448299模块foo --->1"""
模块与Py文件
两种导入方式的首次导入发生的事儿
#!/usr/bin/env python3# -*- coding: utf-8 -*-import foo # 文件名为foo.py,模块名为fooprint(foo.x)"""使用import首次导入模块会发生3件事: 1.执行foo.py 2.产生foo.py的命名空间,将foo.py运行过程中产生的名字都丢到foo的命名空间中 3.在当前run.py也就是程序文件中产生一个名字为foo.该名字指向foo名称空间如有重复导入,不必再次经历首次导入的三步骤。(命名空间以存在,常驻内存中..)"""# ==== 执行结果 ===="""模块foo --->1"""
#!/usr/bin/env python3# -*- coding: utf-8 -*-from foo import x # 文件名为foo.py,模块名为fooprint(x)"""使用from ... import ... 首次导入模块会发生3件事: 1.执行foo.py 2.产生foo.py的命名空间,将foo.py运行过程中产生的名字都丢到foo的命名空间中 3.在当前run.py也就是程序文件中产生一个名字为x.该名字指向foo名称空间中的同名变量所指向的内存空间如有重复导入,不必再次经历首次导入的三步骤。(命名空间以存在,常驻内存中..)"""# ==== 执行结果 ===="""模块foo --->1"""
.Py文件的两种用途
一个Python文件有两种用途,一种被当主程序/脚本执行,另一种被当模块导入,为了区别同一个文件的不同用途,每个py文件都内置了__name__
变量,该变量在py文件被当做脚本执行时赋值为__main__
,在py文件被当做模块导入时赋值为模块名。
作为模块foo.py
的开发者,可以在文件末尾基于__name__
在不同应用场景下值的不同来控制文件执行不同的逻辑
# foo.py...if __name__ == '__main__': foo.py被当做脚本执行时运行的代码else: foo.py被当做模块导入时运行的代码
通常我们会在if的子代码块中编写针对模块功能的测试代码,这样foo.py
在被当做脚本运行时,就会执行测试代码,而被当做模块导入时则不用执行测试代码。
#!/usr/bin/env python3# -*- coding: utf-8 -*-# 这是一个模块print("模块foo --->")x = 1def get(): print(x)def change(): global x x = 0if __name__ == '__main__': # Ps:由于导入模块时会先执行一遍模块文件。故可以采用此种方式做功能测试 get()else: print("不运行get..")"""执行结果如下: 1.当将foo.py当做主程序运行时,会 print("模块foo --->") 后 执行get()方法。 2.当将foo.py当做模块进行导入时,会 print("模块foo --->") 后 print("不运行get..") 。"""
模块导入相关
import导入模块
强调1:模块名.名字,是指名道姓的问某一个模块要名字对应的值,不会与当前命名空间发生冲突。(即使模块中没有该名字,也不会来执行文件中找)
强调2:无论是查看还是修改操作的都是模块本身,与调用位置(执行文件)无关。
使用
import
导入模块的优缺点:优点:肯定不会与当前名称空间中的名字冲突
缺点:使用模块中功能或变量时需加前缀
#!/usr/bin/env python3# -*- coding: utf-8 -*-import foo # 文件名为foo.py,模块名为foodef get(): # run.py全局命名空间下定义了get() print("run.py ... get()")foo.get() # 运行依旧是foo中的get()x = 33333print(foo.x) # 拿foo中的x,就算foo中没有定义x也不会来run.py中找xfoo.change() # 修改的是foo中的x内存指向,与run.py中的x无关。即使foo中没有x也不会修改run.py中的xprint(foo.x)print(x)"""强调1:模块名.名字,是指名道姓的问某一个模块要名字对应的值,不会与当前命名空间发生冲突。(即使模块中没有该名字,也不会来执行文件中找)强调2:无论是查看还是修改操作的都是模块本身,与调用位置(执行文件)无关。"""# ==== 执行结果 ===="""模块foo --->11033333"""
from .. import .. 导入模块
强调1:使用
from .. import ..
的方式可能会导致命名空间冲突的问题。使用
from .. import ..
导入模块的优缺点:优点:在使用模块中功能时,代码更加精简
缺点:容易与当前命名空间发生混淆。
#!/usr/bin/env python3# -*- coding: utf-8 -*-from foo import x # 文件名为foo.py,模块名为fooprint(x) # 此刻的x指向的内存地址是foo.x指向的内存地址x = 33333print(x) # 此刻的x已经改变指向"""强调1:使用from .. import .. 的方式可能会导致命名空间冲突的问题。"""# ==== 执行结果 ===="""模块foo --->133333"""
导入模块的规范
"""导入模块的规范: 1.Python内置模块 2.第三方模块 3.程序员自定义模块模块是第一类对象(允许被当做参数传入等等...)自定义的模块命名应该采用纯小写+下划线的风格 Ps: 尽管Python2中有些模块是以驼峰体进行命名,但是在Python3中他们都全部变为纯小写了。 如:Python2中的PyMySql已经命名为pymysql"""import datetime,time,os # 使用 , 逗号 可以一行导入多个模块.但是不建议这么做。import collections as coll # 使用 as 可以为模块取一个别名。from foo import x,get,change # 使用 , 逗号 可以一行导入同个模块下的多个功能.但是不建议这么做。from foo import * # 导入模块中的所有功能。(大多数情况下不推荐使用,视情况而定)
模块查找相关
模块查找优先级
无论是
import
还是from .. import ..
在导入模块时都涉及到查找的问题。优先级:
1.内存(内置模块)
2.硬盘(
sys.path
)当一个模块被导入过一次后,就会加载至内存中,重复导入便可直接从内存中拿到该模块。
sys.modules查看内存模块
1.
sys.modules
是导入模块后最先查找的路径顺序,当模块名不存在于该字典时将去硬盘中进行查找。2.当首次导入一个模块之后,该模块会在此次执行文件运行的过程中将首次导入的模块路径存放至内存中,
del
该模块后解除变量名与其绑定关系。(只要程序再运行,二次导入都从内存中导入)3.当二次导入该模块时依照查找顺序直接从内存就可导入该模块。
#!/usr/bin/env python3# -*- coding: utf-8 -*-import sysimport foo # 执行foo,开始首次导入三步骤del foo # <=== 此时已经将foo模块加载至内存中,即使删除绑定关系也无妨print("foo" in sys.modules)import foo # <--- 第二次导入不会执行首次导入三步骤。print(sys.modules)# 查看已经加载至内存中的模块# ==== 执行结果 ==== Ps: <--- 可以看到foo即使被del也依旧存在于内存中"""模块foo --->True{'sys': <module 'sys' (built-in)>, 'builtins': <module 'builtins' (built-in)>,'_frozen_importlib': <module '_frozen_importlib' (frozen)>, '_imp': <module '_imp' (built-in)>, '_warnings': <module '_warnings' (built-in)>, '_frozen_importlib_external': <module '_frozen_importlib_external' (frozen)>, '_io': <module 'io' (built-in)>, 'marshal': <module 'marshal' (built-in)>, 'nt': <module 'nt' (built-in)>, '_thread': <module '_thread' (built-in)>, '_weakref': <module '_weakref' (built-in)>, 'winreg': <module 'winreg' (built-in)>,'time': <module 'time' (built-in)>, 'zipimport': <module 'zipimport' (frozen)>, '_codecs': <module '_codecs' (built-in)>,'codecs': <module 'codecs' from 'C:\\Python38\\lib\\codecs.py'>, 'encodings.aliases': <module 'encodings.aliases' from 'C:\\Python38\\lib\\encodings\\aliases.py'>,'encodings': <module 'encodings' from 'C:\\Python38\\lib\\encodings\\__init__.py'>, 'encodings.utf_8': <module 'encodings.utf_8' from 'C:\\Python38\\lib\\encodings\\utf_8.py'>, '_signal': <module '_signal' (built-in)>, '__main__': <module '__main__' from 'C:/Users/Administrator/PycharmProjects/learn/ModelsLearn/run.py'>,'encodings.latin_1': <module 'encodings.latin_1' from 'C:\\Python38\\lib\\encodings\\latin_1.py'>, '_abc': <module '_abc' (built-in)>, 'abc': <module 'abc' from 'C:\\Python38\\lib\\abc.py'>, 'io': <module 'io' from 'C:\\Python38\\lib\\io.py'>, '_stat': <module '_stat' (built-in)>, 'stat': <module 'stat' from 'C:\\Python38\\lib\\stat.py'>, '_collections_abc': <module '_collections_abc' from 'C:\\Python38\\lib\\_collections_abc.py'>,'genericpath': <module 'genericpath' from 'C:\\Python38\\lib\\genericpath.py'>, 'ntpath': <module 'ntpath' from 'C:\\Python38\\lib\\ntpath.py'>, 'os.path': <module 'ntpath' from 'C:\\Python38\\lib\\ntpath.py'>, 'os': <module 'os' from 'C:\\Python38\\lib\\os.py'>,'_sitebuiltins': <module '_sitebuiltins' from 'C:\\Python38\\lib\\_sitebuiltins.py'>, 'sitecustomize': <module 'sitecustomize' from 'D:\\Application\\PyCharm 2020.1\\plugins\\python\\helpers\\pycharm_matplotlib_backend\\sitecustomize.py'>, 'site': <module 'site' from 'C:\\Python38\\lib\\site.py'>, 'foo': <module 'foo' from 'C:\\Users\\Administrator\\PycharmProjects\\learn\\ModelsLearn\\foo.py'>}"""
sys.path的应用
1.当内存中没有模块路径时,将按照
sys.path
的路径顺序依次在硬盘中查找。2.Pycharm中的
sys.path
比Python原生解释器中的sys.path
多了三个选项。分别是(第二项,第三项,倒数第一项)3.可使用
sys.path.append("模块绝对路径")
添加搜索路径。
#!/usr/bin/env python3# -*- coding: utf-8 -*-import sysimport foo # 执行foo,开始首次导入三步骤print(sys.path)# 当内存中没有模块路径时,将按照sys.path的路径顺序依次在硬盘中查找。# ==== 执行结果 ===="""Ps:Pycharm执行该脚本时产生的结果模块foo --->['C:\\Users\\Administrator\\PycharmProjects\\learn\\ModelsLearn', 'C:\\Users\\Administrator\\PycharmProjects\\learn', # 不存在'D:\\Application\\PyCharm 2020.1\\plugins\\python\\helpers\\pycharm_display', # 不存在'C:\\Python38\\python38.zip','C:\\Python38\\DLLs','C:\\Python38\\lib','C:\\Python38','C:\\Python38\\lib\\site-packages','D:\\Application\\PyCharm 2020.1\\plugins\\python\\helpers\\pycharm_matplotlib_backend' # 不存在]""""""Ps:Python解释器执行该脚本时产生的结果模块foo --->['C:\\Users\\Administrator\\PycharmProjects\\learn\\ModelsLearn','C:\\Python38\\python38.zip','C:\\Python38\\DLLs','C:\\Python38\\lib','C:\\Python38','C:\\Python38\\lib\\site-packages']"""
包
包与__init__
1.包就是一个包含有
__init__.py
文件的文件夹。(在Python2中包必须有__init__.py
文件,Python3中不强制有此要求。)2.包的本质是模块的一种形式。
导入包的三步骤:
1.产生一个命名空间
2.执行
__init__.py
文件将产生的名字丢到该包的命名空间中3.在当前
run.py
中拿到一个变量,该变量指向包的命名空间。
# 包可以当做一系列模块的集合体(包本身也是一个模块)# 自定义包:"""my_plugin/ #顶级包├── __init__.py ├── tools #子包│ ├── __init__.py│ ├── f1.py│ └── f2.py└── m1.py #子模块foo.pyrun.py"""# === my_plugin/__init__.py ===from . import m1from . import tools# === my_plugin/ m1.py ===def plugin_m1(): print("plugin_m1...")# === my_plugin/tools/__init__.py ===from . import f1from . import f2# === my_plugin/tools/f1.py ===def tools_f1(): print("tools_f1...") # === my_plugin/tools/f2.py ===def tools_f2(): print("tools_f2...")
使用者 - 包导入强调三点
1.关于包相关的导入语句也分为
import
和from .. import ..
两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如import 顶级包.子包.子模块
,但都必须遵循这个原则。但对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。使用者无法使用from . import xxx
的操作,该操作只能在__init__
中使用。也就是说对于使用者只能使用绝对导入。2、包
A
和包B
下有同名模块也不会冲突,如A.a
与B.a
来自俩个命名空间3、
import
导入文件时,产生名称空间中的名字来源于文件,import
包,产生的名称空间的名字同样来源于文件,即包下的__init__.py
,导入包本质就是在导入该文件
#!/usr/bin/env python3# -*- coding: utf-8 -*-# 根据包名使用各个功能from ModelsLearn import my_pluginmy_plugin.m1.plugin_m1()my_plugin.tools.f1.tools_f1()my_plugin.tools.f2.tools_f2()# 引入某一个单独的文件from ModelsLearn.my_plugin import m1m1.plugin_m1()# 引入单独的某一个功能from ModelsLearn.my_plugin.m1 import plugin_m1plugin_m1()# ==== 执行结果 ===="""plugin_m1...tools_f1...tools_f2...plugin_m1...plugin_m1..."""
开发者 - 包内绝对导入
.
代表当前文件夹
..
代表上层文件夹包内的绝对导入是指:
from
无论何时都跟上后面跟顶级包名(不推荐这么做,万一后期包名需要更改,很麻烦。)
# my_plugin/__init__.py# from . import m1 # 对于开发者来说,只要该文件不是执行文件就可以使用 from . import xxx# from . import tools# === Ps === : 为了使用者的使用方便。我们可以更加详细的导入(此处导入为绝对导入)from my_plugin.m1 import plugin_m1from my_plugin.tools.f1 import tools_f1 from my_plugin.tools.f2 import tools_f2# run.py 执行文件# 使用者可以更简单的使用了from ModelsLearn import my_pluginmy_plugin.plugin_m1()my_plugin.tools_f1()my_plugin.tools_f2()
开发者 - 包内相对导入
使用
from . import xxx
其中
.
代表当层文件夹。(注意:包的使用者不能使用from . import xxx
的操作,因为对于使用者来说.
的左边必须是一个包名)注意,当使用
..
的时候不能返回顶级包以上的目录。开发者若想使用
from . import xxx
,则必须在__init__.py
文件中使用。其余任何地方使用都会引起异常
# my_plugin/__init__.py# from . import m1 # 对于开发者来说,只要该文件不是执行文件就可以使用 from . import xxx# from . import tools# === Ps === : 使用 from . import xxx 的文件不可被当做执行文件执行。只能被当做模块from .m1 import plugin_m1from .tools.f1 import tools_f1from .tools.f2 import tools_f2
扩展:循环导入问题及解决方式
循环导入是指:
模块
a
中导入模块b
,模块b
中又导入模块a
...循环导入可能导致的问题:
命名空间中名字可能加载不全...
解决办法:
1.导入语句放到最后,保证在导入时,所有名字都已经加载过
2.导入语句放到函数中,只有在调用函数时才会执行其内部代码
# === run.py ==="""结构如下:- foo1.py- foo2.py- run.py"""import foo1 # 首次导入,执行foo1.pyprint(foo1.x)# === foo1.py ===from foo2 import y # 首次导入,执行foo2.pyx = 10 # 根本来不及将x放入foo1的模块命名空间中,就又去执行foo2去了# === foo2.py ===from foo1 import x # foo1 二次导入,不会执行。尝试拿到x,抛出异常。y = 20 # 由于第一行是导入x。 # 但是foo1中并没有把x放入foo1模块命名空间中去, # 直接抛出异常导致foo2的y也放不进foo2的模块命名空间中去# 执行run.py:\Python38\python3.exe C:/Users/Administrator/PycharmProjects/learn/ModelsLearn/run.pyTraceback (most recent call last): File "C:/Users/Administrator/PycharmProjects/learn/ModelsLearn/run.py", line 4, in <module> import foo1 File "C:\Users\Administrator\PycharmProjects\learn\ModelsLearn\foo1.py", line 1, in <module> from foo2 import y File "C:\Users\Administrator\PycharmProjects\learn\ModelsLearn\foo2.py", line 1, in <module> from foo1 import xImportError: cannot import name 'x' from partially initialized module 'foo1' (most likely due to a circular import) (C:\Users\Administrator\PycharmProjects\learn\ModelsLearn\foo1.py)# 可以看到。是 x 抛出的异常,原因上面说的很清楚了,x并没有放入foo1的模块命名空间中去,但是foo2的x尝试将内存指向foo1中的x的内存指向# 解决方案1# === run.py ===import foo1print(foo1.x)# === foo1.py ===x = 10from foo2 import y # 先将 x 放入 foo1 的模块命名空间中,就没问题了。# === foo2.py ===y = 20from foo1 import x # 解决方案2# === run.py ===import foo1print(foo1.x)# === foo1.py ===def init(): from foo2 import y x = 10init() # 函数定义阶段不会执行,最后调用时x也是已经放在了 foo1 的模块命名空间中。也不会抛出异常# === foo2.py ===def init(): from foo1 import xy = 20init()
扩展:编写规范模块
#!/usr/bin/env python #通常只在类unix环境有效,作用是可以使用脚本名来执行,而无需直接调用解释器。"The module is used to..." #模块的文档描述import sys #导入模块x=1 #定义全局变量,如果非必须,则最好使用局部变量,这样可以提高代码的易维护性,并且可以节省内存提高性能class Foo: #定义类,并写好类的注释 'Class Foo is used to...' passdef test(): #定义函数,并写好函数的注释 'Function test is used to…' passif __name__ == '__main__': #主程序 test() #在被当做脚本执行时,执行此处的代码
扩展:* 与 __all__
# foo.py__all__ = ["x","y"] # #该列表中所有的元素必须是字符串类型,每个元素对应foo.py中的一个名字x = 10y = 20z = 30"""也就是说,当使用 from ModelsLearn.foo import * 的时候,* 只能拿到 __all__ 中存在的名字。"""# run.pyfrom ModelsLearn.foo import *print(x) # 正常访问print(y) # 正常访问print(z) # 抛出异常# ==== 执行结果 ===="""1020Traceback (most recent call last): File "C:/Users/Administrator/PycharmProjects/learn/ModelsLearn/run.py", line 9, in <module> print(z) # 抛出异常NameError: name 'z' is not defined"""
还没有人抢沙发呢~