简介:
在我们之前学习的编程范式中有 面向过程式编程、函数式编程、面向对象编程……………… 函数式编程是为了将相同功能的代码归类到一块,方便调用,而类也是解决代码冗余,功能区分开来。 不可能将一个APP的功能全部都写在一个文件里。 那这样的话如果下次要修改某个功能,全部代码都要跟着改动。 而模块与函数、类也大至相同,都是为了将代码功能区分开来,方便调用 ,解决代码的冗余。比如 需要一个删除的功能,则直接调用‘删除’功能的模块代码。也就实现了删除功能。 而不用再去重复的造轮子。 下面就进入模块的学习。
模块与包:
模块分为三类:1、内置模块(也就是当python程序执行时候,就已经加载了。python自带)
2、第三方模块:自己下载的一些第三方包
3、自定义: 自己定义的模块,可以被import 或from …… import …
先说自定义模块:
新建一个cover.py文件
#!/usr/bin/env python #!-*- coding:utf-8 –*- print(' cover file')
name='tony' age='22' def check_name(): print('cover file ,name is ',name) def check_age(): print('cover file age is',age) def modify_age(): global age age=17
再新建 一个call_cover.py 的文件: 这个文件用来调用 cover 模块
#!/usr/bin/env python #!-*- coding:utf-8 -*- import cover #调用cover模块,注意调用是不需要.py的 ,
这里的import cover
第一件事:是在创建名称空间,这个名称空间是用来存放cover中定义的名字
第二件事:基于刚刚创建的名称空间,执行cover.py ,在 import cover 的时候整个文件都已经被执行了一遍。 而此时也会生一个相应的pyc后缀的文件。—>也就是已经保存在内存里了,只是 再次引用
第三件事:在本文创建的名字不影响cover 的调用,因为cover.名字的操作只操作cover 的名称空间---> 简言之: 创建名字cover 来引用该命名空间
print(cover.name) # 再打印出cover 下的name 这个名字 ,name对应的名字是'tony'
每个模块都是一个独立的名称空间,定义在这个模块中的函数、变量,把这个模块的名称空间当做全局名称空间,这样我们在编写自己的模块时,就不用担心我们定义在自己模块中全局变量在被导入时,与使用者的全局变量冲突。
如:
######call_cover.py 文件
from cover import check_name #这里导入了cover 文件 下的check_name方法,这种调用方式 from … import … 就不用cover.名字的方式调用了
def check_name(): #自己定义了一个check_name方法
print('call_cover name is',name)
check_name() #这里的调用 依旧是 cover的内容, 验证了 上面注红的那句。
这样就完成了一个模块的导入
查看当前已经加载的模块
import sys print(sys.modules) #可以查看到当前已经加载的模块,sys.modules 是一个字典,内部包含了‘模块名与模块对象的映射’,该字典决定了导入模块时,是否需要重新导入。
为模块起别名:
import cover as cov #如果模块名字过长,就可以采取这种方式,优雅的使用 module.名字 的调用 print(cov.name)
还有另一种情况用到 模块起别名
当指定文件格式的时候:
if file_format=='xml': 不同格式,导入不同的模块名 import xmlread as reader elif file_format=='csv': import csvread as reader
import 模块名 与 from ….. import 模块名:
二者有相应的应用场景, 对比 import cover ,会将尖文件的名称空间‘cover’ 带到当前名称空间中,使用时必须是 cover. 的方式,而from .. import .. 也会创建新的名称空间,但是将cover 中的名字直接导入到当前的名称空间中,在当前名称空间直接使用名字或者方法就可以调用。
from cover import name print(name) #当前名称空间, 如果在这里修改了name,那值肯定会发生改变,因为会再次将name 绑定到另一个新的值,
模块重名:
cover.py
print(' cover file') name='tony' age='22' def check_name(): print('cover file ,name is ',name) def check_age(): print('cover file age is',age) def modify_age(): global age age=17 def look_all(): print('cover file name is',name)
call_cover.py
from cover import check_age,look_all # cover 下有个方法或名字叫 check_age , print(check_age) #通过打印出来知道这是一个函数 ,既然是函数,加括号就能运行。 check_age() 这里就已经运行cover 的check_age()方法 那如果这个时候有重名的的名字的话呢?? check_age='abc' print(check_age) #这个时候的结果然会被替换掉。 因为使用的是from 到当前的名称空间了。 这个时候再调用其它的函数: look_all() #这个函数调用的是cover 的name ,与当前的名称空间无关,不受影响
from cover import *
这种情况不推荐使用,因为在自己不知道的情况下导入,很大的可能把之前定义的内容覆盖掉。因为 * 代表了所有,你不知道的都导入进去了。
name='liang' from cover import * print(name)
既然不能用,还说它干嘛???
可以在 cover 文件中规定 被 import * 的内容
#!/usr/bin/env python #!-*- coding:utf-8 -*- __all__=['check_name','check_age'] #就是它 print(' cover file') name='tony' age='22' def check_name(): print('cover file ,name is ',name) def check_age(): print('cover file age is',age) def modify_age(): global age age=17 def look_all(): print('cover file name is',name)
call_cover.py
from cover import * #这里导入的就只是 'money','read1' 这两个模块了 check_age() check_name() print(name) #这个就会查找不到。因为导入的时候 已经限制,且 全局 名称空间也没有创建
把模块当做脚本运行:
在文件最后加如下代码 ,
if __name__=="__main__": print('文件被当做脚本去执行') read1() print(__name__)
模拟搜索路径:
python 解释器在启动时会自动加载一些模块,可以使用sys.modules 查看
在第一次导入某个模块时 如(cover ) ,会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用。
如果没有--->按照顺序查找 ------ 1、 –> 先从内存找,sys.modules , 内存找不到 -->2、查找同名的内建模块 ,内建模块没有 --> 3、查找sys.path -->4、查看不到报错
sys.path
sys.path是python的搜索模块的路径集,是一个list
import sys
print(sys.path)
可以在python环境下使用 sys.path.append(path) 添加相关的路径,但在退出python环境后自己添加的路径就会自动消失!
sys.path.append('/one/two/three') # 添加至列表最后面 sys.path.insert(0,'/two/one/three') #插入到最前,最先搜索路径
python 编译
python 编译文件 python -m compileall /module_directory #递归编译 python -O -m compileall /module_directory -1 #则编译一层 命令行里使用compile() 函数时,自动使用python -O -m compileall 详见:https://docs.python.org/3/library/compileall.html#module-compileall
包
理解 ‘包’ :
‘包’ 是一堆代码文件构成的集合,每个包都有一个__init__.py 的文件,更高级别的模块
1、无论是import 形式还是from ….. import 形式,凡是在导入语句中(而不是在使用时)遇到带点的,都是关于‘包’才有的导入语法
- 包的本质就是一个包含__init__.py文件的目录.
- 包 A和包B下有同名模块也不会冲突,如A.a与B.b 来自两个命名空间
glance/ #Top-level package ├── __init__.py #Initialize the glance package ├── api #Subpackage for api │ ├── __init__.py │ ├── policy.py │ └── versions.py ├── cmd #Subpackage for cmd │ ├── __init__.py │ └── manage.py └── db #Subpackage for db ├── __init__.py └── models.py
文件内容创建:
#文件内容 #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)
在与包glance 同级别的文件中创建一个call_all.py 文件:
这个文件中调用glance下的模块
#!/usr/bin/env python #!-*- coding:utf-8 -*- import glance.db.models glance.db.models.register_models('nosql') from glance.db import models models.register_models('mysql')
__init__.py 文件:
__init__.py文件,只要是第一次导入包或者是包的任何其他部分,都会执行包下的__init__.py文件,(我们在每个包的文件内 都打印一行内容进行验证,),这个文件可以为空,但是也可以存放一些初始化包的代码。
__init__.py
print('db-init') #import 就会执行
如下:都会触发 __init__.py
#!/usr/bin/env python #!-*- coding:utf-8 -*- import glance.db.models
现在在当前glance文件下,想用glance 的police 模块
import glance.api.policy glance.api.policy.get()
这样用需要加 glance.api.policy 前缀 ,
现在想用不要前缀 调用 get()
from glance.api.policy import get get()
在导入的时候带点的---------->必须满足 from 包名 import 模块名 或者是from 包名.模块名 import 方法名,或者是import 后面一定不能是有点的形式,必须是 一个单独的。
点的左边必须是包 点的左边必须是包 点的左边必须是包
包下 有__init__.py就是一个包,那__init__.py有什么用。
这里只导入了api,但是只要一 import glance 下的__init__.py 就触发,跟着还有api 也触发了__init__.py
导入包也就做了这么一件事。请看打印内容。都打印相应的内容
那现在能不能调用到 glance.api.policy
导入包仅仅是做了一件事,就是执行了__init__.py 其它的什么事也没做,如果这个时候再来个导入其它的模块,是调用不到的
而下面这个导入方法,则指定了要导入谁,所以能调用到policy下的get() 方法
from glance.api import policy policy.get()
导入包就只是在执行__init__.py文件,你没有给什么文件就是没有文件,* 号还是代表__init__.py里面的所有
from glance.api import *
现在在api的 __init__.py的文件下 添加如下内容:
__all__=[‘x’,’y’,’’policy,’versions’]
x=1
y=2
现在再来导入:
现在就能拿到policy versions
同样,也可以policy.get()
但是不是能直接 get(),因为拿到的名字是policy 这个模块 ,对于模块来说*代表的意思。
现在看问题
import glance.api ,为何说没有 ,而且提示信息跑到 api 的__init__.py 去了
x却能找到:
现在的问题是api 文件下__init__.py没有policy的这个文件:
可以在__init__.py 文件里面添加内容:,但这个不是我们想要的。
那既然可以定义,不如直接 import policy 吧。
再到call_all.py文件调用 ,还是不行,错误出在__init__.py 文件里
现在是谁在调用 policy.py ,是call_all.py 在调用policy.py ,而不是__init__.py 在调用 policy .py文件
在call_all.py里只要一 import glance.api 就会触发api的__init__.py ,这个时候内存没有 policy ,内建也没有,sys.path 以call_all.py的为准。
都没有,所以报错。
再分析:在call_all.py import glance.api 就会触发api的__init__.py(它导入import policy ), 但是当前 sys.path 路径仍然是 call_all 的路径,它的路径指定没有policy--->报错。 如何解决 ---->>绝对导入, 和相对导入。 * 是对于__all___的,对于 单纯的import 没有意义
绝对导入:
在glance 下 api 下 __init__.py 添加 from glance.api import policy , 那call_all.py 就能找到路径并找到policy ,相应的也找到get()方法了,但是如果直接在glance下 api 下__init__.py 那就会报错,因为 from glance.api import policy 是基于调用者 的,而不是被调用者的,如果是被调用者也就是__init__.py 自己 from glance.api import policy 没有这个目录,所以是调用不到的。除非使用 import policy 是在自己的这一层目录的,但是这样就没有任何意义了, 包的意义就是被调用的,如果这样import 自已目录下的文件,就没有了调用的意义。
缺点:如果包名更名,就找不到模块了,所以有了相对导入
相对导入: . 和 . .
相对导入是 __init__.py 相对的。
现在要找一个main 的一个方法,基于glance下的api 下的 __init__.py , 那这个时候的路径就如下:
call_all.py文件调用 :
再调用同一级目录的create_resource() 方法
注意:还是基于glance下的api文件的__init__.py 文件,所以文件路径是 当前目录的versions 导入 create_resource ,这样就可以调用它下面的方法了
调用 :
用import 导入内置的或者第三方的模块,但是要绝对避免使用import来导入自定义包的子模块,应该使用from ….import …的绝对路径或者相对路径导入,且包的相对导入只能用 from 的形式。
比如现在glance/api/version.py 中导入glance/api/policy.py , call_all.py 调用:
这是version.py 文件 加入了from . import policy 与policy.py 同级
这时,call_all.py 调用 versions.py
这样就完成 api 里调用 versions 功能,versions里调用 policy功能。
错误用法:
现在glance/api/version.py 中导入glance/api/policy.py , call_all.py 调用:
没毛病。。。。在versions.py文件里执行也没有问题
那就call_all.py 继续调用吧。 ,调用方法和之前没有变,但是报错了、、、、、
因为在versions.py , 它的import policy 在它自己的文件中执行没有任何问题,但是包的本质意义就是让其它的模块去调用, 这里用的是让call_all.py去调用 , 这样一调用就是基于 和 glance 同一级的 call_all 去调用 policy了, call_all 这一级压根就没有policy.py模块文件,就报错。 所以还是得用 相对路径 或者绝对路径 来调用 ,同时推荐使用 相对路径来调用。
包 直接 . 使用:
glance/api/version.py
glance/api/policy.py
现在要 实现 glance. 任何功能
glance.get()
glance.register_modles(‘mysql’)
分析: 要实现 glance. 任何功能 的调用,那 就是在执行它的__init__.py 所以,把注意力放在glance的__init__就好,然后再是glance. 那就是glance下的__init__要 导入其 同级目录的 功能
这时候调用的话,就可以直接调用了
现在有另一个问题,现在glance 如果在另外一个目录 里面。如图:
现在已经不与call_all.py 同一级目录了。 现在 在call_all.py再用 import glance 必然找不到。
现在就要用到sys 模块
将它的路径添加到 sys.path中,这样就正常运行了,但是不能每次都 这样去运行吧,每次都COPY目录手动加进去。 这就要用到‘路径处理’了
路径处理:
方法一:
如果自己定义的包,想在文件任意位置 都能被 import ,永久生效,那就把文件放到 site-packages 文件 里,这样就可以在任意位置调用
print(sys.path) # 找到site-packages
如果只是用完就清除的话就用以下方法
方法二:
先找到本地的绝对路径
import os print(os.path.abspath(__file__)) #这里打印的就是绝对路径
现在要拿到的是test 的路径,才能拿到glance
print(os.path.dirname(os.path.abspath(__file__))) #这里拿到是 test 的上一级目录
将test 这一层的目录添加到 sys.path中
base_dir=os.path.dirname(os.path.abspath(__file__)) sys.path.append(base_dir)
call_all.py 调用
from test import glance glance.get()
还 有问题,我还想 直接 import glance
import os,sys base_dir=os.path.dirname(os.path.abspath(__file__)) sys.path.append(r'%s\test'%base_dir) #将目录拼接 import glance glance.get()