Python中的模块与包可以说是最基础的概念之一了。
每一个Python开发者,不论是新手还是老鸟,在开发过程中都会和模块与包打交道。
而且现在的各种IDE工具都特别的发达,各种提示功能一应俱全。很多时候,IDE都可以帮我们自动导入包与模块。
但是便利的同时,也带来了问题,因为过于依赖IDE,可能有些写了不少代码的同学,对模块与包的一些细节还不是很清楚。
本文就带大家搞清楚,模块、包以及库的概念和一些使用细节。
01
模块与包的概念
模块、包还有库这三者之间是紧密关联的,就概念上来讲,三者存在着递进关系:
模块(Module):一个.py文件就可以看做是一个模块,其中包含了变量、函数、对象、类等。
包(Package):本质上就是文件夹,里面包含了一个或者多个模块,也可以包含其它子包。在Python3.3版本以前包中必须要有__init__.py这个文件,不然就是普通文件夹,不能称之为包。
从Python3.3 以后,__init__.py如果是空的,可以省掉了,但是还是不建议这样做。
库(Library):可看成为完成特定功能的模块与包的集合。
02
模块的基本使用
我们用案例来说明Python中模块的使用。
首先创建一个文件夹module_test,在文件夹内创建文件one.py。在one.py中写一些简单的Python代码:
num = 10 def fn(): print("num是一个变量%s" % num)
one.py就可以认定是一个模块了,其中有一个变量num和一个函数fn。
接下来我们就来使用one.py中的变量与函数。
在文件夹module_test路径下,打开python的命令行,输入命令导入one模块:
>>> import one
然后就可以使用one中的变量和函数了,但是请注意,使用的方法:
>>> numTraceback (most recent call last): File "", line 1, in NameError: name 'num' is not defined>>> one.num10>>> one.fn()num是一个变量10
上述例子我们可看到,使用模块中的变量与函数(以下统称模块属性),需要用 模块名.变量名 或者 模块名.函数名的方式。
如果你嫌麻烦,不想在每次使用变量和函数时还要带上模块名,可以这样做:
>>> from one import num, fn>>> num10>>> fn()num是一个变量10
使用from – import语法,可以指定从模块中导入你想要的模块属性。
有时候,模块名字会老长老长了,我们可以在导入的时候,给模块指定一个别名,这个用import-as语法就可以轻松实现了,但是请注意,给模块指定的别名可不要和其他命名冲突了。
>>> import one as O>>> O.num10>>> O.fn()num是一个变量10
同样的,从模块中导入指定属性时,也可以指定别名
>>> from one import num as N>>> N10
还有一个导入模块属性的方式,就是使用 * 号作为通配符,导入模块中的所有属性。
>>> from one import *>>> num10>>> fn()num是一个变量10
但是这种用法,建议慎重使用,因为可能你会导入一些意想不到的属性,和已有的命名冲突。
03
包的基本使用
接下来我们来看看包。
在module_test文件夹下,我们再创建一个package_test文件夹,里面创建两个文件__init__.py和two.py。
文件结构如下:
在two.py中写一些简单的代码:
num = 20 class MyClass: def __init__(self, name): self.name = name def say_hello(self): print('Hello!我是:%s' % self.name)
然后我们在还是在module_test文件夹路径下,使用python命令行,尝试导入package_test包中的内容。
>>> import package_test as pt>>> pt.twoTraceback (most recent call last): File "", line 1, in <module>AttributeError: module 'package_test' has no attribute 'two'
当我们直接导入了package_test,并尝试访问其中的two模块时,直接报属性错误了。所以导入包之后想直接根据包来获取其中的模块,貌似是不行的。
接下来我们换一种方式尝试下:
>>> from package_test import two>>> two.num20>>> j = two.MyClass('jack')>>> j.say_hello()Hello!我是:jack
还有一种导入包中模块的方式:
>>> import package_test.two>>> two.num20
小结一下:
当你仅仅导入了包时,你并不能直接使用包下面的模块以及模块中的属性,而是涉及到具体的层级关系,一层层的导入与访问。
当要导入包中的模块时,可以用import 包名.模块名 也可以用 from 包名 import 模块名,但如果要导入模块中的某个属性时,就只能用 from 包名.模块名 import 属性名 的形式。
例如,在本例中就不能直接写:import package_test.two.num。
04
特殊的模块__init__.py
当我们使用import package_test as pt时到底发生了什么?
我们可以做一个小测试:
>>> import package_test as pt>>> pt'package_test'
从打印的结果中我们可以看到,pt其实指向的就是package_test中的__init__.py模块!
利用Python这样的一个特性,我们可以在__init__.py中根据实际需求,写一些语句。
例如,在__init__.py中我们写如下代码:
print('hello! 你试着在import package_test') from .two import * a = 30
然后重启Python命令行,并执行如下命令:
>>> import package_test as pthello! 你试着在import package_test>>> pt.num20>>> pt.a30>>> rose = pt.MyClass('rose')>>> rose.say_hello()Hello!我是:rose
从打印结果中我们可以看得很清楚,当我们import package_test as pt之后,__init__.py中的print语句被执行了。
同时由于在__init__.py中,from .two import * 语句将
two.py模块中的所有属性都导入了,所以我们可以直接根据pt包名,来访问two.py模块中的属性。
而__init__.py中自带的属性a,也可以根据导入的包名,直接访问到。
用好__init__.py模块,可以让我们的项目结构更加的清晰,使用起来更方便。
05
import的一些细节
本文的最后,我们探讨下import的一些细节:
1. import模块时,会执行模块文件中的语句
还是以我们这个项目结构为例:
- 当import package_test 时,会执行__init__.py文件
- 当 from package_test import two时, 会执行__init__.py 和 two.py文件
- 当 from package_test.two import num时, 也会执行__init__.py 和 two.py文件
这个大家可以自己在模块中写一些print语句打印信息来验证一下。
2. python的导入机制,会避免重复import
例如,重启一下命令行,先后两次导入package_test包,
>>>import package_testhello! 你试着在import package_test>>>import package_test
你会发现,只打印了一次 ' hello! 你试着在import package_test' ,
这是因为在执行导入时,Python会首先检查待导入的模块是否在当前已有模块之中,如果有则跳过import,来避免重复的导入。
3. 可通过sys模块中的modules属性,查看当前的模块导入情况
>>> import sys>>> sys.modules
sys.modules以字典的形式记录导入模块的信息,键是模块名,值是模块文件所在路径。
我们可以通过这个属性,来判断模块是否已经被导入
END