一、模块
-
常见的场景:一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀,但其实import加载的模块分为四个通用类别:
- 使用python编写的代码(.py文件)
- 已被编译为共享或DLL的C或C++扩展
- 包好一组模块的包
- 使用C编写并链接到python解释器的内置函数
-
如果你退出Python解释器然后再进入,那么你之前定义的函数或变量都将丢失,因此我们通常将程序写到文件中以便永久保存下来,需要时就通过python 文件.py ,此时执行的文件就叫做script。当文件脚本很多时,我们将程序分成一个个文件,这样程序的结构就更清晰,方便管理,这时我们不仅仅可以把这些文件当成脚本文件,还可以把他们当作模块来导入到其他模块,实现功能的重用
-
使用模块: 我们可以从sys.modules中找到当前已经加载的模块
-
模块的导入相当于执行了整个文件
-
一个模块不能被多次导入,一旦导入原文件中的修改也不会生效,且各个模块的变量于本模块不冲突
x#测试一:money与my_module.money不冲突
import my_module # 默认有以下属性
money=10
print(my_module.money) #属性和方法都可以my_moudle.方法()
my_moudle.read()
'''
执行结果:
from the my_module.py
1000
'''
import my_moudle # 次文件中有print('123')
import my_moudle
# 打印一个 123
-
一个模块不会被多次执行,只执行一遍,再两个模块相互调用时要分清。:第一次导入后就将模块名加载到内存了,后续的import语句仅是对已经加载大内存中的模块对象增加了一次引用,不会重新执行模块内的语句。
-
导入一个模块的时候命名空间的变化:
- 创建了一个要导入模块的命名空间
- 创建一个变量指向这个命名空间,其实相当于加载文件类(个人理解)
- 执行这个文件,注意这个关系是单向引用,也就是说,如果不是函数传参的形式,是不能把本文件的变量a传给模块中的变量a
-
模块虽然一行可以导入多个,但是不推荐这样使用。如:import time,os,random,my_module
-
as语法的使用:
xxxxxxxxxx
#1、time这个名字就失效的,只剩下t了
import time as t
t.time()
#2、用来做兼容,当要根据判断取模块时,我们可以先判断,再以同样的as名字
mode = 'pickle' #不确定是 json还是 pickle
if mode == 'pickle':
import pickle as mode
else:
import json as mode
def dump():
mode.dump(obj,f)
def load():
mode.load(f)
#其他用法:
#1
from my_module import read1 as read
#2
from my_module import (read1,
read2,
money)
-
from …… import ……from 语句相当于import,也会创建新的名称空间,但是将my_module中的名字直接导入到当前的名称空间中,在当前名称空间中,直接使用名字就可以了
xxxxxxxxxx
#1测试一:导入的函数read1,执行时仍然回到my_module.py中寻找全局变量money
from my_module import read1
money=1000
read1()
'''
执行结果:
from the my_module.py
spam->read1->money 1000
'''
#测试二:导入的函数read2,执行时需要调用read1(),仍然回到my_module.py中找read1()
from my_module import read2
def read1():
print('==========')
read2()
'''
执行结果:
from the my_module.py
my_module->read2 calling read1
my_module->read1->money 1000
'''
#测试三:导入的函数read1,被当前位置定义的read1覆盖掉了
from my_module import read1
def read1():
print('==========')
read1()
'''
执行结果:
from the my_module.py
==========
'''
#from my_module import * 把my_module中所有的不是以下划线(_)开头的名字都导入到当前位置,大部分情况下我们的python程序不应该使用这种导入方式,因为*你不知道你导入什么名字,很有可能会覆盖掉你之前已经定义的名字。而且可读性极其的差,在交互式环境中导入时没有问题
#在my_module.py中新增一行:
__all__=['money','read1']
#这样在另外一个文件中用from my_module import *就这能导入列表中规定的两个名字
#__all__=[] 和 * 配合使用
#补充:
#如果my_module.py中的名字前加_,即_money,则from my_module import *,则_money不能被导入
-
二、模块导入的三大问题
-
模块的搜索路径:
-
python解释器在启动时会自动加载一些模块,可以使用sys.modules查看,在第一次导入某个模块时(比如my_module),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用.如果没有,解释器则会查找同名的内建模块,如果还没有找到就从sys.path给出的目录列表中依次寻找my_module.py文件。
xxxxxxxxxx
#1、所以总结模块的查找顺序是:内存中已经加载的模块 -> 内置模块 -> ys.path路径中包含的模块
#2、需要特别注意的是:我们自定义的模块名不应该与系统内置模块重名
#3、搜索时按照sys.path中从左到右的顺序查找,位于前的优先被查找,sys.path中还可能包含.zip归档文件和.egg文件,python会把.zip归档文件当成一个目录去处理。在pycharm中会自动为我们加载本地路径
import sys
sys.path.append('/a/b/c/d')
sys.path.insert(0,'/x/y/z') #排在前的目录,优先被搜索
-
-
模块能不能被循环导入:
-
xxxxxxxxxx
#能不能 在 a.py import b
#在 b.py import a
# 以主程序为主,加载顺序从上至下,可引用
-
把模块当做脚本执行:
-
当做脚本运行:
__name__ 等于'__main__'
:__name__
等于模块名xxxxxxxxxx
def fib(n):
a, b = 0, 1
while b < n:
print(b, end=' ')
a, b = b, a+b
print()
if __name__ == "__main__": #只有在当前文件下执行,如果是模块调用则不会执行
print(__name__)
num = input('num :')
fib(int(num))
-
三、包
-
包是一种通过使用'模块名'来组织python模块名称空间的方式。
-
无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,都要第一时间提高警觉:这是关于包才有的导入语法
-
包是目录级的(文件夹级),文件夹是用来组成py文件(包的本质就是一个包含
__init__.py
文件的目录 -
import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的
__init__.py
,导入包本质就是在导入该文件 -
在python3中,即使包下没有
__init__.py
文件,import 包仍然不会报错,而在python2中,包下一定要有该文件,否则import 包报错 -
创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包即模块
-
import 包:相当于执行了这个包下的init文件
-
具体使用:
xxxxxxxxxx
import glance.api.policy as policy #假设glance是包,api是文件,policy是py文件 用别名
policy.get()
#也可以:import glance.api.policy.get() 但是太长了,每个都要写
# 根据包的导入要精确到模块名,不能精确到具体的函数或者变量,然后使用glance.api.policy或者重命名的方式,来使用这个模块中的所有名字
#from……import 在包中的用法
from glance.api import policy #import后面不能有. 且至少要精确到模块
policy.get()
#or
from glance.api.policy import get
get()
#使用from……import import后面至少是精确到模块的,import后面不能有。from后面可以有.,但是.的左边永远是包名
#import 直接导入包时,再导入时执行了包中的__init__.py文件(类似于实例化),我们可以在__init__.py中写相应的路由,假如我想直接import 一个包,就需要配置__init__.py文件:
# 1、现在glance(包)中的__init__.py,需要配置:
-
__init__.py
文件:不管是哪种方式,只要是第一次导入包或者是包的任何其他部分,都会依次执行包下的init.py文件(我们可以在每个包的文件内都打印一行内容来验证一下),这个文件可以为空,但是也可以存放一些初始化包的代码
-
首先要想使用import glance 里面的如api下的policy中的方法,先配置glance文件下的
__init__.py
xxxxxxxxxx
# /glance/__init__.py
from glance import api
from glance import cmd
from glance import db # 此时在包外一个文件import glance,可以调用api,cmd,db,但是还不能导入其中的具体方法
#test/my_moudle
import glance
print(glance) # <moudle 'glance' from 'D:\\'>
print(glance.api) #<moudle 'glance.api' from 'D:……>
print(glance.api.policy) #不行 如何解决,因为没有在api文件的init中配置路径
#此时在api/__init__.py中输入
from api import policy # 之后再运行还是报错,问题出在from api这里,找不到api,这时候可以通过sys.path 查看路径,显示只能查看能找到glance,也就是说能执行glance中的__init__.py文件,但是不能执行api中的init文件,所以这里有两种方法解决这个问题:
#方案一: 在api文件中的__init.py文件中添加路径
import sys
sys.path.append(r'D:\……\glance') #将glace文件加载到系统路径中,于是会查找其中的各个文件
from api import policy # 导入glance后,api文件就会被找到,一层一层的
from api import versions
#此时api文件下的文件都可以使用了,但是如果要导入cmd文件下的文件,就还需要在它文件下的init文件做同样的处理。麻烦
#方案二:绝对路径,不需要添加路经,使用绝对导入、
#/glance/api/__init__.py
from glance.api import policy #效果相同,不需要让api的上一级成为环境变量中
from glance.api import versions
#总结: 在包中的__init__.py文件中的就是在import时要执行的文件,各级子文件的__init__.py文件最好使用绝对导入,这样,想导入的时候才能找到
-
五、绝对导入和相对导入
-
绝对导入:以glance作为起始
相对导入:用.或者..的方式最为起始(只能在一个包中使用,不能用于不同目录内)
xxxxxxxxxx
#在glance/api/version.py
#绝对导入
from glance.cmd import manage
manage.main()
#相对导入
from ..cmd import manage
manage.main()
#可以用import导入内置或者第三方模块(已经在sys.path中),但是要绝对避免使用import来导入自定义包的子模块(没有在sys.path中),应该使用from... import ...的绝对或者相对导入,且包的相对导入只能用from的形式。
-
单独导入包:单独导入包名称时不会导入包中所有包含的所有子模块
x
#在与glance同级的test.py中
import glance
glance.cmd.manage.main()
'''
执行结果:
AttributeError: module 'glance' has no attribute 'cmd'
'''
#解决办法: 规划路经,使用__init__.py,__all__是用于控制from...import *
#glance/__init__.py
from . import cmd
#glance/cmd/__init__.py
from . import manage
#执行
#在于glance同级的test.py中
import glance
glance.cmd.manage.main()
六、软件开发规范
七、补充:
x
# 同一大文件下(项目目录),如何导入各个小文件中的py文件的导入: __file__:当前文件路径
#项目开始的文件中导入
import os
start_path = __file__
bin_path = os.path.dirname(start_path) # 翻一层
project_path = os.path.dirname(bin_path) # 再翻
sys.path.append(project_path) # 添加路径,可以迁移文件
#规定
Base_path = os.path.dirname(os.path.dirname(__file__))
sys.path.append(project_path)
#注意的是:在每个模块的开头,需要导入时,都需要使用from 父目录 import ……,也就是说现在项目的所有小目录能找到,所以,我们的子目录都是要记录这些小目录开始 from。