一、概论
模块支持从逻辑上组织python代码。
当代码量变得相当大的时候,我们最好把代码分成一些有组织的代码段,前提是保证它们的彼此交互。
把其他模块中属性附加到你的模块中的操作叫做导入(import),那些自我包含并且有组织的代码片段就是模块(module)。
如果说模块是按照逻辑来组织python代码的方法,那么文件便是物理层上组织模块的方法。
因此,一个文件被看作是一个独立的模块,一个模块也可以被看作是一个文件,模块的文件名就是模块的名字加上扩展名.py.
一个名称空间就是一个从名称到对象的关系映射集合,模块名称是它们的属性名称中一个重要的部分。
向名称空间添加名称的操作过程涉及绑定标识符到指定对象的操作(以及给该对象的引用计数加1)。
改变一个名字的绑定叫做重新绑定,删除一个名字叫做重新绑定。如果在执行期间调用了一个函数,那么将创建局部名称空间。
给定一个模块名之后,只可能有一个模块被导入到python解释器,所以在不同的模块间不会出现名称交叉的现象,故而每个模块都定义了它自己的唯一的名称空间。
模块可以包含可执行的语句和函数定义,这些语句的目的是初始化模块,它们只在模块名第一次遇到导入import语句时才执行。
import语句可以在程序中的任意位置使用的。
第一次导入后就将模块名加载到内存了,后续的import语句仅是对已经加载到内存中的模块对象增加一次引用。所以当以重复导入的时候,并不会出现问题。
我们可以从sys.modules中找到当前已经加载的模块,sys.modules是一个字典,内部包含模块名与模块对象的映射,该字典决定了导入模块时是否需要重新导入。
首次导入模块my_module时会做三件事:
(1)为源文件(my_module模块)创建新的名称空间,在my_module中定义的函数和方法若是使用到了global时访问的就是这个名称空间。
(2)在新建的名称空间中执行模块中包含的代码,即初始导入import my_module。这个过程就是将模块中的函数名放入模块全局名称空间表。
(3)创建名字my_module来引用该命名空间
二、导入
1.import语句
使用import语句导入模块
>>> import os >>> import math
也可以在一行之内导入多个模块,就像这样
>>> import os,time,random
下面这样导入,代码的可读性不如多行导入语句。
解释器执行完导入语句之后,如果在搜索路径找到了指定的模块,就会加载它。
该过程遵循作用域原则,如果在一个模块的顶层导入,那么它的作用域是全局的,如果在函数中导入,那么它的作用域是局部的。
如果模块是第一次被导入,它将被加载并执行。
2.from-import语句
from语句相当于import,也会创建新的名称空间,但是是将模块的名字直接导入到当前的名称空间中,直接使用名字就可以了
>>> from time import time,localtime >>> time() 1510832882.9023538 >>> localtime() time.struct_time(tm_year=2017, tm_mon=11, tm_mday=16, tm_hour=19, tm_min=48, tm_sec=7, tm_wday=3, tm_yday=320, tm_isdst=0)
还可以导入所有,就像这样
>>> listdir('/') ['boot', 'dev', 'proc', 'run', 'sys', 'etc', 'root', 'var', 'tmp', 'usr', 'bin', 'sbin', 'lib', 'lib64', 'home', 'media', 'mnt', 'opt', 'srv', 'backup', 'Python-3.6.3.tar.xz', 'Python-3.6.3', 'script', 'test', 'zuoye'] >>> getcwd() '/root'
from my_module import *把my_module中所有的不是下划线(_)开头的名字都会导入到当前位置,这不是一个好的选择。
它会“污染”当前名称空间,而且很有可能覆盖当前名称空间中现有的名字。
在两种场合下建议使用这样的方法,一个场合是:目标模块的属性非常多,反复键入模块名很不方便;
还有一个场合就是在交互式解释器下,因为这样可以减少输入次数。
3.扩展的import语句(as)
有时候你导入的模块或是模块属性名称已经在你的程序中使用了,或者你不想使用导入的名字,你想使用自己想要的名字替换掉模块的原始名称,这时你可以使用as。
>>> import random as ra >>> ra.randint(10,100) 30
使用场景:
(1)模块的名称太长
(2)名称空间有冲突
(3)当兼容多个模块的相同操作的时候。
三、编译
1.模块的搜索路径
python解释器在启动时会自动加载一些模块,可以使用sys.modules查看。
>>> import sys >>> sys.modules {'builtins': <module 'builtins' (built-in)>, 'sys': <module 'sys' (built-in)>, '_frozen_importlib': <module '_frozen_importlib' (frozen)>, '_imp': <module '_imp' (built-in)>, '_warnings': <module '_warnings' (built-in)>, '_thread':.....
在第一次导入摸个模块的时候(比如my_module),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有,直接引用。
如果没有,解释器则会查找同名的内建模块,如果还没有找到就从sys.path给出的目录列表中一次寻找my_module.py文件。
模块查找的顺序:内存中已经加载的模块——》内置模块——》扩展模块——》自定义模块。
在初始化后,python程序可以修改sys.path路径,路径放在前面的优先加载
2.编译python文件
为了提高加载模块的速度,提高的是加载速度而绝非运行速度。
python解释器会在__pycache__目录下缓存每个模块编译后的版本格式为:module.version.pyc。
python检查源文件的修改时间与编译时间的版本进行对比,如果过期就需要重新编译。
这是完全自动的过程。并且编译的模块是平台独立的,所以相同的库可以在不同的架构的系统之间共享,即pyc是一种跨平台的字节码。
python解释器会在一下两种情况下不检查缓存。
1.如果是在命令行中被直接导入模块,则按照这种方式,每次导入都会重新编译,并且不会存储编译后的结果(python3.3以前)。
2.如果源文件不存在,那么缓存的结果也不会被使用,如果想在没有源文件的情况下来使用编译后的结果,则编译后的结果必须在源目录之下。
注意事项:
(1)模块名区分大小写,foo.py与FOO.py代表的是两个模块。
(2)你可以使用-O或者--OO转换python命令来减少编译模块的大小。
(3)在速度上从.pyc文件中读取指令来执行不会比.py文件中读指令执行更快,只有在模块被夹在时,.pyc文件才是更快的。
(4)只有使用import语句才会将文件自动编译为.pyc文件,在命令行或标准输入中指定运行脚本则不会生成这类文件,因而我们可以使用compieall模块为一个目录中的所有模块创建.pyc文件。
(5)如果在模块中使用__name__这个变量,则在调用的时候要指定名字。