Python进阶与拾遗2:Python中的包
本篇博文,主要总结Python中的包。在Python的大型工程中,包是常用的代码管理工具,用于管理上一讲中的 模块,下面开始干货。
包相关概念
包定义
- Python代码的目录称为包。
- 包导入是把计算机上的目录变成另一个Python命名空间,而属性对应于目录中包含的子目录和模块文件。
使用包的好处
- 包导入提供了程序文件的目录信息,因此可以轻松地找到文件,从而作为组织工具来使用。
- 大幅简化PYTHONPATH和.pth文件搜索路径设置。
- 包导入让你想导入的文件更明确,从而解决模糊性。
包的常见使用
包导入的用法
- 在import和from中,采用点号分隔目录路径。注意,需要读取两个或两个以上路径内的同名属性时,需要使用import,不能使用from。
import dir1.dir2.mod
from dir1.dir2.mod import x
# 读取同名属性
import doc1.file
import doc2.file
doc1.file.f()
doc2.file.f()
- import语句中的目录路径是平台不相关的,只能是以点号分隔的变量,不能在import语句中使用任何平台特定的语法。比如C:/file 或者My Doc.file(包含空格)。要实现上述功能,可以将相关目录添加到sys.path列表中,PYTHONPATH环境变量或者.pth文件中。
包导入的__init__.py文件
包导入语句的路径中每个目录内必须有一个__init__.py文件,否则会失败。这个文件可以包含Python代码,也可以是空的。作用如下:
- 包的初始化。Python首次导入某个目录时,会自动执行该目录下的__init__.py文件中的所有程序代码。
比如,在工程路径下有一个doc_1文件夹,里面有一个__init__.py和一个a.py:
# __init__.py(空)
# a.py
X = 88
在工程的与doc_1文件夹平级的执行main.py文件中:
from doc_1 import a
def main():
print(a.X) # 88
if __name__ == '__main__':
main()
更改__init__.py和a.py:
# __init__.py
Y = 99
# a.py
X = 88
在工程的与doc_1文件夹平级的执行main.py文件中:
import doc_1
from doc_1 import a
def main():
print(doc_1.Y) # 99
print(a.X) # 88
if __name__ == '__main__':
main()
-
模块命名空间初始化。在包导入的模型中,脚本内的目录路径,在导入后会变成真实的嵌套对象路径。在导入后,会返回一个模块对象,此对象的命名空间中包含了包路径中最后的目录中的__init__.py文件所赋值的所有变量名。
-
尤其注意,from *语句的行为。
可以在__init__文件内使用__all__列表定义目录以from *语句形式导入时,需要导出什么。在__init__.py文件中。__all__列表是指当包目录名称使用from *的时候,应该导入的子模块的名称清单。如果没有指定__all__,from *语句不会自动加载嵌套于该目录内的子模块,只加载该目录的__init__.py文件中赋值语句定义的变量名和程序代码明文导入的任何子模块。
比如,在工程路径下有一个doc_1文件夹,里面有一个__init__.py和一个a.py:
# __init__.py(空)
# a.py
X = 88
在工程的与doc_1文件夹平级的执行main.py文件中:
from doc_1 import *
def main():
# print(a.X) # 报错: NameError: name 'a' is not defined
if __name__ == '__main__':
main()
更改main.py:
from doc_1 import a
def main():
print(a.X) # 88
if __name__ == '__main__':
main()
更改main.py:
from doc_1.a import X
def main():
print(X) # 88
if __name__ == '__main__':
main()
更改__init__.py和a.py:
# __init__.py
__all__ = ["a"]
# a.py
X = 88
在工程的与doc_1文件夹平级的执行main.py文件中:
from doc_1 import *
def main():
print(a.X) # 88
if __name__ == '__main__':
main()
包的相对导入
包相对导入的用法
- 相对导入是用在包自身的内部。包文件导入可以使用和外部导入相同的路径语法,也可以使用相对路径。
- from语句后面的点号或连点号(一般都是用点号),表示导入时的搜索路径,并且不会搜索位于导入搜索路径上某处的同名模块,直接效果是包模块覆盖了外部的模块。
- 相对导入只适用于from语句,import语句无法实现。
- 在Python 2.6及之后的版本中,包的代码中的常规导入默认是先相对(先搜索自己的路径)再绝对(sys.path)的搜索路径顺序。而在Python 3.0及之后的版本中,在一个包中导入默认是绝对的,会忽略自身路径并直接在sys.path上面查找。也就是说,在Python 3.0及之后的版本中使用包相对导入,需要显式地指定路径。
比如,在工程路径下有一个doc_1文件夹,里面有一个__init__.py和一个a.py:
# __init__.py
from .a import *
# a.py
X = 88
在工程的与doc_1文件夹平级的执行main.py文件中:
import doc_1
def main():
print(doc_1.a.X) # 88
if __name__ == '__main__':
main()
更改main.py:
from doc_1 import a
def main():
print(a.X) # 88
if __name__ == '__main__':
main()
更改main.py:
from doc_1.a import *
def main():
print(X) # 88
if __name__ == '__main__':
main()
Python 3.0及之后版本的模块查找规则总结
- 简单模块名通过搜索sys.path路径列表上的每个目录来查找,从左到右进行。
- 包是一个带有特殊的__init__.py文件的Python模块的直接目录。这使得一个导入中可以使用A.B.C目录路径语法。A总是相对于sys.path的常规模块导入搜索,B是A中另一个包子目录,C是一个模块或B中的其他可导入项。
- 在一个包文件中,常规的import语句使用和其他地方的导入一样的sys.path搜索规则。如果使用相对路径,就不使用常规的sys.path查找,仅在指定的相对路径中查找。如果没有使用相对路径,就只在sys.path中查找。
以上,欢迎各位读者朋友提出意见或建议。
欢迎阅读笔者后续博客,各位读者朋友的支持与鼓励是我最大的动力!
written by jiong
江山代有才人出,
各领风骚数百年。