包是⼀种通过 '.模块名'来组织python模块名称空间的⽅式。那什么样的东⻄是包呢? 我们创建的每个⽂件夹都可以被称之为包。但是我们要注意, 在python2中规定, 包内必须存在__init__.py⽂件, 创建包的⽬的不是为了运⾏, ⽽是被导入使⽤。包只是⼀种形式⽽已,包的本质就是⼀种模块。
为何要使⽤包? 包的本质就是⼀个⽂件夹, 那么⽂件夹唯⼀的功能就是将⽂件组织起来,随着功能越写越多,我们⽆法将所有功能都放在⼀个⽂件中, 于是我们使⽤模块去组织功能,随着模块越来越多, 我们就需要⽤⽂件夹将模块⽂件组织起来, 以此来提⾼程序的结构性和可维护性。
⾸先, 我们先创建⼀些包。⽤来作为接下来的学习。包很好创建,只要是⼀个⽂件夹, 有__init__.py就可以。
import os os.makedirs('glance/api') os.makedirs('glance/cmd') os.makedirs('glance/db') l = [] l.append(open('glance/__init__.py','w')) l.append(open('glance/api/__init__.py','w')) l.append(open('glance/api/policy.py','w')) l.append(open('glance/api/versions.py','w')) l.append(open('glance/cmd/__init__.py','w')) l.append(open('glance/cmd/manage.py','w')) l.append(open('glance/db/__init__.py','w')) l.append(open('glance/db/models.py','w')) map(lambda f:f.close() ,l)
我们接下来给每个⽂件中添加⼀些⽅法:
#policy.py def get(): print('from policy.py') #versions.py def create_resource(conf): print('from version.py: ',conf) #manage.py def main(): print('from manage.py') #models.py def register_models(engine): print('from models.py: ',engine) #接下来. 我们在test中使⽤包中的内容. 并且, 我们导入包的时候可以使⽤import或者 from xxx import xxx这种形式. ⾸先, 我们看import import glance.db.models glance.db.models.register_models('mysql')
没问题,很简单, 我们还可以使⽤from xxx import xxx 来导入包内的模块
from glance.api.policy import get get()
也很简单, 但是, 要注意:from xxx import xxx这种形式, import后⾯不可以出现"点" 也就是说from a.b import c是ok的, 但是 from a import b.c 是错误的。
好了, 到⽬前为⽌, 简单的包已经可以使⽤了。那包⾥的__init__.py是什么⻤? 其实. 不论我们使⽤哪种⽅式导入⼀个包, 只要是第⼀次导入包或者是包的任何其他部分, 都会先执⾏
__init__.py⽂件. 这个⽂件可以是空的。但也可以存放⼀些初始化的代码(随意在glance中的__init__.py都可以进⾏测试)。
那我们之前⽤的from xxx import *还可以⽤么? 可以,我们要在__init__.py⽂件中给出__all__来确定* 导入的内容。
print("我是glance的__init__.py⽂件. ") x = 10 def hehe(): print("我是呵呵") def haha(): print("我是哈哈") __all__ = ['x', "hehe"] test.py from glance import * print(x) # OK hehe() # OK haha() # 报错. __all__⾥没有这个⻤东⻄
接下来, 我们来看⼀下绝对导入和相对导入, 我们的最顶级包glance是写给别⼈⽤的。 然后在glance包内部也会有彼此之间互相导入的需求,这时候就有绝对导入和相对导入两种⽅式了。
1. 绝对导入: 以glance作为起始
2. 相对导入: ⽤. 或者..作为起始
例如, 我们在glance/api/version.py中使⽤glance/cmd/manage.py # 在glance/api/version.py #绝对导⼊ from glance.cmd import manage manage.main() #相对导⼊ # 这种情形不可以在versions中启动程序. # attempted relative import beyond top-level package from ..cmd import manage manage.main()
测试的时候要注意,python包路径跟运⾏脚本所在的⽬录有关系。说⽩了,就是你运⾏的py⽂件所在的⽬录,在python中不允许你运⾏的程序导包的时候超过当前包的范围(相对导
入)。如果使⽤绝对导入,没有这个问题,换个说法,如果你在包内使⽤了相对导入,那在使⽤该包内信息的时候, 只能在包外⾯导入。
接下来, 我们来看⼀个⼤坑,比如,我们想在policy中使⽤verson中的内容。
# 在policy.py import versions
如果我们程序的入⼝是policy.py 那此时程序是没有任何问题的。但是如果我们在glance外⾯import了glance中的policy就会报错,原因是如果在外⾯访问policy的时候. sys.path中的
路径就是外⾯。所以根本就不能直接找到versions模块,所以⼀定会报错:
ModuleNotFoundError: No module named 'versions'
在导包出错的时候,⼀定要先看sys.path 看⼀下是否真的能获取到包的信息。
最后,我们看⼀下如何单独导入⼀个包。
# 在test.py中 import glance
此时导入的glance什么都做不了,因为在glance中的__init__.py中并没有关于⼦包的加载,此时我们需要在__init__.py中分别取引入⼦包中的内容。
1. 使⽤绝对路径 2. 使⽤相对路径
包的注意事项:
1. 关于包相关的导入语句也分为import和from xxx import xxx两种, 但⽆论使⽤哪种,⽆论在什么位置,在导入时都必须遵循⼀个原则: 凡是在导入时带点的,点左边都必须是⼀
个包,否则报错,可以带⼀连串的点,比如a.b.c。
2. import导入⽂件时, 产⽣名称空间中的名字来源于⽂件, import 包, 产⽣的名称空间中的名字同样来源于⽂件, 即包下的__init__.py, 导入包本质就是在导入该⽂件。
3. 包A和包B下有同名模块也不会冲突, 如A.a和B.a来⾃两个名称空间。