模块与包

除了交互式编写python代码在Python解释器执行之外,还可以将代码写入后缀为py的文件。然后通过python命令编译执行。py文件就是模块。模块的名字就是该文件的名字(不包含后缀)。

Python 中(至少)有三类模块:
1.使用 Python 编写的模块(.py);
2.使用 C 编写的动态加载模块(.dll,.pyd,.so,.sl 等);
3.使用 C 编写并链接到解释器的模块,要获取此列表,输入:

1
2
import sys
print(sys.builtin_module_names)

输出结果

1
('_ast', '_codecs', '_collections', '_functools', '_imp', '_io', '_locale', '_operator', '_signal', '_sre', '_stat', '_string', '_symtable', '_thread', '_tracemalloc', '_warnings', '_weakref', 'atexit', 'builtins', 'errno', 'faulthandler', 'gc', 'itertools', 'marshal', 'posix', 'pwd', 'sys', 'time', 'xxsubtype', 'zipimport')

除了模块,还有包的概念。可以将包视为文件系统上的目录,将模块视为目录中的文件。与文件系统目录一样,包是按层次结构组织的,包本身可能包含子包以及模块。

重要的是要记住所有包都是模块,但并非所有模块都是包。换句话说,包只是一种特殊的模块。具体来说,任何包含path属性的模块都被视为包。

所有模块都有一个名称。子包名称与其父包名称用句点分开,类似于Python的标准属性访问语法。例如A.B.C,A是B的父包,B是C的父包。

import语句不是调用机制唯一的方法。importlib.import_module()和内置__import __()等函数也可用于调用导入机制。

import语句,from…import语句

通过导入,一个模块中的Python代码可以访问另一个模块中的代码。 import语句是调用导入机制的最常用方法。import语句进行两个操作;它搜索命名模块,然后将搜索结果绑定到本地。 import语句的搜索实际上是调用__import __()函数。 __import __()的返回值用于执行import语句的名称绑定操作。

首次导入模块时,Python会搜索模块,如果找到,它会创建一个模块对象,并对其进行初始化。如果找不到指定的模块,则引发ModuleNotFoundError。

用import或者from…import来导入相应的模块。有以下几种形式。 将整个包(package)导入,格式为:import <package>
将整个模块(module)导入,格式为:import <module>
从某个包中导入模块/子包/对象,格式为:from <package> import module or subpackage or object>
从某个模块中导入对象,格式为:from <module> import <object>
从某个模块中导入多个对象,格式为:from <module> import <object1>,<object2>,<object3>
将某个模块中的全部对象导入,格式为: from <module> import *

from <module> import * 可以导入像sayhi这样公用的名字,但不会导入version,因为它是以双下划线开始的特殊属性。不建议使用from import * ,有不可预知的风险

重命名模块/子包/对象

格式为:
from <package> import <module or subpackage or object> as myname

例如: import sys as systemModule

常规包(regular packages)

Python定义了两种类型的包,常规包(regular packages)和命名空间包(namespace packages)。 常规包是Python 3.2及更早版本中存在的传统包。 常规包通常为包含init.py文件的目录。 导入常规包时,将隐式执行此init.py文件,并且它定义的对象将绑定到包命名空间中。__init__.py文件可以包含Python代码,并且Python将在导入模块时向模块添加一些其他属性。

例如,以下文件系统布局定义了一个包含三个子包的顶级父包parent:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
parent
    │─__init__.py
    
    ├─one
    │───__init__.py
    
    ├─three
    │───__init__.py
    
    └─two
    │───__init__.py

导入parent.one将隐式执行parent/__ init__.py和parent/one/ __init__.py。 随后导入的parent.two或parent.three将分别执行parent/two/__init__.py和parent/three/__init__.py。

import module 时,模块中所有的代码将被执行(类对象,函数对象将被创建,不会被调用),import package时,init.py文件中的代码也将被执行。

命名空间包(namespace packages)

不包含init.py文件的目录,是命名空间包(namespace packages)。这一特性是在 Python 3.3 被引入的。

比如下面的目录结构: .
├── configs
│ └── config.py
├── depends
├── tools
└──init.py
│ └── tool.py
如果要在tools/tool.py导入configs/config.py中的内容,在Python 3(3.3及之后) 中就可以直接使用from configs.config import * 来导入,Python 3.3以前就不可以,如果一定要这样做,就需要给configs文件夹下加入init.py才可以。

但是所谓的命名空间包提出的本意却不是说为了导入没有init.py 的文件夹的 Python 模块,而是利用命名空间包这个技术来导入目录分散的代码。

假设你有Python代码的两个不同的目录如下:

1
2
3
4
5
6
7
foo-package/  
    spam/  
        blah.py  

bar-package/  
    spam/  
        grok.py  

在这2个目录里,都有着共同的命名空间spam。在任何一个目录里都没有init.py 文件。这时候再导入这两个包的时候:

1
2
3
4
import sys
sys.path.extend(['foo-package', 'bar-package'])
import spam.blah
import spam.grok

两个不同的包目录被合并到一起,你可以导入spam.blah和spam.grok,并且它们能够工作。 在这里工作的机制被称为命名空间包的一个特征。从本质上讲,命名空间包是一种特殊的封装设计,为合并不同的目录的代码到一个共同的命名空间。

命名空间包的关键是确保顶级目录中没有init.py文件来作为共同的命名空间。缺失init.py文件使得在导入包的时候会发生有趣的事情:这并没有产生错误,解释器创建了一个由所有包含匹配包名的目录组成的列表。特殊的包命名空间模块被创建,只读的目录列表副本被存储在其path 变量中。

1
2
3
>>> import spam
>>> spam.__path__
_NamespacePath(['foo-package/spam', 'bar-package/spam'])

上面 configs 的例子其path变量如下:

1
2
3
>>> import configs
>>> configs.__path__
_NamespacePath(['/project/configs'])

在定位包的子组件时,目录path将被用到(例如, 当导入 spam.grok 或者 spam.blah 的时候或者 configs.config). 一个包是否被作为一个包命名空间的主要方法是检查其file 属性。如果没有,那包是个命名空间。这也可以由其字符表现形式中的 namespace 这个词体现出来。

1
2
3
4
5
6
7
8
9
>>> configs.__file__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'configs' has no attribute '__file__'
>>> import other
>>> other.__file__
'/other/__init__.py'
>>> other
<module 'other' from '/other/__init__.py'>

模块的 file属性

导入模块时,可以通过模块的file属性查看模块所在磁盘的路径位置

1
2
3
>>> import requests
>>> requests.__file__
'D:\\Programs\\Anaconda3\\envs\\py_test\\lib\\site-packages\\requests\\__init__.py'

包引用顺序

解释器会按照sys.path列表的顺序来查找被引入的包或模块名字。

1
2
3
4
5
6
7
8
9
>>> import sys
>>> import pprint
>>> pprint.pprint(sys.path)
['',
 'C:\\Python\\Python35-32\\python35.zip',
 'C:\\Python\\Python35-32\\DLLs',
 'C:\\Python\\Python35-32\\lib',
 'C:\\Python\\Python35-32',
 'C:\\Python\\Python35-32\\lib\\site-packages']

优先加载当前工作目录下的模块,如果你的项目中使用了与内建模块中同名的包或模块名,就会遇到没有xx属性之类的报错提示。

你可以操作sys.path,使得其他路径文件加入到Path中,使之能被解释器发现。

1
2
3
4
import sys

# 当前目录没有hi模块,报错找不到模块
import hi

由于找不到hi模块,会报错如下,

1
2
Traceback (most recent call last):
ImportError: No module named hi

将hi模块加入到path中,

1
2
3
4
5
6
7
8
import sys

# hi模块所在位置: /data/hi.py
# 将hi所在模块加入sys.path
sys.path.append("/data")

# 可以正常工作了
import hi

另外一种加载模块的方法:如果你的模块不在sys.path,还可以使用 imp 模块中的方法 imp.load_source

1
2
3
4
5
6
import imp
imp.load_source("hi", "C://data/hi.py")
import hi
# 可以自己指定模块的名字,相当与 import hi as h2
imp.load_source("h2", "C://data/hi.py")
import h2

if __name__ == ‘__main__‘: 的解析

通过上面的讲解,我们知道Python文件既可以拿来执行,也可以用来作为模块使用import导入。当Python解析器读取一个源文件时它会执行所有的代码。在执行代码前会定义一些特殊的变量。如果解析器运行的模块(源文件)作为主程序,它将会把name变量设置成”__main__”。如果只是引入其他的模块,name变量将会设置成模块的名字,通常为模块文件名,不带路径或者文件扩展名。

在代码下方添加if __name__ == ‘__main__‘: 的主要原因是有时你需要你写的模块既可以直接的执行,还可以被当做模块导入到其他模块中去.通过检查是不是主函数,可以让你的代码只在它作为主程序运行时执行,而当其他人调用你的模块中的函数的时候不必执行。简单来说就是,方便我们代码复用,也可以测试模块。

举例说明:创建一个using_name.py文件,内容如下。
print(__name__)

通过python命令直接运行,python using_name.py
运行结果是main

在Python的交互模式下运行,结果是using_name。

1
2
>>> import using_name
using_name

修改using_name.py,如下。

1
2
3
4
if __name__ == '__main__':
    print('run  itself')
else:
    print('imported from another module')

通过python命令直接运行,python using_name.py
运行结果是run itself。

在Python的交互模式下运行,结果是imported from another module。

1
2
>>> import using_name
imported from another module

转载请注明本网址。