Python模块化

Python的包和模块

module类型

在Python中,使用import关键字导入一个包或者模块,模块是一个名为module的类型,只有模块类型才可以直接使用import导入。首先是导包过程。

创新互联成立与2013年,是专业互联网技术服务公司,拥有项目成都网站建设、做网站网站策划,项目实施与项目整合能力。我们以让每一个梦想脱颖而出为使命,1280元西峰做网站,已为上家服务,为西峰各地企业和个人服务,联系电话:18982081108

print('导入前:', dir())   # 导包前全局变量
import os
print('导入后:', dir())   # 导包后全局变量

-----输出结果-----#省略
导入前:全局变量中没有"os"
导入后:出现了"os"

这说明全局变量中出现了os标识符,这也是我们为什么可以使用os.chdir()等通过os标识符进行成员访问的原因。

再看一下os标识符的类型及os指向的这个对象。

print(type(os))
print(type(os.path))
print(global()["os"])  # 收集全局变量,查看os对象信息

-----输出结果-----


上面结果显示osos.path是一个module类型,这也是os可以使用import导入的原因。只有module类型才能被导入。同理os.path也可以直接被导入。

print(dir())
import os.path
print(dir())
-----输出结果-----#省略
导入前:全局变量中没有"os" 或者 "os.path"
导入后:出现了"os",但是没有"os.path"

os.path并不是一个合法的标识符(字符,下划线,数字),所以这个os.path对象没有被变量接受,但是我们之后需要能访问这个os.path对象 ,所以python为我们保留了os变量指向os模块。我们才可以使用os.path的方式访问。当然我们使用别名的方式os.path就可以被接收了。

import os.path as osp
osp.dirname(".")           # osp 即指向 os.path模块对象.调用它全局空间中的dirname(".")函数                   
print(type(osp))           # 
print(globals()["osp"])    # 

osp直接指向了我们需要os.path模块,我们可以osp进行访问,os变量也就没有意义,所以此时的全局变量总并不存在os这个全局变量,但是os这个模块确实被import加载到内存空间中。只是在该模块中没有对应的表示符进行对应。

模块搜索顺序

sys.path中记录了模块的所有顺序,并且可以程序执行时候,导入了哪些类,所有被导入的类都会缓存在sys.modules中,调用即可查看。

import sys
print(*sys.path, "\n", *sys.modules, sep="\n")
--------输出结果------
'''
D:\学习资料\练习项目\pycharm基础练习\myproject   # 当前路径
D:\学习资料\练习项目\pycharm基础练习             # pycharm 创建的项目环境
E:\Python\python36.zip                       # 以下为Python安装路径下的各个路径,可以为zip,egg文件
E:\Python\DLLs
E:\Python\lib
E:\Python
E:\Python\lib\site-packages
'''

{'builtins': ,   # 已经被导入的各个模块信息
'sys': , 
'os': , 
'_frozen_importlib': , 
'myproject': }

.egg文件是由setuptools库创建的包,第三方库常用的格式,添加了元数据信息的zip文件。

使用from导入

from ... import ...从一个模块中导入对象,这个模块可能是一个.py文件也可能是一个包(目录),导入的对象可以是包或者模块下的函数,类,模块等全局变量标识符,并且导入后可以直接使用import后的原变量标识符进行访问,而不需要加上模块前缀。fromimport后指定的内容都将被加载到内存。

from os import path          # 从os 导入 path模块,并可以使用path全局变量直接访问
from os.path import dirname  # 从os.path 模块导入 dirname方法,可以直接通过dirname标识符访问该方法对象。

from os.path import *        # 默认从os.path模块导入所有公共变量(非下划线开头),并直接使用原变量名作为该包下的全局变量

使用第三种方式容易造成该包中的全局变量被导入包中全局变量覆盖。除非清楚的知道两个包中的变量不会重名,否则尽量不使用。

使用 from ... import *默认导入的是公开变量(非___打头的变量),非公开变量导入必须手动才能访问,或者添加到all属性中才能使用*导入

from os import _abc, __abc   # 直接指定可导入,* 默认不含该类属性
__all__

在被导入模块中,可以添加__all__属性来指定该模块默认的全部可向外导出的模块。配合from ...import * 使用。__all__属性是一个字符串列表,这些字符串必须与该全局空间中的全局变量名对应,或者与该包下的子包名对应。

__all__ = ["x", "y", "_z"]

x = 1
y = 2
z = 3
_z = 4 

这个包被其他包使用 *导入时,导出__all__中对应的x, y,_z变量,实现了我们手动控制变量的导出。

包导入时属性优先,没有这个属性才会判断是否有这个模块。所以如果属性名和模块名冲突,该模块将无法再被导出。

Python中的包就是一个文件夹,文件夹下放各个模块的.py文件或者一个子包,方便管理。在包上也是可以写代码,这就是需要这个包下的__init__.py文件,这个文件属于这个包,当我们仅仅导入一个包时候,就是导入这个包下的__init__.py文件中的全局变量所指定的内容。并不会自动的导入这个包下的子模块。

包文件和模块文件

包模块的代码托管到包下的__init__.py文件中,即使包没有__init__文件仍然可以被导入,但并没有导入任何内容,这种情况下只能通过这个包名去寻找这个包下的模块文件。使用from ... inport ...导入子模块中的内容。

包是一个稍微特殊的模块对象,包的属性有['__doc__', '__loader__', '__name__', '__package__', '__path__', '__spec__'],根据__package__属性可以判断它是否是一个包,该属性为None或者不存在表示不是包。

包下的模块为这个包的子模块,他们之间可以通过package.mod_name进行访问,前提是这个模块已经被导入,可以使用from package import mol_name或者import package.mol_name进行导入,两种导入效果相同,只是导入空间的变量名不同,使用形式不同。但是只import package无法自动导入它的子包,package.mul_name将不能访问。

相对导入和绝对导入

使用import导入模块顺序在sys.module记录,默认优先从执行文件的当前目录中寻找,无法找到再去Python安装文件中的库中寻找。这是一种绝对路径导入。

相对导入通常在一个包中使用,方便的将包中的各个模块进行关联,这种关联只受包内文件的相对位置约束,然而对于一个包我们通常不会去干涉内部的文件关系。相对导入原则遵循文件系统使用原则,即使是包__init__.py文件,这时属于这个包下的一个子文件,等同于这个包下的其他子文件。

符号示例
. .m 当前文件夹中的m模块
.. ..m 上一级文件中的m模块
... ...m 上上一级文件中的m模块
from .m import a     # 将会在当前目录下找 m 文件并导入a
from ..m import a    # 访问该文件的上一层目录找 m 文件并导入a
from .m.m1 import a  # 当前文件所在目录中的m包下的 m1文件并导入a

以上导入方式只能在包中使用,(任何一个相对导入的路径不能涉及主包所处的当前目录)这些文件不能作为主模块直接运行,它是一种包内部的关系的建立方式,直接运行将报错。

命名空间

当我们导入一个模块时,我们可以访问这个模块是因为在当前的作用域内部,创建了一个与模块名同名的全局变量,才可以通过这个全局变量对这个模块进行访问。所以我们是否能使用一个模块,需要两个条件:

  • 1、该模块被加载到内存中
  • 2、可以找到一个标识符直接和这个模块对象关联或者可以通过其他标识符的成员访问方式访问到这个模块对象。

不同的导入文件方式在这个命名空间中创建的标识符是不同的

import os        # 只导入os模块(os下的__init__.py中的内容),创建os标识符
import os as o   # 只导入os模块,创建o标识符

import os.path   # 同时导入os 和 os.path,只创建 os标识符,指向os模块。os.path可访问
import os.path as osp  # 同时导入os 和 os.path,只创建 osp标识符,指向os.path模块,os模块在内存,但是无法访问,无标识符

from os import path   # 同时导入os 和 os.path,只创建path标识符,指向os.path。os无法访问,无标识符
from os import *      # 同时导入os中所有公开变量,如果有__all__属性,导入指定的内容,创建所有 * 指代的标识符,不会创建os

form .m import a   # 在包内导入, m 和 a 均导入,m标识符可能存在,可使用 dir查看确认再使用,a 标识符存在

自定义模块

模块命名规范
在导入模块时,会使用模块名作为标识符,这就要求模块名满足标识符的命名规范,这样才可以被导入。并且模块一帮使用小写字符,可以使用下划线分割。

模块重复导入

模块被加载一次后会在内存中缓存这个模块对象,这个模块的辨识信息被缓存到sys.modules中。导入模块时候,首先是从sys.modules中查询该模块是否已经被加载,如果已存在,并不会从文件中读取,而是直接使用内存中已存在的这个模块对象,否则import会IO搜索该模块的文件,编译,执行这个模块并加载大内存。

缓存的模块被保存在sys.module中,包括自己,其中还包括解释器运行需要的模块。
自定义模块a/b/c路径,如果只导入a模块import a,直接使用a.b是有风险的,该模块没有被加载,只有使用import关键字时,解释器才会模块进行加载,而不会自动的加载。

模块运行

当我们执行一个.py文件,此时解释器会启动去执行,但在执行这个文件之前,解释器还需要其他的工作,比如导入一些包,例如io,将我们的.py文件读入内存,或者build-in,因为我们可能使用到了这个内建函数。还有其他很多准备流程,当这个工作完成后,才会执行我们自己的.py文件。而这个.py文件也会被命名为__main__包。查看这个模块名,显示为__main__

print(__name__)       # __main__

除解释器执行的代码外,这个模块将会作为最外层的代码块,这个模块的结束代表该线程的结束。也被称作顶层代码。顶层代码包的"name"属性为__main__,其余模块的__name__属性为模块名。所以测试代码通常这样写

if __name__ == "__main__":
    # test code   

只有这个模块直接执行时,if 中的测试代码才会执行。


文章名称:Python模块化
标题路径:http://ybzwz.com/article/pppdeh.html