day24 包
1. 包
包是一种通过 '.模块名'来组织python模块名称空间的形式. 那什么样的东西是包呢? 我
们创建的每个文件夹都可以被称之为包. 但是我们要注意, 在python2中规定. 包内必须存在
__init__.py文件. 创建包的目的不是为了运行, 而是被导入使用. 包只是一种形式而已. 包的本
质就是一种模块
为何要使用包? 包的本质就是一个文件夹, 那么文件夹唯一的功能就是将文件组织起来,
随着功能越写越多, 我们无法将所有功能都放在一个文件中, 于是我们使用模块去组织功能,
随着模块越来越多, 我们就需要一个件夹将模块文件组织起来, 以此来提高程序的结构性和可
维护性
首先, 我们先创建一些包. 用来作为接下来的学习. 包很好创建. 只要是一个文件夹, 有
init__.py就可以 (尽管python3不要求,为了安全起见还是装了比较保险)
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 是错误的
2. __init__
包里的__init__.py是什么鬼? 其实. 不论
我们使用哪种方式导入一个包, 只要是第一次导入包或者是包的任何其他部分, 都会先执行
__init__.py文件. 这个文件可以是空的. 但也可以存放⼀些初始化的代码. (随意在glance中的
__init__.py都可以进行测试)
那我们之前用的from xxx import *还可以⽤么? 可以. 我们要在__init__.py文件中给出
__all__来确定* 导入的内容.
#在__init__文件中
print("我是glance的__init__.py⽂件. ")
x = 10
def hehe():
print("我是呵呵")
def haha():
print("我是哈哈")
__all__ = ['x', "hehe"]
在test文件中
from glance import *
print(x) # OK
hehe() # OK
haha() # 报错. __all__没有这个东西
3 .绝对导入和相对导入
我们的最顶级包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中 test.py和glance是同级的
import glance
此时导入的glance什么都做不了. 因为在glance中的__init__.py中并没有关于⼦包的加
载. 此时我们需要在__init__.py中分别取引入⼦包中的内容.
1. 使用绝对路径
2. 使用相对路径
包的注意事项:
1. 关于包相关的导入语句也分为import和from xxx import xxx两种, 但无论使用哪种,
无论在什么位置, 在导入时都必须遵循三个原则: 凡是在导入时d带点的. 点左边都必须是一
个包. 否则报错. 可以带一连串的点. 比如a.b.c,对于导入后,在使用时就没有这种限制了,
点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。
2. import导入文件时. 产生名称空间中的名字来源于文件, import 包, 产生的名称空间
中的名字同样来源于文件, 即包下的__init__,py, 导入包本质就是在导入该文件
3. 包A和包B下有同名模块也不会冲突, 如A.a和B.a来自两个名称空间
模块的搜索路径
python解释器在启动时会自动加载一些模块,可以使用sys.modules查看
在第一次导入某个模块时(比如my_module),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用
如果没有,解释器则会查找同名的内建模块,如果还没有找到就从sys.path给出的目录列表中依次寻找my_module.py文件。
所以总结模块的查找顺序是:内存中已经加载的模块->内置模块->sys.path路径中包含的模块
sys.path的初始化的值来自于:
The directory containing the input script (or the current directory when no file is specified).
PYTHONPATH (a list of directory names, with the same syntax as the shell variable PATH).
The installation-dependent default.
需要特别注意的是:我们自定义的模块名不应该与系统内置模块重名。虽然每次都说,但是仍然会有人不停的犯错。
在初始化后,python程序可以修改sys.path,路径放到前面的优先于标准库被加载。
import sys
sys.path.append('/a/b/c/d')
sys.path.insert(0,'/x/y/z') #排在前的目录,优先被搜索
注意:搜索时按照sys.path中从左到右的顺序查找,位于前的优先被查找,sys.path中还可能包含.zip归档文件和.egg文件,python会把.zip归档文件当成一个目录去处理。
#首先制作归档文件:zip module.zip foo.py bar.py
import sys
sys.path.append('module.zip')
import foo,bar
#也可以使用zip中目录结构的具体位置
sys.path.append('module.zip/lib/python')
#windows下的路径不加r开头,会语法错误
sys.path.insert(0,r'C:\Users\Administrator\PycharmProjects\a')
包是一种通过使用‘.模块名’来组织python模块名称空间的方式。
1. 无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,都要第一时间提高警觉:这是关于包才有的导入语法
2. 包是目录级的(文件夹级),文件夹是用来组成py文件(包的本质就是一个包含__init__.py文件的目录)
3. import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的__init__.py,导入包本质就是在导入该文件
强调:
1. 在python3中,即使包下没有__init__.py文件,import 包仍然不会报错,而在python2中,包下一定要有该文件,否则import 包报错
2. 创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包即模块
如果当前有重名read1或者read2,那么会有覆盖效果。
#插一个点
#my_module.py
print('from the my_module.py')
money=1000
def read1():
print('my_module->read1->money',money)
def read2():
print('my_module->read2 calling read1')
read1()
def change():
global money
money=0
my_module模块
#测试三:导入的函数read1,被当前位置定义的read1覆盖掉了
#demo.py
from my_module import read1
def read1():
print('==========')
read1()
'''
执行结果:
from the my_module.py
==========
'''
注:
,每个模块只被导入一次,放入字典sys.modules中,如果你改变了模块的内容,你必须重启程序,python不支持重新加载或卸载之前导入的模块,
有的同学可能会想到直接从sys.modules中删除一个模块不就可以卸载了吗,注意了,你删了sys.modules中的模块对象仍然可能被其他程序的组件所引用,因而不会被清除。
特别的对于我们引用了这个模块中的一个类,用这个类产生了很多对象,因而这些对象都有关于这个模块的引用。