python 有一个关键字和 java 一样:
import
, 其功能也类似: 在代码中引入其他的依赖(模块)以使用;
不过, 不像 java 那么单纯, python 还要区分为 import module 和 import names 两大类; 作为一个 python 新手, 这些使用上的区别有时会令人感到迷惑;
python 包和 java 包在概念上也有类似之处, 不过 python 的 __init__.py 规范更讲究一些, java 的 package-info.java 重要性没有那么强, python 初学者在此也很容易栽跟头;
在使用了一段时间的 python 之后, 我突然发现, 关于模块引入相关的知识, 我还从来没有过一个系统性的整理; 故作此文以备将来查阅;
下面所示的是一个 python 工程结构, 包括了一个父 package 和其下的子 package , 结构比较完整; 本文将以此工程结构为例, 展开内容;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15MyPackage
├── connections.py
├── constants
│ ├── CLIENT.py
│ ├── CR.py
│ ├── ER.py
│ ├── FIELD_TYPE.py
│ ├── FLAG.py
│ ├── __init__.py
│ ├── REFRESH.py
├── converters.py
├── cursors.py
├── __init__.py
├── release.py
├── times.py
其中, 假设 connections.py 中定义了 Connection 类:1
2
3
4
5# connections.py
import _mysql
class Connection(_mysql.connection):
def __init__(self, *args, **kwargs):
...
下面开始本文的内容;
基础预备知识
对象的 __name__ 字段
所有 python 程序的执行必须要有一个入口, 而我们经常见到的入口会有这么一行代码:1
if __name__ == '__main__':
这里面涉及到了一个模块的属性: __name__
:
当一个模块以主模块被执行时, 该模块的 __name__ 就被解释器设定为 ‘__main__’;
当一个模块被其他模块引入时, 该模块的 __name__ 就被解释器设定为 ‘该模块的文件名’;
内建方法: dir()
python 中有一个全局内建方法 dir(p_object=None)
可以返回目标作用域里所有的成员 (names);
当方法参数 p_object 为 None 时, 默认返回当前作用域内的所有成员:1
2
3
4# 在 python shell 里执行, 作用域为主模块, 展示模块属性
import MyPackage
dir()
['MyPackage', '__builtins__', '__doc__', '__name__', '__package__']
1 | # 在方法内部执行, 作用域为方法内, 展示方法的字段 |
如果指定了目标作用域(对象), 则无论在哪里指定 dir () 方法, 都只打印指定目标的成员;1
2
3
4
5
6
7
8
9
10
11from MyPackage.connections import Connection
# 指定作用域
def print_dir(obj=None):
print dir(obj)
if __name__ == '__main__':
conn = Connection()
print_dir(conn)
output:
['__doc__', '__init__', '__module__']
import 的规则语法
python 导入其他模块分为两种: import module/package 与 import names (包括变量, 函数, 类等);
import module/package 的语法如下:1
2import MyPackage
import MyPackage.connections
import names 的语法如下:1
2
3
4
5
6# 引入类
from MyPackage.connections import Connection
# 引入方法
from MyPackage.connections import numeric_part
# 引入 __all__ 指定的所有 names
from MyPackage import *
对于不同的 package, 不同的 __init__.py 文件, 这些 import 语句所产生的效果都不尽相同, 详细的区别将在下一节描述;
__init__.py 文件的功能
对于 python 的每一个包来说, __init__.py 是必须的, 它控制着包的导入行为, 并可以表达非常丰富的信息; 如果没有 __init__.py 文件, 那这个包只能算是一个普通目录, 目录下的任何 python 文件都不能作为模块被导入;
以下是几种常见的 __init__.py 文件的内容:
__init__.py 文件内容为空
__init__.py 文件必须有, 但可以是空文件, 这将是最简单的形式, 当然其所提供的功能也最简单: 标识这是一个 python 包, 仅此而已;
如果将该包作为一个模块导入, 其实是等于什么都没导入:1
2
3
4
5import MyPackage
dir()
['MyPackage', '__builtins__', '__doc__', '__name__', '__package__']
dir(MyPackage)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__']
通过 dir() 内建方法可以发现, 无论是当前主模块, 还是 MyPackage 包, 除了一些保留 names, 不再有其他任何自定义符号, 这时将无法直接使用 MyPackage 下的任何模块:1
2
3
4
5# 直接 使用 connections.py 下的 Connection 类
conn = MyPackage.connections.Connection()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'MyPackage' is not defined
不过, 既然 __init__.py 已经标识了这是一个 python 包, 所以对于包下所有其他的模块文件, 我们可以主动引入它们, 这算是空 __init__.py 的唯一作用:1
2
3
4
5
6# 主动引入模块
import MyPackage.connections
dir(MyPackage)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', 'connections']
dir(MyPackage.connections)
['Connection', '__builtins__', '__doc__', '__file__', '__name__', '__package__', 'numeric_part']
这时可以发现, dir(MyPackage) 列表里有了 connections 模块, dir(MyPackage.connections) 列表里有了 Connection 类; 这时带着 python 路径, 就可以使用 target name 了:1
conn = MyPackage.connections.Connection()
另外, 如果使用 from … import … 主动引入目标符号:1
2
3from MyPackage.connections import Connection
dir()
['Connection', '__builtins__', '__doc__', '__name__', '__package__']
便可以直接将目标符号引入当前作用域, 不需要使用模块路径, 就可以直接使用:1
conn = Connection()
在 __init__.py 中 import 其他模块
__init__.py 中自己主动 import 第三方模块是一种常见的操作:1
2# __init__.py
import connections
1 | import MyPackage |
或者使用 from … import … 语法:1
2# __init__.py
from connections import Connection
1 | import MyPackage |
对于以上两种 import 方式, 结合 dir() 内建方法的展示, 可以发现在具体使用目标符号时所带路径的区别;
__init__.py 中的保留字段
(1) __all__ 字段:
如果在代码中使用了如下的引用方式:1
from MyPackage import *
解释器便会试图去指定的模块中寻找 __all__
字段, 将该列表中列举的所有 names 全部引入:1
2
3
4
5
6
7
8
9
10__all__ = [ 'BINARY', 'Binary', 'Connect', 'Connection', 'DATE',
'Date', 'Time', 'Timestamp', 'DateFromTicks', 'TimeFromTicks',
'TimestampFromTicks', 'DataError', 'DatabaseError', 'Error',
'FIELD_TYPE', 'IntegrityError', 'InterfaceError', 'InternalError',
'MySQLError', 'NULL', 'NUMBER', 'NotSupportedError', 'DBAPISet',
'OperationalError', 'ProgrammingError', 'ROWID', 'STRING', 'TIME',
'TIMESTAMP', 'Warning', 'apilevel', 'connect', 'connections',
'constants', 'converters', 'cursors', 'debug', 'escape', 'escape_dict',
'escape_sequence', 'escape_string', 'get_client_info',
'paramstyle', 'string_literal', 'threadsafety', 'version_info']
不过, 这种情况下不能完全清楚引入了什么 names, 有可能覆盖自己定义的 names, 最好谨慎使用;
(2) 其他信息, 如版本, 作者:1
2
3
4
5# 作者
__author__ = "Andy Dustman <farcepest@gmail.com>"
# 版本信息
version_info = (1,2,5,'final',1)
__version__ = "1.2.5"
在 __init__.py 中定义方法/类
__init__.py 也是 python 源文件, 在其中亦可以定义方法, 类, 或者执行代码段:1
2
3
4
5
6# MyPackage: __init__.py
def test_DBAPISet_set_equality():
assert STRING == STRING
def Binary(x):
return str(x)
此时, import 了 MyPackage 之后, 便可以正常使用定义的内容;
python 的工作/搜索路径
当导入一个 python 模块时, 解释器的查找路径如下:
- 在当前的包中查找;
- 在
__buildin__
模块中查找; - 在 sys.path 给定的路径中中查找;
其中, 第一点自不必说;
关于 __buildin__ 模块, 更多的信息请参见另一篇文章: python module 使用总结: __buildin__; 上文描述的 dir() 方法其实就是 __buildin__ 模块中的内建方法, 不需要额外引入其他模块便能直接使用;
而关于 sys.path, 其初始构成内容又包含了以下几处地方:
- 程序的主目录;
- PYTHONPATH 中定义的路径;
- 标准链接库, 例如: /usr/lib/python2.7, /usr/local/lib/python2.7 等;
1 | import sys |
如上所述, 从运行主模块的角度考虑:
- 如果引入的模块是第三方模块, 那么大部分情况下, 所需要的模块在标准链接库 dist-packages 中都有, python 能够成功引到;
- 如果引入的模块是自己的子模块, 由于子模块一定在主模块的子目录下, 所以 python 也能成功引到;
- 如果引入的模块是自己的父模块或者兄弟模块, 这时 python 能否成功引到, 就得分情况了:
如果工程在自己创建的目录中运行, 引入父模块或者兄弟模块, 在默认的搜索路径里是找不到的;
这时要想成功引到目标模块, 有两种办法:
(1) 向 sys.path 中拓展添加目标路径:1
2import sys
sys.path.append(os.path.abspath('xxx/yyy/zzz'))
(2) 使用 PYTHONPATH, 向其中添加目标路径:1
2
3# /etc/profile
export PATH=${PATH}:${target_path}
export PYTHONPATH=${PYTHONPATH}:${target_path}
至于这两种方法的好坏, 就是仁者见仁, 智者见智的问题了;
使用 sys.path.append, 比较灵活, 每个模块都可以自己定义, 但缺点是需要多添加两行代码, 比较繁琐;
使用 PYTHONPATH, 优点是不需要在自己的模块中添加额外的代码, 但是如果自己创建的工程路径比较零散, PYTHONPATH 就需要不停地补充新路径;
不过, 如果有诸如公司规范之类的, 将 python 项目都部署在约定的公共目录下, 那么 PYTHONPATH 只需要添加这一个公共路径即可, 这样问题便简单了;
至此, 关于 python 模块导入的基础性问题就讲完了;
最后要说的是, 其实本文最开始所列出的那个自定义模块 MyPackage, 其原型是 MySQLdb
;