python学习笔记DAY08(模块_概念)
这是我个人的学习笔记,都赖于(egon老师)的分享,以下是老师博客的原地址:
https://www.cnblogs.com/xiaoyuanqujing/articles/11640888.html
python模块
一、模块基础知识
- 模块就是一系列功能的集合体。
- 内置的模块
- 第三方模块
- 自定义模块
- 定义补充:
- 一个python文件本身就是一个模块,文件名moo.py。它的模块名就叫 moo
- 模块分为四种形式
- 使用python编写的.py文件(自定义)
- 已经被编译为共享库或DLL的C或者C++扩展(第三方)
- 把一系列模块组织到一起的文件夹(自定义)文件夹下有__init__.py文件,文件夹称为“包”
- 使用C编写并连接到python解释器(内置模块)
-
特点
- 内置模块和第三方模块,拿来就用。无需定义,这种拿来主义,可以极大的提升自己的开发效率
- 自定义模块,可以将程序的各部分功能提取出来放到一个模块中供其它功能引用,减少冗余,结构清晰
-
import导入、模块. 引用
- 导入:import moo (import 加上模块名就可以将对应模块引入) import之后发生的事情:
- 执行moo.py
- 产生moo.py的名称空间,将moo.py运行过程中产生的名字丢到它自己的名称空间。(运行文件本身也是这样的)
- 当前文件的名称空间内产生一个moo的名字,指向的是moo自己的名称空间。
- 引用:模块名.具体功能名,指名道姓的与向一个模块取对应的值,不会与当前文件发生冲突。
- import导入,优点是肯定不会与当前名称空间发生冲突;缺点是,每次引用都需要加“模块.”的前缀,太麻烦了。
- 导入:import moo (import 加上模块名就可以将对应模块引入) import之后发生的事情:
-
导入规范(以下为约定的导入顺序)
- python内置模块
- 第三方模块
- 自定义模块
-
模块别名
- import my_family_mather_cang as cang (当模块名过长时 用as给模块起 别名)
-
了解知识
- 模块是第一类对象,可以被赋值,可以当参数,可以当返回值,可以当元素
- 自定义模块命名方式,纯小写加下划线
- 模块也能在函数内进行导入操作
-
一个python文件有几种用途
- 当做程序运行
- 当做模块导入
两者的区别:当做程序运行时,内存空间是代码运行完毕就会被回收。而当做模块使用时,内存空间会在所有引用它的文件代码全部运行完,才会被回收。
-
python中的__name__功能
- 当一个模块被调用时,如果模块自身就有函数调用等代码,那么按照模块调用就会运行的逻辑,导入时就会直接调用模块内部的函数。为了避免这种事情发生,python中提供了__name__功能。
- 在模块中运行:print(__name__) 结果是:__main__
- 在运行调用文件时候,结果是:被调用的模块名,foo
- 所以,通过__name__的结果,我们可以很清楚的看出,现在它是在被当成模块调用,还是当成文件在运行。
在模块本身增加代码如下:
if __name__ == '__main__': # 当条件成立时,说明是自身文件在运行,就可以继续调用功能 f1() f2() f3() else: # 条件不成立,说明是被当成模块调用,就什么都不做 pass
这样就解决了
from...import...
from...import...方式的导入也发生了三件事:
- 产生一个模块的名称空间
- 运行模块,将运行过程中产生的名字,都丢到模块名称空间。
- 在当前名称空间拿到一个名字,该名字指向模块中的某一个功能具体的地址(而import指向的是整个模块的内存地址)
引用:from...import...方式的导入,引用时不用加前缀。
**优点:**代码更加精确 **缺点:**容易与当前名称空间混淆(如果模块中有 x 这个变量名,而运行文件在导入 x 这个功能之后,又另外定义了x,那后面的就会覆盖掉前面的那一个)
from 模块名 import * :表示导入模块下的所有功能;(但是本来这个方式就容易与当前运行文件的名字混淆,如果直接用*就会导致更加混乱)所以, import * 一般都与--all--一起使用:
z在被导入模块增加代码:__all__ = ["x","f1"] 就表示,引用者的星号只能引用到"x"和"f1"这两个功能。
-
循环引用(两个文件作为模块互为引用)
在程序设计过程中,切记不要出现循环引用的问题。但是如果已经出现了,要知道可以解决问题的办法:
- 引用代码写在所有功能代码的最下方。
- 如果只是某一个模块需要引用,就把引用代码写在函数内部
- 导入模块的查找(无论是import还是from...import...都涉及到查找问题)
查找的优先级:
- 内存:内置模块,或者已经加载到内存的模块
- 硬盘:按照sys.path中存放的文件顺序,依次查找要导入的模块。
sys.path:环境变量,存放一系列路径,第一个值是当前运行文件所在的文件夹路径
import sys :其值作为一个列表存放了一系列的文件夹
sys.modules:可以查看已经加载到内存中的所有模块(是一个字典)
- 被引用的模块在其他文件夹下:也就是在内存中无法找到,在sys中找,因为位置没有在当前文件夹下,所以也无法找到。这时候就需要把要引用模块的路径添加到sys中:(sys.path.append(r"G:\NIDA\pythonstudent0.0\test\foo.py "))。然后继续导入就没有问题了。
- 模块定义规范
我们编写代码不仅要自己用,也需要让别人看的清楚,所以可读性和易维护性非常重要,所以编写一个模块时,最好按照统一规范:
#!/usr/bin/env python #通常只在类unix环境有效,作用是可以使用脚本名来执行,而无需直接调用解释器。
"The module is used to..." #模块的文档描述
import sys #导入模块
x=1 #定义全局变量,如果非必须,则最好使用局部变量,这样可以提高代码的易维护性,并且可以节省内存提高性能
class Foo: #定义类,并写好类的注释
'Class Foo is used to...'
pass
def test(): #定义函数,并写好函数的注释
'Function test is used to…'
pass
if __name__ == '__main__': #主程序
test() #在被当做脚本执行时,执行此处的代码
二、包
随着模块增多,我们必须把模块集中在一个位置,便于管理和使用。把所有模块放入一个带有init文件的文件夹下,这个文件夹就是包。
- 包就是一个包含有__init__.py 文件的文件夹。(包的本质就是模块的一种形式,用来被当做模块进行导入)
包的使用:
- 导入包与init
包和包内的模块都是用来被导入使用的,而不是被执行,首次导入包同样会做三件事:
- 执行包内的 init.py 文件
- 产生一个新的名称空间用于存放 init.py 执行过程中产生的名字
- 在执行当前文件所在的名称空间得到一个名字 boo(包名),该名字指向 init.py 的名称空间,比如:boo.x 和 boo.y 都是来自于boo包下的init.py 文件,也就是说导入包时,并不会导入包下所有的子模块与子包。
综上所述:如果想用到包内的所有模块,就必须要在init文件中,可直接调用到所有模块。方式如下:
- 绝对导入和相对导入
绝对导入:以包的文件头作为起始来进行导入。(无论从哪个位置导包都是从最外层包作为起始一层层往下找,一直找到最后一个包)
# boo包 中的 init 文件如下:
from boo.m1 import f1 # 虽然导入的代码写了init文件中,但是导包的查找顺序依然是按照运行文件的sys进行查找的
from boo.m1 import f2 # 导入 boo 包的 m1 文件 ,使用 f2 的功能
from boo.m1 import f3 # 切记,前提是在运行文件的 sys 中已经添加过了 boo 的文件夹路径。
相对导入:. 代表当前文件夹,.. 代表上一层文件夹,以此类推。(每多一个点,就往上找一层)但是:最终这个点的数量不能超过作为包被引用的这个文件夹之外。
所以:相对导入,仅限于包内使用,不能跨出包
总结:包内模块之间导入,推荐使用相对导入,代码简介清晰。绝对导入是一种通用的导入方式,没有任何限制。
强调:
-
无论是import还是from ... import ...导入,无论在什么位置,在导入时都必须遵守一个原则:凡是带 . 的,点的左侧必须是个包。如果包下方有同名模块也不会有冲突,如A.a与B.a来自俩个命名空间。(无论导入时,有多少层,使用了多少个点,点的左侧都必须是包。)
备注:导入之后,使用时没有这个限制,点的左边可以是任意类型。
-
import导入文件时,产生名称空间的名字来源于文件,import包,产生的名称空间的名字也是来源于文件,就是包下的init.py,导入包的本质就是在导入该文件。
三、软件开发目录规范
为了提高程序的可读性与可维护性,应该设计良好的目录结构,假设软件名称为:Foo 目录如下
Foo/ # 软件名称
|-- core/ # 存放核心业务逻辑相关代码
| |-- core.py
|
|-- api/ # 存放接口文件,接口主要用于为业务逻辑提供数据操作。
| |-- api.py
|
|-- db/ # 存放操作数据库相关文件,主要用于与数据库交互
| |-- db_handle.py
|
|-- lib/ # 存放程序中常用的自定义模块
| |-- common.py
|
|-- conf/ # 存放配置文件
| |-- settings.py
|
|-- run.py # 程序启动文件,一般存放在项目的根目录下,因为运行时默认会将运行文件所在文件夹作为sys.path的
# 第一个路径这样就省了处理环境变量的步骤
|-- setup.py # 安装 部署 打包的脚本
|-- requirements.txt # 存放软件依赖的外部python包列表
|-- README # 项目说明文件
关于README的内容需要有以下几个:
- 软件定位,软件的基本功能
- 运行代码的方法:安装环境,启动命令等;
- 简要的使用说明;
- 代码目录结构说明,更详细点可以说明软件的基本原理
- 常见问题说明
补充:之前我们append到sys.path中的地址是固定的,但凡换一个环境,这些导入的文件都会出错。所以我们需要实时获取到正确的地址,然后再append到sys.path中。
- 在运行文件中有一个 __file__功能,它的输出结果是,当前运行文件的绝对路径
- 模块os中,有一个 os.path.dirname(__file__) 功能,输出结果是括号内文件路径的上一层文件夹的绝对路径。
- 有了以上两个功能,无论在什么环境下,我们都可以获取到当前模块所在的绝对路径,把获取到的路径添加到sys.path中即可。