python 中模块和模块包
模块相关概念
模块是高级别的程序组织单元,它将程序代码和数据封装起来以便重用。
模块可以通过下面两个语句导入
- import
- from
模块的作用
- 代码重用
- 系统的划分命名空间
- 实现共享服务和数据
python import模块如何工作
python在执行import语句时,会有下面三个操作:
1. 找到模块文件
首先python会把载入的模块存储到一个名为sys.modules的表中,并在一次导入操作的开始检查该表。
如果模块不存在,将会启动搜索过程(一旦导入一次此模块就存在于sys.modules了,
当再次import 时,必须首先reload,不然修改不生效):
搜索路径顺序
-
程序的主目录
首先python会在运行文件的当前目录内搜索导入的文件,如果运行的是顶层文件(此时路径为顶层文件当前路径)。 交互式窗口中运行,这一入口就是当前工作目录。这个搜索是第一优先级的。 如果在这个目录下搜索到对应名字的脚本,将会使用此脚本。 所以避免和库文件使用相同的名字。除非你清楚的知道这个问价你是要替换掉库文件。
-
PYTHONPATH 目录
如果程序的主目录中没有找到对应的脚本文件,则执行PYTHONPATH 目录。 PYTHONPATH 是指:电脑中的环境变量中名字为PYTHONPATH 的变量值。 设置步骤:我的电脑--属性-->高级-->环境变量,新建/编辑 PYTHONPATH的变量,将路径添加进去。
-
标准连接库目录
当以上两个都没有的时候,python会自动搜索标准库模块安装在及机器上的那些目录。 这些通常是不需要安装在PYTHONPATH中的
-
任何.pth文件的内容
最后,python有个新的功能,允许用户把有效的目录添加到模块搜索路径中去, 也就是后缀名 为.pth的文本文件中一行一行的列出来。 但是.pth必须放在python中的sitepackages中,如何判断是否是sitepackages: a) 将.pth文件放入python中定义的用户的sitepackages
import site
site.getsitepackages()
通过这个命令一般会得到两个路径,[0]是user的site-packages,[1]是库文件所在的site-packages.
一般我们建议把自己定义的my.pth放在user的site-packages
b) 用户定义自己的某个folder路径为sitepackages**
import site
site.addsitedir(YOUR_PATH)
执行完此条命令后,python会把路径下的.pth文件中的路径添加到sys.path中。
因此此方法等于通过sys.path.append()语句一条一条的添加
总结:无论通过哪种方法来添加搜索路径,最终的搜索路径都会出现在sys.path中。我们可以直接通过添加到sys.path中来增加可见性。其中搜索顺序是按照sys.path中的顺序进行的。
2 .编译成位码(如果需要)
当找到符合import语句的源代码文件后,如果有必要python接下来会将源代码编译成字节码(.pyc)。
只有被导入的文件才会产生.pyc字节码文件,顶层文件是不会产生的。
python 会通过实践戳来判断源代码是否有更新。如果只找到字节码,那么跳过编译。
3 .执行模块的代码来创建其定义的对象
import操作的最后步骤是执行模块的字节码。文件所有语言都会依次执行,产生文件的属性。例如def 语句会在导入时创建 函数,
此时模块就有了这个函数的属性。对于那些在顶层的语句,例如单独一行的print函数会在第一次导入时执行。
所以我们建议如果是需要做顶层测试的语句通过__name__和__main__来设计。
对于已经导入过的文件(已经存在于sys.modules),再次使用import 语句时,上面三个步骤不会执行,python会加载内存内的模块.
可以通过下面的命令来查看已经导入的模块文件
import sys
list(sys.modules.keys())
import 和from 是赋值语句
因为无论使用import还是from语句,其本质都是赋值语句。
通过import语句的,调用变量时,需要使用module.x语句
通过from语句,是讲模块中的变量名赋值给另一个模块中的同名变量。
所以通过from语句赋值的变量名会变成共享对象的引用。因此根据此变量时可变(本地可以修改)还是不可变对象,针对变量的修改会影响到原有模块中的同名变量。
# pack_try0.py
x=1
y=[2,3]
通过GUI执行
>>> x=5
>>> y=[78]
>>> from pack_try0 import x,y
>>> x
1
>>> y
[2, 3]
>>> x=5
>>> y[0]=4
>>> y
[4, 3]
>>> import pack_try0
>>> pack_try0.x
1
>>> pack_try0.y
[4, 3]
>>>
>>> y=99
>>> pack_try0.y
[4, 3]
>>>
此处可以看到,通过from语句导入的变量x,y覆盖了本地的x,y。因为from 是赋值。
x的对象是1(数字)
y的对象是[2,3](list),
数字是不可改变的对象,list是可以本地修改的可变对象。
所以对于x变量的修改(x=5),并没有修改原始文件中的值。
对于变量y的修改(y[0]=4)改变了原始文件中的y值。因为此时y是引用了可变对象。
对于y=99,因为此时本地的y引用了另一个对象99(数字),所以此时y已经不是对原有导入的文件中的y的引用了。
不会影响原有文件中的y的值
模块包
除了能够导入某个模块名外,导入也可以指定目录路径,python代码的目录就成为包。这种导入目录就称为包导入。
包导入基础
-
导入语法
包导入中子目录中间是以点号相隔的。例如import dir1.dir2.mod
-
init.py包文件
在包导入的每一级路径下都必须有一个__init__.py的文件。否则包导入会失败。 作用: - 包的初始化 python首次导入某个目录的时,会自动执行该目录内的__init__.py中所有程序代码 例如我们的例子中如果import dir1 ,则会执行dir1下的__init__.py,如果import dir1.dir2则会执行 dir1下的__init__.py 和dir2 下的__init__.py - 模块命名空间的初始化 在包导入的时候,脚本内的目录路径,在导入后会变成真实的嵌套对象路径,即,import dir1,此时 dir1变为了一个模块对象,此对象的命名空间内包含了所有在__init__文件中的所有变量。 调用中可以用dir1.x调用__init__中的变量x - from*语句的行为 __init__.py中可以实现所有的代码,即可以定义函数,变量等。但当有其它的正常模块的文件时, 我们需要通过在__init__.py中使用__all__列表来定义目录中需要被导入的变量。因为通过包导入 的时候,python只会导入__init__.py中的变量。当然我们可以在__init__.py中使用 import/from 来导入当前目录下其它的文件中的变量.
注意:init.py文件,如果你用不着也必须存在,你可以创建一个空的文件。
实例
包目录结构:
在python的搜索目录中,包含
– dir1
– init.py
–dir2
–init.py
–mod.py
#di11/__init__.py
print('dir1 init')
x=1
y=[1,2,3]
#dir1/di12/__init__.py
print('dir1/dir2 init')
z=1
m=[1,2,3]
#dir1/dir2/mod.py
print("dir1/dir2/mod.py")
n=4
-
实例1 import dir1
>>> import dir1 dir1 init >>> dir1.x 1 >>> dir1.y [1, 2, 3]
实例2 import dir1.dir2
import dir1.dir2 dir1 init dir1/dir2 init >>> dir1.x 1 >>> dir1.y [1, 2, 3] >>> dir1.dir2.m [1, 2, 3] >>> dir1.dir2.z 1 >>> dir1.dir2.mod.n Traceback (most recent call last): File "<pyshell#9>", line 1, in <module> dir1.dir2.mod.n AttributeError: module 'dir1.dir2' has no attribute 'mod'
-
实例3
#根据实例2,基础上 import dir1.dir2.mod dir1/dir2/mod.py >>> dir1.dir2.mod.n 4
上面的实例我们可以看到通过导入包的时候mod中的变量没有被导入。因为在__init__.py中看不到对应的变量。
那么可以通过我们所说的两种方式
1. __ all __
__all__只对from import *有用。
例如我们把dir2/__init__改为
这里使用的是module的名字作为__all__列表的某个值
#dir1/di12/__init__.py
__all__=['mod']
print('dir1/dir2 init')
z=1
m=[1,2,3]
执行from dir1.dir2 import *
此时只有mod.n可以调用
2. 使用import在__init__.py中
改变dir2/__init__.py 为
这里的.mode中的.为当前目录(后面会解释这个用法)
#dir1/di12/__init__.py
#__all__=['mod']
from .mod import n
print('dir1/dir2 init')
z=1
m=[1,2,3]
**注意: 这里我们说的import dir1/import dir1/dir2/from dir1/dir2 import 等是基于通过外部文件导入当前包文件。 我们可以看到这里在__init__.py中使用from .dir1 import 等语句时基于包内的导入。这个时候的点号就是包相对导入的一种。下面我们会介绍。
通过外部访问包时
- 此时的包路径必须在搜索路径内。(可以通过我们之前将的方式设置当前包为搜索路径内)
包相对导入
目前为止,通过包导入(import dir1.dir2)也可以用于包内导入,但是相对来说比较麻烦。包自身内部可以使用相对导入,是相对于包,而不是先列出包导入路径。
相对导入实例:
from . import xx
from .dir2 import xx
from .. import xx
from ..dir2 import xx
相对导入的作用域
- 相对导入适用于只在包内导入
- 相对导入只是用于from语句