2. __all__ 的使用
2.1. 从__init__.py谈起
__init__.py
的 作用一 :package 的标识
在每一个 package 文件夹中都会有一个
__init__.py
文件。我们导入一个包时,实际上是导入了它的__init__.py
文件。因此我们可以在__init__.py
文件中批量导入所需的模块, 而不需要再一个一个地倒入。### package ## __init__.py import sys import os import math ## test.py import package print package.math.sqrt(2)
__init__.py
的 作用二 :定义该 package 的 __all__
,用以模糊导入
python中包(package)和模块(module)有两种导入形式:精确导入和模糊导入。
- 精确导入
from PACK import CLASS1, CLASS2 import PACK.CLASS1- 模糊导入
from PACK import *
__all__
是一个字符串列表,用于定义模糊导入中的 *
中的模块,即暴露接口,也是对于模块公开接口的一种约定。
举个例子,建立如下目录结构的文件夹及文件:
.
├── myPack
│ ├── func.py
│ └── __init__.py
└── test.py
创建了包 myPack ,其中 func.py 中定义了该包的功能,包括变量、类、函数的定义。使用 test.py 来测试这个包的调用。
__init__.py
中的内容如下:
1import sys
2import os
3import math
4
5from func import x, foo
6# 假设x是一个变量,foo是一个函数
7
8__all__ = ['x', 'foo', 'math', 'os', 'sys']
test.py 中的内容如下:
1from myPack import *
2
3print x
4
5foo()
6
7print math.sqrt(2)
2.2. 路径
import
在导入模块时,会根据 sys.path
中的路径来搜索对应的模块。 sys.path
是一个列表,import
时会从 sys.path
的第一个路径开始搜索。
sys.path
默认的路径为:
当前目录的路径,即
sys.path[0]
。环境变量
PYTHONPATH
中指定的路径列表。Python 安装路径的
lib
目录所在路径。
我们可以将需要的路径添加到 sys.path
,有如下几种方式:
动态修改
sys.path
。这种方式只会对当前项目临时生效。1import os 2import sys 3## parenddir 是当前代码文件所在目录的父目录 4parenddir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)) 5sys.path.append(parenddir)修改
PYTHONPATH
环境变量,所有的 Python 项目都会受到影响。在
sys.path
已有的某一个目录下添加 .pth 后缀的配置文件,该文件的内容是要添加的搜索路径。Python 在遍历已有目录的过程中,如果遇到 .pth 文件,就会将其中的路径添加到sys.path
中。
2.3. 命名空间包
Python3.3 之后引入了命名空间包(namespace packages)的概念,目录下不再需要 __init__.py
。
命名空间包可以避免名字空间的污染,且具有不连续性(类似于C++),即同一个包内的模块可以不在同一个文件系统。
相比之下,有 __init__.py
的包叫做常规包(regular packages),同一个包内的模块在同一个文件目录下。
新建如下目录(命名空间库):
datetime/
└── datetime.py
datetime.py 内容如下:
1def now():
2 print("hello world")
3
4def then():
5 print("good bye")
注意到,这里的 datetime 与 python 自带的库重名了。
1>>> from datetime import datetime
2>>> datetime
3<class 'datetime.datetime'>
4>>> datetime.now() ## 调用了系统的库而不是新建的库
5datetime.datetime(2020, 3, 8, 11, 42, 53, 472470)
1>>> import sys
2>>> sys.path.insert(0, '/data6/fong/a/datetime')
3>>> import datetime
4>>> datetime
5<module 'datetime' from '/data6/fong/a/datetime/datetime.py'>
6>>> datetime.now()
7hello world
8>>> datetime.then()
9good bye
新建如下目录(常规库):
datetime/
├── datetime.py
└── __init__.py
1>>> from datetime import datetime
2>>> datetime
3<module 'datetime.datetime' from '/data6/fong/a/datetime/datetime.py'>
4>>> datetime.now()
5Traceback (most recent call last):
6 File "<stdin>", line 1, in <module>
7AttributeError: module 'datetime.datetime' has no attribute 'now'
2.4. 相对路径导入
from .Module import func
表示从当前目录的模块中导入。
from ..PKG.Module import func
表示从上一级目录的包中导入。
错误一:
ImportError: attempted relative import with no known parent package.
这是因为相对导入发生在包的内部,此时在包的内部直接运行该模块会报错,应该在项目的顶层目录运行主程序,通过主程序(直接/间接)调用该模块。
错误二:
ValueError: attempted relative import beyond top-level package
与主程序同一目录下的包称为顶层包(top-level package),各个顶层包之间不能进行相对调用。
2.5. getattr
getattr()
函数用于返回一个对象属性值:
getattr(object, name[, default])
参数:
object: 对象。
name:字符串,对象属性。
default:默认返回值,如果不提供该参数,在没有对应属性时,将触发
AttributeError
。
1>>>class A(object):
2... bar = 1
3...
4>>> a = A()
5>>> getattr(a, 'bar')
61
7>>> getattr(a, 'bar2')
8Traceback (most recent call last):
9 File "<stdin>", line 1, in <module>
10AttributeError: 'A' object has no attribute 'bar2'
11>>> getattr(a, 'bar2', 3)
123
在 __all__
中添加包名之后,可以通过 getattr()
直接调用相应的模块。
建立新的包如下:
pkg/
├── func.py
└── __init__.py
func.py 内容为:
def say():
print("hello")
__init__.py 内容为:
from .func import *
__all__ = ["say",]
1>>> import pkg
2>>> getattr(pkg, "say")
3<function say at 0x7f6134fdf560>
4>>> getattr(pkg, "say")()
5hello
2.6. 参考资料
Python中的 __all__
Python包中 __init__.py 作用
Python __init__.py 作用详解
Python中 __all__ 的用法
What is __init__.py for?
Regular packages
详解 Python import 机制 (一):import 中的基本概念
Python getattr() 函数
Python 相对导入attempted relative import beyond top-level package