2015/9/15 Python基础(12):模块和包
模块是用来组织 Python 代码的方法,而包则是用来组织模块的。
当代码量很大时,我们一般会把代码分成几个有组织的代码段,然后每个代码段之间有一定的联系。代码单之间是共享的,所以Python允许调入一个模块,允许使用其他模块的属性利用之前的工作成果,实现代码重用。那些自我包含并且有组织的代码片段就是模块(module),将其他模块中属性附加到你的模块中的操作较导入(import)
模块是逻辑上的说法,而它们在物理层是一个个独立的文件,模块的文件名就是模块的名字加拓展名.py。与其他可以导入类的语言不同,Python中你导入的是模块或模块属性。
模块的名称空间
模块名称是他们属性名称中的一个重要部分,给定一个模块名后,只可能有一个模块被导入到Python解释器中,所以不同模块间不能出现名称交叉现象。如果在模块mymodule里创建了一个atoi()函数,那么它的名字应该是 mymodule.atoi()。所以即使属性之间有名称冲突,但他们的完整授权名称通过句点属性标识指定了各自的名称空间,防止名称冲突产生。
模块的导入需要一个路径搜索的过程。即在文件的“预定义区域”中查找 mymodule.py 文件(如果导入的是mymodule的话)。这些预定义区域不过是你的 Python 搜索路径的集合。路径搜索和搜索路径概念不同,前者指查找某个文件,后者是去查找一组目录。
默认的搜索路径是在编译或是安装时指定的,它可以在一个或两个地方修改。
一个是启动Python的shell命令行的PYTHONPATH环境变量。该变量的内容是一组用冒号分隔的目录路径。如果你想让解释器使用这个变量,请确保在启动解释器或执行Python脚本前设置或修改了该变量。
解释器启动之后,也可以访问这个搜索路径,它会被保存在 sys 模块的 sys.path 变量里。
不过它已经不是冒号分隔的字符串,而是包含每个独立路径的列表。
这只是个列表,所以可以随时随地对它修改,如果需要知道你要导入的模块是什么,而它的路径不再搜索路径里,那么只需调用append()方法即可。
修改完成后,你就可以加载自己的模块了。只要列表中的某个目录包含这个而文件,它就会被正确导入。
在同一个模块的多个拷贝都出现在路径中时,会使用沿用搜索路径找到的第一个模块。
名称空间
名称空间是名称(标识符)到对象的映射。向名称空间添加名称的操作过程涉及到绑定标识符到指定对象的操作(以及给该对象的引用计数加1)。
Python在执行期间由两个或三个活动的名称空间,分别是局部名称空间,全局名称空间和内建名称空间。
Python解释器会首先加载内建名称空间,它由 __builtins__ 模块的名字构成。随后加载执行模块的全局名称空间,他会在模块开始执行后变为活动名称空间。如果在执行期间调用了一个函数,那么将创建出第三个名称空间,局部名称空间。
关于__builtins__ 和 __builtin__
现在尽可能只使用 __builtins__ ,不使用__builtin__ ,现在所有__builtin__的内容都在__builtins__里有,而__builtin__已经不太支持。
名称空间与变量作用域的关系
名称空间是纯粹意义上的名字和对象间的映射关系,而作用域还指出了从用户代码的那些物理位置可以访问到这些名字。
名称查找、确定作用域和覆盖
名称查询将确定作用域的规则覆盖到名称空间。访问一个属性时,解释器必须在三个名称空间中的一个里找到它,先从局部名称空间开始,如果没有找到,解释器会继续查找全局名称空间,如果失败了,将在内建名称空间里查找。如果都没找到,返回一个NameError。
这种先后查找的方式是遮蔽了之后的名称空间。也就是局部变量覆盖了全局变量:
def foo(): print "calling foo()..." bar = 200 print "in foo(), bar is",bar bar = 100 print "in __main__, bar is", bar foo() print "in __main__, bar is", bar
结果是:
>>> in __main__, bar is 100 calling foo()... in foo(), bar is 200 in __main__, bar is 100
如果如下代码:
def foo(): print "calling foo()..." print "before we assign bar,bar is",bar bar = 200 print "in foo(), bar is",bar bar = 100 print "in __main__, bar is", bar foo() print "in __main__, bar is", bar
则返回:
>>> in __main__, bar is 100 calling foo()... before we assign bar,bar is Traceback (most recent call last): File "F:/桌面/PythonProgram/namespace.py", line 8, in <module> foo() File "F:/桌面/PythonProgram/namespace.py", line 3, in foo print "before we assign bar,bar is",bar UnboundLocalError: local variable 'bar' referenced before assignment
如果删除其中一行:
def foo(): print "calling foo()..." print "before we assign bar,bar is",bar print "in foo(), bar is",bar bar = 100 print "in __main__, bar is", bar foo() print "in __main__, bar is", bar
则结果为:
>>> in __main__, bar is 100 calling foo()... before we assign bar,bar is 100 in foo(), bar is 100 in __main__, bar is 100
上面的尝试里,反映出了局部名称空间覆盖了全局名称空间的过程。当我在函数中给bar赋值后,bar变成局部变量,也就是出现在局部名称空间里,此时在给bar赋值前调用它就会报错。
导入模块
导入语句有两种:
import module1 import module2 ...或 import module1,module2,...
这两种没有太大的不同,只有可读性的差别。
核心风格:一般我们推荐所有的模块在开头导入,最好按这样的顺序
Python标准库模块
Python第三方模块
应用程序自定义模块
然后用一个空行分割这三个模块的导入语句
from-import语句
这是导入模块的指定属性。也就是把指定名称导入到当前作用域,语法如下:
from module import name1[,name2,...]
导入内容过长变成多行导入时,用一个\可以换行。
拓展import语句(as)
当你想使用一个模块或者模块属性,但想改一个名字时,可以使用自己要的名字替换模块的原始名字,语法如下:
import longmodulename as yourname
from-import语句可以把名字导入到当前空间去,不需要在使用时加句点属性标识符,如果要把所有的名称都导入到当前名称空间可以用:
from module import *
当然,从实践中来看,这不是很好的编程风格,它污染了当前名称空间,很可能覆盖当前名称空间现有的名字。
模块内建函数
__import__()
这是import的实际函数,我们使用它来完成导入工作,而提供这个函数是为了让有特殊需求的用户来覆盖它,实现自定义导入。
__import__()语法是:
__import__(module_name[,globals[, locals[, fromlist[]]]])
module_name 是导入模块的名字,globals 是包含当前全局符号表的名字的字典, locals 是包含局部符号表的名字的字典, fromlist是一个使用from-import 语句所导入符号的列表。
globals() 和 locals()
这两个内建函数分别返回调用者全局和局部名称空间的字典。
在全局名称空间下,globals() 和 locals() 返回相同的字典,因为局部名称空间就是全局空间。
reload()
这个内建函数可以重新导入一个已经导入的模块。语法是:
reload(module)
模块中的代码在导入时被执行,但只执行一次。以后执行 import 语句不会再次执行这些代码,只是绑定模块名称。而 reload()可以多次执行。
包
包是一个有层次的文件目录结构,它定义了一个由模块和子包组成的 Python 应用程序执行环境。
可以解决如下问题:
为平坦的名称空间加入有层次的组织结构
允许程序员把有联系的模块组合到一起
允许分发者使用目录结构而不是一大堆混乱的文件
帮助解决有冲突的模块名称
包也使用句点属性标识来访问他们的元素。
假设有这样的包
Phone/
__init__.py
common_util.py
Voicedta/
__init__.py
Pots.py
Isdn.py
Fax/
__init__.py
G3.py
Mobile/
__init__.py
Analog.py
digital.py
Pager/
__init__.py
Numeric.py
可以这样导入包:
import Phone.Mobile.Analog Phone.Mobile.Analog.dial() 或 from Phone import Mobile Mobile.Analog.dial() 或 from Phone.Mobile import Analog Analog.dial() 或 from Phone.Mobile.Analog import dial dial()
上面的目录结构中有很多__init__.py文件。这是初始化模块,导入子包时from-import语句要用到它。如果没有用到,他们可以是空文件。
绝对导入
因为包的使用越来越广发,很多情况下导入子包会和标准库模块发生冲突,包模块会把名字相同的标准库模块隐藏掉。为此,所有的导入现在都被认为是绝对的,也就是说这些名字必须通过Python路径来访问。
当然,也留下了一定的相对导入操作,第一部分是句点,表示相对导入,然后用附加句点表示在哪个位置级别。
如上的目录结构,如果我们在Digital.py中,有以下的导入方法:
from Phone.Mobile.Analog import dial #绝对导入 from .Analog import dial from ..common_util import setup form ..Fax import G3.dial