在 Python 中, 一个. py 文件代表一个 Module 在 Module 中可以是任何的符合 Python 文件格式的 Python 脚本了解 Module 导入机制大有用处
1 Module 组成
1.1 Module 内置全局变量
2 Module 导入
2.1 导入及其使用
2.2 一次加载多次导入
2.3 搜索路径
- 2.4 reload
- 3 Package
- 3.1 __init__.py
- 3.2 __all__
- 3.3 __path__
1 Module 组成
一个. py 文件就是一个 moduleModule 中包括 attribute, function 等 这里说的 attribute 其实是 module 的 global variable
在一个 ModuleTests.py 文件中:
- #!python
- #-*- coding: utf-8 -*-
- """全局变量"""
- # hello doc
- global moduleName
- moduleName = __name__
- a = 1
- def printModuleName():
- print(a+1)
- print(__name__)
- print(moduleName)
- '''if __name__ =='__main__' :
- print('current module name is"' + __name__+'"')'''
- printModuleName()
- print(a)
- print(dir())
- import __builtin__
- print(__builtin__ == __builtins__)
- print(__doc__)
- print(__file__)
- print(__name__)
- print(__package__)
- __name__ = 'hello'
- print(__name__)
- View Code
除了你自己定义的那些全局变量和函数外, 每一个 module 还有一些内置的全局变量在这个 module 就包括了三个 attribute:a,moduleName,printModuleName 如果该模块被导入到另一个模块, 在另个一模块中, 就可以通过某种方式来访问这三个 attribute
1.1 Module 内置全局变量
每一个模块, 都会有一些默认的 attribute(全局变量)dir()函数 是 python 中的一个顶级函数, 勇于查看模块内容例如上面的例子中, 使用 dir()查看结果是:
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a', 'moduleName', 'printModuleName']其中 a, moduleName, printModuleName 是由用户自定义的其他的全是内置的
1)__name__ : 模块的名称例如上面的 ModuleTests.py, 模块的名称默认就是 ModuleTests 在运行时, 如果一个 module 是程序入口, 那么__name__就是__main__它是最常用的
2)__builtins__: 在 Python 中有一个内置的 module, 叫做:__builtin__, 它是一个 Python 的模块而任何一个 Python 的模块都有一个__builtins__全局变量, 它就是内置模块__builtin__的引用可以通过如下代码测试:
- import __builtin__
- print(__builtin__ == __builtins__)
- // 测试结果是 True
3)__doc__:module 的文档说明即便是 Python 的初学者都知道 Python 中的多行注释是用三对单引号或者双引号包含的网上有人说__doc__其实就是注释, 这句话呢说的太随意容易给人误解经过测试, 确认__doc__应该是: 文件头之后代码 (包含 import) 之前 的 第一个 多行注释
4)__file__: 当前 module 所在的文件的路径
5)__package__: 当前 module 所在的包名如果没有, 为 None
2 Module 导入
2.1 导入及其使用
一个 Module 可以导入 (import) 到其他的 Python 脚本中使用导入方式有多种:
- )import module1
- )import module1 as m1
- )from module1 import xxx
- )from module1 import xxx as yyy
从包 (package) 导入, 也分为类似的三种:
- )import p1.p2.p3.module1
- )import p1.p2.p3.module1 as m1
- )from p1.p2.p3.module1 import xxx
- )from p1.p2.p3.module1 import xxx as yyy
假设 module1 有两个 attribue: a1,a2, 两个 function: f1,f2 下面来说明这几种导入方式的区别:
方式一是导入整个 module1, 并将赋值给一个变量 module1, 来供使用使用时, 可以使用 module1.a1, module1.a2, module1.f1(params), module.f2(params)
方式二是在方式一的基础上, 重命名为 m1, 也就是说使用时得使用: m1.a1, m1.a2, m1.f1, m1.f2
方式三是导入模块的部分内容(导入一个或者一些 attribute 或者 function) 例如 from module1 import a1, 导入完成后, 在当前的模块中创建了一个 a1 的变量调用是直接调用 a1 即可
方式四对于导入一个 attribute 或者 function 时, 可以重命名例如 from module1 import a1 as msg, 那么导入完毕, 就是在当前的模块中创建了一个 msg 的变量, 指向了 module1.a1 我们在调用时, 只能通过 msg 来调用
2.2 一次加载多次导入
对于上面的 4 种导入方式, 不论哪一种, 都有两个阶段: 1)找到 module 对象, 2)按需分配给变量
模块本身就是为了复用的在一个大的项目中, 一些基础的公共的模块通常会被大量使用, 也就是说会被很多的 module 导入使用我们也知道, module 是放在 py 文件中的如个一个 module 被大量导入时, 难道要每一次导入, 都去磁盘上找一 py 文件吗?
显然不能这样设计, 如果真的这样设计, 程序的性能将是极差的了
对于同样的问题, Java 中的做法是, 使用 ClassLoader 加载类, 并采用父加载器委托机制尽可能的保证, 同一个 ClassLoader 下, 在多次引用一个类时, 都是同一个我们可以将该方式称为一次加载, 多地使用
Python 的设计者, 也考虑到这个问题也采用了类似方案, 被我称为一次加载, 多次导入我们假设它有一个 Module Loader 的存在, 在首次加载 (其实是首次 import) 时, 执行流程如下:
1)由 Module Loader 从检索路径下找出相应的模块
2)编译或者找到合适的字节码文件(.pyc 结尾)
3)解释执行要导入的 Module, 并放入缓存
4)将导入的 Module 对象 (或者其属性) 分配给当前 Module 下的变量
随后整个程序中再有执行 import 该 moudle 时, 只需要从缓存中拿到该 module, 然后执行 4)
此外, 对于过程 2)有这样 4 种情况:
A: 若. py 与. pyc 都存在: 会对. py 文件的最后修改时间与. pyc 文件的最后修改时间比较执行时间靠后的那个
B: 若. py 与. pyc 都不存在, 继续找, 如果最终都没有找到, 出错
C: 若. py 存在,.pyc 不存在: 编译. py 为. pyc
D: 若. py 不存在,.pyc 存在, 直接执行. pyc
再者还要说明 2 点:
1)一次加载, 多次导入的机制, 在 Python 程序包中提供的交互式命令行里使用 import 是不管用的在交互式下, 一次加载只能用于一次导入
2)一般 main py 是不会被编译成 pyc 的, 一个模块要想被编译成 pyc, 需要 import 到其他模块才行
2.3 搜索路径
依据 Java 编程经验来看, 通常程序会将文件放在不同的地方 Python 必然也不例外 Python 的搜索顺序为:
1) 已加载模块的缓存
2) 内置模块
3) sys.path
其中 sys.path 包含以下几部分:
1)入口程序的目录
2)系统环境变量 PYTHONPATH 代表的目录
3)标准 Python 库目录
4)任何. pth 文件的内容(如果存在的话)
下面使用命令看一下 sys.path 的目录有哪些:
['','C:\\windows\\SYSTEM32\\python27.zip','D:\\Program Files\\Python\\Python27\\DLLs','D:\\Program Files\\Python\\Python27\\lib','D:\\Program Files\\Python\\Python27\\lib\\plat-win','D:\\Program Files\\Python\\Python27\\lib\\lib-tk','D:\\Program Files\\Python\\Python27','D:\\Program Files\\Python\\Python27\\lib\\site-packages']
如果要加载的 module 不在上述目录下, 可以通过 3 钟手段:
1) 配置环境变量 PYTHONPATH, 配置是与环境变量 PATH 的风格一样
2) 程序动态修改 sys.path
3) 放到 site-packages 目录下
2.4 reload()
有些情况下, 我们需要在程序运行是对程序代码做修改例如我们需要监控某一方法执行快慢, 是否存在性能问题时我们需要在 function 的开始结束部分记录一个 startTime,endTime, 依此来判定执行性能时像这样的场景下, 因为 Module 的加载一次, 多长调用的机制, 我们修改完代码, 也不会生效此时就需要一种机制来重新加载 module, 以达到期望效果 reload() 就可以解决这个问题
reload(module) 是一个函数, 参数是一个 module 对象执行 reload, 就会等于再一次进行加载
- 3 Package
- 3.1 __init__.py
每一个 package 下必须有一个__init__.py 文件, 该文件用于表明当前目录可以作为一个 package
__init__.py 也是一个 python, 当首次加载相应的 package 时, 会执行__init__.py
__init__.py 文件可以什么也没有, 也可以指定__all__或(和)__path
3.2 __all__
__all__的值是一个列表, 用于当程序中使用 from pkg1.pkg2.pkg3 import * 时
就拿 Python_HOME/Lib / 下的 json 包来做实验, 由于该文件比较大, 我就写出主要部分:
- __version__ = '2.0.9'
- __all__ = [
- 'dump', 'dumps', 'load', 'loads',
- 'JSONDecoder', 'JSONEncoder',
- ]
- __author__ = 'Bob Ippolito <bob@redivi.com>'
- from .decoder import JSONDecoder
- from .encoder import JSONEncoder
- def dump(params):
- pass
- def dumps(params):
- pass
- def load(params):
- pass
- def loads(params):
- pass
目录结构如下:
当程序中使用 from json import * 引起 json 包首次加载时, 执行过程如下:
1)找到 json 目录, 执行__init__.py 执行完毕后: 包下会暴漏出: load,loads,dump.dumps, JSONDecoder, JSONEncoder
2)查找 * , 即从__all__找出要导出的变量
当程序使用 Import json.encoder 引起 json 包首次加载时, 执行过程如下:
1)找到 json 目录, 执行__init__.py 执行完毕后: 包下会暴漏出: load,loads,dump.dumps, JSONDecoder, JSONEncoder (以供 from json import * 使用)
2)导入 json 包到一个变量里(此后程序可以直接使用 json.encoder, json.decoder,json.scanner)
上述结论, 来源于下面的测试用例:
- #!python
- #-*- coding: utf-8 -*-
- """Package Import Test"""
- #from json import *
- from json import encoder
- import json
- print(json.encoder == encoder)
- print(json.decoder is None)
- print(json.scanner is None)
- print(dir())
- 3.3 __path__
该变量用于配置包下的搜索位置例如:
在 Utils 下增加 2 个目录 Linux 和 Windows, 并各有一个 echo.py 文件, 目录如下
Sound/Utils/
|-- Linux 目录下没有__init__.py 文件, 不是包, 只是一个普通目录
| `-- echo.py
|-- Windows 目录下没有__init__.py 文件, 不是包, 只是一个普通目录
- | `-- echo.py
- |-- __init__.py
- |-- echo.py
- |-- reverse.py
- `-- surround.py
如果__init__.py 是空的, 当使用 import Sound.Utils.echo 导入 echo 时, 会导入的是 Sound/Utils/echo.py
接下来我将__init__.py 做如下修改:
- import sys
- import os
- print "Sound.Utils.__init__.__path__ before change:", __path__
- dirname = __path__[0]
- if sys.platform[0:5] == 'linux':
- __path__.insert( 0, os.path.join(dirname, 'Linux') )
- else:
- __path__.insert( 0, os.path.join(dirname, 'Windows') )
- print "Sound.Utils.__init__.__path__ AFTER change:", __path__
在 Linux 上执行 import Sound.Utils.echo, 那么搜索路径就会变成了: 'Sound/Utils/Linux', 'Sound/Utils'以此来达到自动化的按需加载响应的 module 的功能
来源: https://www.cnblogs.com/f1194361820/p/8641288.html