原载于https://mp.weixin.qq.com/s/5-VhYqQapJn2lfoe5nuuyQ
模块是一个文件,它的文件名一般是"模块名.py"。
包是一个目录,如果一个目录下面有一个"__init__.py"的文件,那么python就会认为这个目录是一个包。
一个包目录下面可以放很多包目录,也可以放很多模块文件。
这样模块文件就可以按照架构和逻辑放在不同的包目录下面,以便管理。
函数把完成某个功能所需的代码组织到一个代码块;模块可以把联系紧密的代码块组织到一个文件;包可以把不同的模块组织到一个或者多个目录当中。
从物理的角度来看,一个软件工程有目录-文件-代码块;从逻辑的角度来看,一个软件工程可以有包-模块- 函数/类。
12.1 模块的定义和使用
把一个文件命名为“module1.py”,就得到了一个名叫 module1 的模块。之前把代码放到一个文件里面,就有了各种模块。
模块里面可以定义函数或者类。比如说如下代码:
def inc(a):
return a+1
把这个函数放在“module1.py”里面,“module1”这个模块就有了一个inc(a)函数。module1的命名空间就有了inc。
其他模块可以通过导入module1来引用“module1.py”文件的内容。例如,新建一个模块文件“testmodule.py”,在里面输入代码:
import module1
print(module1.inc(4))
这段代码的输出为
5
上面的代码通过import语句导入module1模块,然后通过"."来访问module1中的inc函数。如果module1里面有全局变量,全局类,同样也可以通过"."来访问。
模块文件若有全局代码,也就是没有被任何函数或者类包裹的代码,那么这些代码会在import的时候运行一次。以后再次调用模块的函数或者类都不会再运行这些全局代码。
在module1中加入一个incn函数,以及全局代码。演示代码如下:
n=3
print(n)
def incn(a):
return a+n
n=3、print(n)是模块中的全局代码段。此时在修改testmodule.py中的代码如下:
import module1
print("import end")
print(module1.incn(4))
print(module1.incn(10))
运行的结果为
3
import end
7
13
可以看到n=3、print(n)在import的时候被执行了。而后来两次调用incn函数并没有再运行这些代码了。
下面的代码是一个修改模块全局变量的例子:
import module1
module1.n=5
print(module1.incn(4))
上述代码修改了module1的步长,这个时候incn的步长就为5。这段代码的运行结果是
9
import语句可以跟随一个as为模块创建别名。如:
import module1 as incmodule
此时可以用incmodule来替代module1,如
print(incmodule.incn(10))
还有一种import语句是from ... import...,可以实现从一个模块导入函数。如
from module1 import incn
此时调用函数就不需要通过module1了,通过incn(4)就可以调用函数。
这里有一点需要注意的是,这样import进来的变量值被修改了之后并不会影响被导入模块的那个变量的值。
修改module1.py的代码为
n=3
def printn():
print(n)
同时修改testmodule.py的代码为
from module1 import n,printn
n=6
print(n)
printn()
运行一下testmodule.py,结果为:
6
3
可以看到此时n被导入到testmodule中,但是修改testmodule中的n,并不会影响module1中的n。这里在使用的时候要注意。最好也不要直接import变量或者函数到本地空间,import一个模块比较安全。
如果想要把module1里面的所有函数都导入当前命名空间,可以用from module1 import *。
这种方法同样不提倡,因为module1里面的函数可能和当前命名空间中的函数名称一致,产生冲突。尤其是需要导入很多模块的时候,不同模块的函数名冲突的概率会很大。
模块可以被程序入口文件导入,也可以被其它模块导入。在程序运行过程中,一个模块只会被导入一次,如果一个模块已经被导入了,那么就不会再进行导入。
在导入一个模块的时候,模块文件的搜索路径是根据sys.path中的路径集合依次进行搜索的。所以要保证模块文件存在于路径集合中的某条路径下面。
使用dir()函数可以获取当前命名空间中的所有模块,而使用dir(modulename)可以获取一个模块命名空间中的所有变量名、函数名、类名等。这个函数在查询命名空间都有哪些变量非常有好处。
模块在被import的时候会生成一个.pyc文件,以后每次import都会优先加载这个.pyc文件,这会加快模块文件的装载速度。而在运行期间,模块被加载了之后,删除或者修改了模块的.py文件不影响模块的运行。这个特性使得模块文件可以在运行时进行升级而不影响当前运行。
12.2 包的定义和使用
新建一个目录package1,并且在这个目录下面添加一个名为__init__.py的空文件,此时这个目录就被python编译器当作是一个包。然后把刚才的module1.py文件放到这个目录下面。一个包目录下面包含一个模块文件的场景就建好了。
通过import package1.module1语句可以在别的模块文件中导入这个模块文件。演示代码如下:
import package1.module1
print(package1.module1.incn(4))
这里要注意的是,import语句的最后一项必须是模块或者包,不能是模块文件里的函数等。
也可以用from ... import ...来导入。用这种方法可以import一个包,一个模块或者一个函数。
比如说导入一个函数
from package1.module1 import incn
print(incn(4))
或者导入一个模块
from package1 import module1
print(module1.incn(4))
要注意的是,不能使用from package1 import module1.incn这样的语句。
使用import或者from ...import ...导入一个包的时候,要注意,在导入包之后,python编译器不会自动去搜寻目录下面的模块。因此这个包的命名空间是空的。此时需要包的__init__.py文件里面有import语句导入相关模块或者函数,包的命名空间中才会有可调用的模块或者函数。
同样使用from package import *的时候,python编译器也不会把package目录下面所有的模块文件自动导入。它只会在package目录下面的寻找__init__.py文件,并且把里面定义的__all__列表中的模块进行导入。
在__init__.py文件中加入一行
__all__=['module1']
此时使用from package1 import * 才可以导入module1
from package1 import *
print(module1.incn(4))
在使用from ... import ...语句的时候,还支持相对导入。
例如 from . import 会在文件所在的包中查找,而from .. import 会在父级的包中查找。相对查找依据的是当前的模块的__name__变量。如果__name__是package1.module1,那么from . import会在package1中查找,而from .. import 会在package1的父级目录中查找。
如果是顶层运行的程序,它的__name__变量一直是'__main__',就无法根据它来进行相对模块查询。因此一个模块被当作程序入口运行的时候,相对导入会出错。
12.3 小结
模块和包的内容比较零散,主要是如何构建一个模块和包,以及如何使用导入语句的内容。使用导入语句导入一个包,导入一个变量或者函数等等,虽然python支持,但是从上面的知识点看,需要做很多配合工作才能够顺利使用。而导入一个模块问题就少了很多。所以一般情况下,无论是import还是from import,确保自己导入的是一个模块,那么使用起来会顺利安全得多。