一、模块
1、什么是模块?
常见的场景: 一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀。但其实import加载的模块分为四个通用类别:
1 使用python编写的代码(.py文件)
2 已被编译为共享库或DLL的C或C++扩展
3 包好一组模块的包
4 使用C编写并链接到python解释器的内置模块
2、为什么要使用模块?
如果你退出python解释器然后重新进入,那么你之前定义的函数或者变量都将丢失,因此我们通常将程序写到文件中以便永久保存下来,需要时就通过python test.py方式去执行,此时test.py被称为脚本script。
随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做程序的结构更清晰,方便管理。这时我们不仅仅可以把这些文件当做脚本去执行,还可以把他们当做模块来导入到其他的模块中,实现了功能的重复利用。
3、如何自己写一个模块?
创建一个py文件,给它起一个符合变量名命名规则的名字,这个名字就是模块名。如下示例:
# my_module.py print(12345) name = 'alex' def read1(): print('hello world') def read2(): print('in read1 func',name) print(54321) 注:下文提到的my_module模块就是此示例内容。
二、导入模块
1、import导入模块
正如我们之前所见,可以通过import导入模块,比如import my_module
2、怎么使用my_module模块中的名字?
print(my_module.name)
my_module.read1()
3、import的命名空间
模块和当前文件在不同的命名空间中。
4、在导入模块的过程中发生了什么?
导入一个模块就是执行一个模块。
模块导入的过程如下:
# 找到这个模块 # 判断这个模块是否被导入过了 # 如果没有被导入过 # 创建一个属于这个模块的命名空间 # 让模块的名字 指向 这个空间 # 执行这个模块中的代码
5、模块是否可以被重复导入?怎么判断一个模块已经被导入过了?
import sys
print(sys.module)
模块的导入相当于执行这个模块所在的文件,模块的多次导入不会重复执行。
6、给模块起别名(起了别名后,使用这个模块就都使用别名引用变量了)
import my_module as m
m.read1() # 使用别名引用,而不能用my_module.read1()了
给模块起别名示例(当需要导入不同模块,且导入不同模块所做的操作相同,可以给模块起别名):
def func(dic, t='json'): if t == 'json': import json as aaa elif t == 'pickle': import pickle as aaa return aaa.dumps(dic)
7、导入多个模块
多行导入:
import os
import time
一行导入:
import os,time 或者 import os as o,time as t
规范建议:模块应该一个一个的导入,顺序为:内置模块、扩展(第三方)模块、自定义模块,如下格式:
import os import django import my_module
8、如何使用from import
from my_module import read2
read2()
结论:import谁就只能用谁。
9、当前文件命名空间和模块的命名空间的问题,有如下代码,分析结果:
from my_module import read1 # 此时执行my_module模块 def read1(): print('in my read1') read1() from my_module import read2 # 此时不再执行my_module模块 read2() # 结果为: # 12345 # 54321 # in my read1 # in read1 func alex 分析:from import导入的过程: # 1.找到要被导入的模块 # 2.判断这个模块是否被导入过 # 3.如果这个模块没被导入过 # 创建一个属于这个模块的命名空间 # 执行这个文件 # 找到你要导入的变量 # 给你要导入的变量创建一个引用,指向要导入的变量
10、导入多个名字、给导入的名字起别名
from my_module import read1 as r1,read2 as r2 def read1(): print('in my read1') r1() # 模块中的read1 r2() # 模块中的read2 read1() # 本文件中的read1
11、from my_module import *(代表导入这个模块中的所有名字),使用如下:
name = 'egon' print(name) # 结果为:egon,模块中的name被本文件的 name='egon'所覆盖 read1 # 模块中的read1 read2 # 模块中的read2
注意:*和__all__的关系:*会参考__all__中的变量,如果没有__all__变量,则导入所有名字,当有__all__变量时,则__all__能够约束*导入的变量的内容,且*里边变量只能是字符串,如下示例:
__all__ = [‘name’]
三、模块引用中的情况
1、模块的循环引用(***)
模块之间不能形成循环引用,否则就会报错。
2、模块的加载与修改(*)
已经被导入的模块发生了修改,是不会被正在运行的程序感知到的,可以用importlib.reload(my_module)重新加载这个模块被感知,但是一般不用,知道即可;
所以要想修改的模块被正在运行中的程序感知到,可以重启这个程序,而不是重新加载这个模块;
3、把模块当成脚本执行(*****)
首先要知道,同一个py文件,直接运行这个文件,这个文件就是一个脚本,而导入这个文件,这个文件就是一个模块。
其次,当一个py文件被当做脚本执行时,它的__name__ ='__main__';当一个模块被调用时,它的__name__ ='模块的名字'。
最后,当一个py文件被当做一个脚本的时候,能够独立地提供一个功能,能自主完成交互;当成一个模块的时候,能够被导入者调用这个功能,不能自主交互。
所以,可以按照如下这样写:
# if __name__ == '__main__': # 代码 # 写在这里面的代码只有这个文件被当做脚本执行的时候才执行
4、模块的搜索路径(*****)
被当做脚本执行的文件 的 同目录下的模块,可以被直接导入,除此之外其他路径下的模块,在被导入的时候需要自己修改sys.path列表。
四、包
什么是包:文件夹中有一个__init__.py文件,是几个模块的集合。如下示例是一个包的目录结构:
# glance/ # ├── __init__.py # ├── api # │ ├── __init__.py # │ ├── policy.py # │ └── versions.py # ├── cmd # │ ├── __init__.py # │ └── manage.py # └── db # ├── __init__.py # └── models.py
1、从包中导入模块(精确到模块,不需要设计__init__.py文件)(必须掌握,很简单)
# 1.1 import方式 import glance.api.policy # 导入模块 glance.api.policy.get() # 使用模块中的方法 import glance.api.policy as policy # 导入模块,并给模块起别名 policy.get() # 使用模块中的方法 # 1.2 form import方式 from glance.api import policy policy.get() from glance.api.policy import get get()
总结:1)从包中引入模块的上述两种方式,无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带一连串的点,但都必须遵循这个原则。
2)from import方式中,import后边必须不能带点,否则有语法错误。
2、导入包
直接导入包:import glance
但是导入一个包并不意味包下面的所有内容都是可以被使用的,即直接导入包什么都干不了,那么导入一个包到底发生了什么?导入一个包相当于执行了这个包下面的__init__.py文件。
所以直接导入包时,我们要通过设计__init__.py文件,来完成导入包之后的操作,这时又分为下面两种方式;绝对导入和相对导入:
2.1 绝对导入
# glance/ # ├── __init__.py (from glance import api) (from glance import cmd) (from glance import db) # ├── api # │ ├── __init__.py (from glance.api import policy) (from glance.api import version) # │ ├── policy.py # │ └── versions.py # ├── cmd # │ ├── __init__.py (from glance.cmd import manage) # │ └── manage.py # └── db # ├── __init__.py (from glance.db import models) # └── models.py
绝对导入的缺点:1)所有的导入都要从一个根目录下往后解释文件夹之间的关系;
2)如果当前导入包的文件和被导入的包的位置关系发生了变化,那么所有的init文件都要做相应的调整;
2.2 相对导入
# glance/ # ├── __init__.py (from . import api) (from . import cmd) (from . import db) # ├── api # │ ├── __init__.py (from . import policy) (from . import version) # │ ├── policy.py # │ └── versions.py # ├── cmd # │ ├── __init__.py (from . import manage) # │ └── manage.py (from ..api import policy) # └── db # ├── __init__.py (from . import models) # └── models.py
相对导入的优点:1)不需要反复去修改路径,只要一个包中的所有文件夹和文件的相对位置不发生改变;
2)也不需要关心当前这个包和被执行的文件之间的层级关系;
注意:含有相对导入的py文件不能被直接执行,且必须放在包中被导入的调用才能正常的使用。所以,在执行一个py脚本的时候,这个脚本以及和这个脚本同级的模块中只能用绝对导入。
我们再看下面一个现象:
import urllib urllib.request() # 报错:AttributeError: module 'urllib' has no attribute 'request' # from urllib import request # print(request) # <module 'urllib.request' from 'C:\Python36\lib\urllib\request.py'> 分析:urllib是一个包,单纯的导入一个包啥也不会发生,包中的request.py也不能用。
总结:如果只是从包中导入模块的话,那么我们不需要做任何多余的操作,直接导入就行了。如果我们希望导入包的时候,能够顺便把模块也导入进来,就需要设计_init__文件,这时有上述绝对目录的设计和相对目录的设计,而且两种方式各有千秋。
五、项目开发规范
# myProject/ # ├── __init__.py # ├── bin (当前项目的启动脚本在这里) # │ ├── __init__.py # │ ├── start.py # ├── core (核心代码都在这里) # │ ├── __init__.py # │ └── main.py # └── db # ├── __init__.py # └── userInfo.json # └── lib(不是内置和第三方模块,可能是自己写的,较完善且跟此程序相关性小) # ├── __init__.py # └── models.py # └── conf(配置文件,多处用到某个值以后可能会被修改,则写到配置文件中) # ├── __init__.py # └── config.py # └── log(日志文件,供用户查看追责或者公司分析数据) # ├── __init__.py # └── all.log