模块化是工程的一个重要方面,如果一门语言不具备模块化的特性,那么自然是不能成为工程语言的候选者了。
为了支持这个功能,Python 有种方法可以把你定义的内容放到一个文件中,然后在脚本或者交互方式中使用。这种文件称为模块;模块中的定义可以 导入 到其它模块或 主模块 中。模块是包含 Python 定义和声明的文件。文件名就是模块名加上.py 后缀。在模块里面,模块的名字(是一个字符串)可以由全局变量 __name__ 的值得到。
一、模块的基础知识
如何创建一个模块?
例如:我们创建一个叫做module的模块,我们只需要建立一个module.py的文件,就可以了。创建该文件之后,写入该模块的类和方法就可以了。
#module.py
def test():
print "测试"
这样,我们就创建好了一个模块。
模块既作为模块又作为主文件执行?
一些时候,该模块文件作为模块被导入到其他模块中调用,另一些时候,我们需要将其作为主文件来执行。
利用__name__属性即可。
#module.py
def test():
print "测试"
if __name__ == "main":
print "我是主文件执行"
else:
print "我是模块文件"
执行的时候如下所示:
#作为模块被引入
import module
#会输出:
#我是模块文件
#作为主文件执行
python module.py
#会输出
#我是主文件执行
如何引入一个模块?
引入模块的方式主要有两种:
第一种:通过import引入
import module
#执行test函数
module.test()
第二种:通过from xxxx import xxxx
from module import test #仅仅只载入test这一个符号
test()
from module import * #载入所有的符号
test()
二、模块的内置属性
dir()函数可以查看模块的属性,其中带__线的是内置属性。
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']
- __name__
该属性代表当前模块的名字,每个py文件默认的属性,如果当前模块是主程序,值为“__main__”,如果不是主程序,值为模块名。这个属性经常用来区分主程序和作为被导入模块的程序。
- __builtins__
该属性代表内置模块builtins,即所有的内建函数、内置类型、内置异常等;在python执行一个py文件时,会将内置模块赋值给这个属性;如果不是主程序,那么这个属性是一个builtins模块所有方法的字典。
- __doc__
模块的说明文档,py文件初始化时,将文件开始的说明字符串赋值给这个属性。
#使用DocStrings,每个对象都有自己的.__doc__属性
def printMax1(x,y):
'''Prints the maximum of two numbers.the two values must be integers.'''
print
x = int(x) #convert to integers,if possible.
y = int(y)
if x>y:
print x,'is maximum.'
else:
print y,'is maximum.'
printMax1(3,5)
print printMax1.__doc__ #调用printMax1()函数中文档字符串属性
输出结果:
5 is maximum.
Prints the maximum of two numbers.the two values must be integers.
__doc__ :获取文件的注释
#!/usr/bin/env python
# -*- coding:utf8 -*-
"""
这里是文件的注释
"""
print(__doc__) #__doc__ :获取文件的注释
#输出
这里是文件的注释
__doc__属性只能输出多行注释。
- __file__
该属性代表文件的绝对路径,任何一个模块使用这个属性就可获得本模块的绝对路径;但是该属性只在windows环境下可用,在linux环境下不可用。
- __cached__
缓存文件,如果是主程序,那么该属性为None,其他模块的该属性指向该模块的pyc字节文件,这样在py文件不发生修改的情况下可以减少编译的时间,更快地加载上下文环境。
- __annotations__
该属性对于模块文件来说,没有开放给用户使用;但对于函数来说,这个方法用来记录参数的类型和返回值。
def get_sum(x,y):
return x + y
print(get_sum.__annotations__) # {}
def get_sum(x, y: int) -> int:
return x + y
print(get_sum.__annotations__) # {'y': <class 'int'>, 'return': <class 'int'>}
函数的参数和返回值如果在定义的时候指定了类型,那么它们就会以键值对的形式记录到函数的__annotations__属性中,但对于匿名函数来说,这个属性是不存在的。
- __loader__
该属性py3.3之前没有,引用了本模块的加载器对象,即该模块的上下文是由这个加载器加载到内存中的。
- __package__
该属性是一个文件是否是包的标志,在主程序中该属性的值永远是None,不要使用它;当一个包被导入到主程序中,该包的__package__属性为包的名字。
# test
--__init__.py
# main.py
import test
print(test.__package__) # test
- __spec__
该属性记录一个模块的详细信息,是一个ModuleSpec对象。
- __author__
该属性用来定义模块的作者信息,可以是字符串,也可以是列表、字典等数据结构。
# test.py
__author__ = {'auth':'tianyuzhiyou';'bg':''}
- __all__
该属性不是模块默认的,需要手动定义,它的功能有二:
- 记录一个模块有哪些重要的、对外开发的类、方法或变量等,或记录一个包哪些对外开放的模块;
- 限制导入,当使用“from xxx import *”形式导入时,实际导入的只有__all__属性中的同名的对象而不是所有,从而达到隐藏某些私有属性和方法,但对于“from xxx import xxx”指定了具体的名字则all属性被忽略。
#kk.py
__all__=('A','func') #在别的模块中,导入该模块时,只能导入__all__中的变量,方法和类
class A():
def __init__(self,name,age):
self.name=name
self.age=age
class B():
def __init__(self,name,id):
self.name=name
self.id=id
def func():
print 'func() is called!'
def func1():
print 'func1() is called!'
#test_kk.py
from kk import * #kk.py中定义了__all__属性,只能导入__all__中定义的属性,方法和类
a=A('python','24')
print a.name,a.age
func()
#func1() #NameError: name 'func1' is not defined
#b=B('python',123456) #NameError: name 'B' is not defined
魔法方法
- __import__
该魔法方法是import触发,即import os 相当于os = __import__('os'),也可以直接使用,主要用于模块延迟加载,或重载模块。
- 参数:
name:模块的名字;
global:包含全局变量的字典,采用默认值 global()
local:包含局部变量的字典,采用默认值 local()
fromlist:被导入的子模块的名称
level:指定使用绝对导入还是相对导入,0表示只执行绝对导入。
# 设有文件目录
testx
--__init__.py
--name.py
--test
--__init__.py
--name.py
t = __import__('testx',fromlist=['name','test2']) # 导入testx包以及包内的name、test2模块
print(t.name) # t变量代表testx包,如果name不再fromlist中则不可用
t = __import__('testx.test2', fromlist=['name']) # 导入testx包的子包
print(t.name) # 代表testx.test2.name
__import__函数返回的是一个变量,代表一个模块,所有后续方法的调用都要在这个变量的基础上。
总结:
1、对于众多的模块属性,python最常用的是__name__,__doc__,__author__,__all__.
2、python在执行import语句时,调用__import__方法,同时也做了对被导入模块的一些初始化工作,如:将被导入模块的名字赋值给被导入模块__name__,将被导入模块的绝对路径赋值给被导入模块__file__,将builtins模块的globals()赋值给被导入模块__builtins__,将被导入模块的说明赋值给被导入模块的_doc_,将被导入模块的编译字节文件路径赋值给被导入模块__cached__,创建一个ModuleSpec对象赋值给被导入模块的__spec__,如果被导入的是包,将包的名字赋值__package__属性.
三、包---package
什么是包?
包是一种管理 Python 模块命名空间的方式,采用“点分模块名称”。例如,模块名 A.B 表示包A 中一个名为 B 的子模块。就像模块的使用让不同模块的作者不用担心相互间的全局变量名称一样,点分模块的使用让包含多个模块的包(例如 Numpy 和 Python Imaging Library)的作者也不用担心相互之间的模块重名。
怎样创建一个包?
将__init__.py文件和众多的模块放在同一个目录下,就组成了一个包。例如:
假设你想要设计一系列模块(或一个“包”)来统一处理声音文件和声音数据。现存很多种不同的声音文件格式 (通常由它们的扩展名来识别,例如: .wav, .aiff, .au),因此你可能需要创建和维护不断增长的模块集合来支持各种文件格式之间的转换。你可能还想针对音频数据做很多不同的操作(比如混音,添加回声,增加均衡器功能,创建人造立体声效果),所以你还需要编写一组永远写不完的模块来处理这些操作。你的包可能会是这个结构(用分层的文件系统表示):
sound/ Top-level package
__init__.py Initialize the sound package
formats/ Subpackage for file format conversions
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
effects/ Subpackage for sound effects
__init__.py
echo.py
surround.py
reverse.py
...
filters/ Subpackage for filters
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
导入这个包时,Python 搜索 sys.path 中的目录以寻找这个包的子目录。
为了让 Python 将目录当做包,目录下必须包含 __init__.py 文件;这样做是为了防止一个具有常见名字(例如 string)的目录无意中隐藏目录搜索路径中正确的模块。最简单的情况下,__init__.py 可以只是一个空的文件,但它也可以为包执行初始化代码或设置__all__变量(稍后会介绍)。
如何使用一个包?
导入sound包中的所有模块:
import sound
或者
from sound import *
从包中导入单独的模块:
import sound.effects.echo
#使用如下
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
#或者使用如下方法:
from sound.effects import echo
#使用如下
echo.echofilter(input, output, delay=0.7, atten=4)
直接导入所需模块中的某个函数或变量:
from sound.effects.echo import echofilter
使用如下:
echofilter(input, output, delay=0.7, atten=4)
通过*来引入一个包:
如果用户写成 from sound.effects import * 会发生什么?
理想情况下,他应该是希望到文件系统中寻找包里面有哪些子模块,并把它们全部导入进来。这可能需要很长时间,而且导入子模块可能会产生想不到的副作用,这些作用本应该只有当子模块是显式导入时才会发生。
唯一的解决办法是包的作者为包提供显式的索引。import 语句使用以下约定: 如果包中的 __init__.py 代码定义了一个名为__all__的列表,那么在遇到 from package import *语句的时候,应该把这个列表中的所有模块名字导入。当包有新版本包发布时,就需要包的作者更新这个列表了。如果包的作者认为不可以用 import * 方式导入它们的包,也可以决定不支持它。例如,文件sound/effects/__init__.py可以包含下面的代码:
__all__ = ["echo", "surround", ]
这意味着 from sound.effects import * 将导入"echo", "surround", 三个子模块。
但是,我们仍然可以使用from sound.effects import reverse来导入这个子模块。
子模块之间的调用?
以 surround 模块为例,你可以使用:
from . import echo #表示从当前包下载入echo子模块
from .. import formats #表示载入上一级的子包formats
from ..filters import equalizer #filters表示从上一级的包的filter子包中载入equalizer模块
.和..跟路径中意思似乎是一样的。
四、模块化的使用
这部分内容参考了python 3.6.15
模块语句语法:
import_stmt ::= "import" module ["as" identifier] ("," module ["as" identifier])*
| "from" relative_module "import" identifier ["as" identifier]
("," identifier ["as" identifier])*
| "from" relative_module "import" "(" identifier ["as" identifier]
("," identifier ["as" identifier])* [","] ")"
| "from" module "import" "*"
module ::= (identifier ".")* identifier
relative_module ::= "."* module | "."+
import语句:
基本的 import 语句(不带 from 子句)会分两步执行:
- 查找一个模块,如果有必要还会加载并初始化模块。
- 在局部命名空间中为 import 语句发生位置所处的作用域定义一个或多个名称。
第一步实际上就是调用__import__()函数,
第一步,如果成功获取到请求的模块,则可以通过以下三种方式一之在局部命名空间中使用它:
- If the module name is followed by as, then the name following as is bound directly to the imported module.
- 如果没有指定其他名称,且被导入的模块为最高层级模块,则模块的名称将被绑定到局部命名空间作为对所导入模块的引用。
- 如果被导入的模块 不是 最高层级模块,则包含该模块的最高层级包的名称将被绑定到局部命名空间作为对该最高层级包的引用。 所导入的模块必须使用其完整限定名称来访问而不能直接访问。
带有from子句的形式:
from 形式使用的过程略微繁复一些:
- 查找 from 子句中指定的模块,如有必要还会加载并初始化模块;
- 对于 import 子句中指定的每个标识符:
- 检查被导入模块是否有该名称的属性
- 如果没有,尝试导入具有该名称的子模块到被导入的模块中,然后再次检查被导入模块是否有该属性
- 如果未找到该属性,则引发 ImportError。
- otherwise, a reference to that value is stored in the local namespace, using the name in the as clause if it is present, otherwise using the attribute name
常用语句形式:
import foo #载入foo模块,并绑定到本地的foo名字。
import foo.bar.baz #载入foo.bar.baz,并绑定到本地的foo名字。
import foo.bar.baz as fbb #载入foo.bar.baz,并绑定到本地的fbb名字。
from foo.bar import baz # 载入foo.bar.baz,并绑定到本地的baz名字。
from foo import attr # 载入foo,并将foo.attr绑定到本地的attr名字。
#对于子模块和属性,python的导入系统的处理有所不同。
from foo.bar import baz as fbb #载入foo.bar.baz,并将foo.bar.baz绑定到本地的fbb名字。
from foo.bar import * #载入foo.bar,并将foo.bar中的所有可导入名字绑定到本地对应的名字上,这里需要注意,foo.bar必须是一个模块,而不能是一个模块的属性(例如类或者函数),否则会引发SyntaxError。
from ..foo.bar import baz #从上一层级的foo模块载入foo.bar.baz并绑定到本地的baz名字。
#这里提到一个模块的层级问题,这个层级就相当于相对路径,..表示上一级层级,.表示当前层级。
#python中按照路径层次划分模块层级,每一层目录就相当于一个模块层级,每一个层级包含众多模块,
#按照层级导入模块,实际上就相当于按照文件系统路径导入模块。
注意点:
相对层级导入如果是在主模块中使用,会报错。因此,相对层级导入只用在模块中,而不是主模块中。
相对模块导入的原理:
1、相对模块导入是相对于调用from .. import xxx的模块层级,其根据调用者模块的__name__属性来确定层级,以__name__属性最后一个.(点)所在的位置进行目标模块的层级计算。
2、主模块中的__name__始终是__main__,所以在主模块中的计算层级起始点。假设在主模块中from .test import testfunc,那么实际上就相当于from __main__.test import testfunc,那么__main__.test显然是不存在的,于是乎,就会报错。所以在主模块中,不能使用相对导入,只能使用绝对导入。
不要使用from xxx import *,因为这条语句的语义不够明确,从而会影响程序的可维护性。
# import语句的导入模块的例子
1)、import foo
首先调用__import__()查找foo模块。
具体查找过程如下:
先从缓存sys.moudles中查找,如果存在,直接返回模块对象。如果不存在,就去调用对应的finder,搜寻foo模块。
如果未找到foo模块,则报错NotFoundModule。
如果找到foo模块,就会创建foo模块对象,然后在sys.modules中创建缓存,接着就执行该模块代码。
假设上一步找到了模块对象foo,那么由于foo是一个顶层模块对象,所以就在本地名字空间中,绑定foo名字到foo模块对象。
对于import foo as foo_ref
这个语句第一步同上面的imort foo一致。
在第二步绑定名字的时候,会将foo_ref绑定到foo模块对象上。
2)、import foo.bar.baz
首先调用__import__()查找foo.bar.baz模块。
具体查找过程如下:
先从缓存sys.modules中查找,如果存在,直接返回模块对象。如果不存在,就去调用对应的finder,搜寻foo.bar.baz模块。
首先搜寻foo这个最顶层模块,然后载入foo模块,创建foo模块对象,缓存到sys.modules中,执行foo模块代码。再然后搜寻foo.bar模块,然后载入foo.bar模块,创建foo.bar模块对象,缓存到sys.modules中,执行foo.bar模块代码。最后搜寻foo.bar.baz模块,然后载入foo.bar.baz模块,创建foo.bar.baz模块对象,缓存到sys.modules中,最后执行foo.bar.baz模块代码。
假设上一步找到了foo.bar.baz模块, 由于foo.bar.baz的顶层模块是foo,所以就会在本地的名字空间中,绑定foo名字到foo模块对象。
对于import foo.bar.baz as fbb
这个语句第一步与import foo.bar.baz一致。
第二部绑定名字的时候,会将foo.bar.baz这个模块对象绑定到本地名字空间的fbb名字上。
3)、from foo.bar import baz
首先调用__import__()查找foo.bar模块。
具体查找过程如下:
先从缓存sys.modules中查找,如果存在,直接返回模块对象。如果不存在,就去调用对应的finder,搜索foo.bar模块。
首先搜索foo这个最顶层模块,然后载入foo模块,创建foo模块对象,缓存到sys.modules中,执行foo模块代码。再然后搜寻foo.bar模块,然后载入foo.bar模块,创建foo.bar模块对象,缓存到sys.modules中,执行foo模块代码。
再然后,检查foo.bar模块中是否有baz属性,显然是没有的。
于是,就尝试载入foo.bar.baz模块,创建foo.bar.baz模块对象,缓存到sys.modules中,执行foo.bar.baz模块代码,然后在foo.bar的名字空间中绑定baz到foo.bar.baz模块对象,接着再检查foo.bar模块中是否存在baz属性,此时显然是存在的(如果不存在,此时就会报错Import Error)。
此时已经查找到了baz,那么接下来就是在本地的名字空间中绑定baz名字到foo.bar.baz模块对象了。
对于from foo.bar import baz as fbb
第一步与from foo.bar import baz是一致的。
但是查找到了baz之后,在本地名字空间中,绑定fbb名字到foo.bar.baz模块对象。
4)、from foo import attr
假设这里的attr是一个属性,而不是一个模块。
首先调用__import__()查找foo模块。
具体过程如下:
此从缓存sys.modules中查找,如果存在,直接返回模块对象。如果不存在,就去调用对应的finder,搜索foo模块。
如果未找到foo模块,则报错NotFoundModule。
如果找到foo模块,就会创建foo模块对象,然后在sys.modules中创建缓存,接着就执行该模块代码。
接着,在foo模块中寻找attr属性,如果找到就返回。
如果没有找到,就尝试载入foo.attr模块,创建foo.attr模块独享,缓存到sys.modules中,执行foo.attr模块代码,然后在foo名字空间板顶attr到foo.attr模块对象,接着再检查foo模块中是否存在attr属性,此时显然是存在的(如果不存在,此时就会报错Import Error)。
假设此时找到了attr,那么接下来就会本地名字空间中绑定attr到foo.attr属性。
from foo import attr as fa_ref
该语句的第一步与from foo import attr一致。
假设找到了attr,那么接下来会在本地名字中间中绑定fa_ref到foo.attr属性。