一. 模块介绍
1. 什么是模块?
'''
模块就是一系列功能的集合体。且分为三大类:
第一个是内置模块: C语言编写的模块。
第二个是第三方的模块。
第三个是自定义的模块: 可以是Python代码写的,也可以是c语言写的。也可以是C++写的, 都可以。
强调: 一个python文件本身就是一个模块。文件名叫m.py. 模块名叫m
补充: 模块分为四种形式
1. 使用Python代码编写的.py文件。
2. 把一系列模块组织到一起的文件夹。(注意: 文件夹下有一个__init__.py文件,该文件夹称之为包)
3. 以被编译为共享库或DDL的C或C++扩展。
4. 使用c编写并链接到Python解释器中的内置模块。
'''
2. 为何要用模块?
"""
第一点好处:
内置与第三方的模块拿来就用,无需定义, 这种拿来主义。可以极大地提升自己的开发效率。
可以将程序的常用功能提取出来放到一个模块中为大家共享使用。从而减少了代码冗余, 让程序组织结构更加清晰。
"""
二. 模块的使用
1.import语句
# 1. 首次导入模块
"""
首次导入模块会发生什么?
1. 执行被导入模块对应的文件
2. 产生一个新的名称空间用于存放被导模块文件执行过程中产生的名字。
3. 在当前执行文件中产生以被导入模块名为命名的名字,该名字指向步骤2中产生的名称空间。(注意: import指向的是名称空间中的名字)
# 注意: 首次导入之后的导入都是直接引用首次导入的文件且名称空间不会重复执行代码。
import foo
import foo
import foo
import foo
import foo
"""
# 2. 引用模块: 如何引用?
"""
强调一: 指名道姓地问某一个模块要名字对应的值,不会与当前名称空间的名字发生冲突。
print(foo.x)
print(foo.get)
print(foo.change)
强调二: 无论是查看还是修改操作的都是模块本身, 与调用位置无关。(提示: 名称空间的嵌套关系是在定义阶段就确定的。)
import foo
x = 33333333
foo.get()
print(x)
foo.change()
print(x)
"""
# 3. 导入模块的两种书写风格
"""
建议如下所示导入多个模块:
import time
import os
import foo
不建议在一行同时导入多个模块:
import time, os, foo
"""
# 4. 导入模块的规范
"""
先写: Python内置模块。
再写: 第三方模块。
最后写: 程序员自定义模块。
import time
import os
import 第三方1
import 第三方2
import 自定义模块1
import 自定义模块2
import 自定义模块3
"""
# 5. 为导入的模块起别名
"""
提示: as一般用于你的模块名比较长的情况下。
import abcdefghijkmnopqrstuvwxyz as super_man
super_man.f1
super_man.f2
super_man.f3
"""
# 6. 与函数对象一样,模块也是第一类对象。模块可以赋值, 模块可以当做参数, 可以当做返回值, 模块可以充当容器类型的元素。
# 7. 模块名的书写规范: 自定义的模块名应该采用纯小写加下划线的风格。
# 8. 可以在函数内导入模块
def func():
import foo
2.一个py文件有几种用途
# 1. 一个python文件有两种用途:
"""
1. 被当做主程序或者说脚本执行。
2. 被当做模块导入。
"""
# 2. 执行py文件与导入py文件的区别是什么?
"""
执行py文件: 程序执行结束后名称空间被回收,
导入py文件: 当不再被引用时名称空间被回收(提示: python中对于模块级别的引用做了优化, 如果是模块级别的名称空间在没有被引用的情况下, 并不久立刻回收)
"""
# 3. 引入__name__:
"""
为了区分同一个文件的不同用途, 每个py文件都内置了__name__变量, 该变量在py文件被当做脚本执行时赋值为'__main__', 在被当做模块导入时赋值为模块名.
__name__作用:
1. 当作项目的执行文件入口时使用. 声明该文件是被执行的, 不是被导入的.
2. 测试该文件的功能的时候使用.
"""
# 伪代码示例:
if __name__ == '__main__':
被当做脚本运行时的代码
else:
被当做模块导入时运行的代码
3.from-import语句
# 1. 由import引入from...import...
"""
import导入模块在使用时,必须加前缀: "模块.". 好处是不会与当前名称空间中的名字冲突。但是缺点就是加前缀会显得麻烦。因此, 我们来了解一下from import语句。
"""
# 2. from import导入也发生了三件事。
"""
1. 执行被导入模块对应的文件
2. 产生一个新的名词空间用于存放被导入模块所对应的文件执行过程中产生的名字.
3. 在当前名称空间中拿到一个以被导入模块命名的名字, 该名字指向步骤2中产生的名称空间中某一个值的内存地址(注意: from import指向的是名字对应的内存地址)
"""
# 3. from import 导入模块在使用时不加前缀的优缺点:
"""
优点: 代码更精简。
缺点: 容易与当前名称空间混淆。
示例:
from foo import x # x=模块foo中值1的内存地址
x = 111 # x=当前执行文件中111的内存地址, 这个时候x被重新赋值, 覆盖掉了对被导入模块值1内存地址的引用
"""
# 4. 导入模块的两种书写风格
"""
# 不推荐一行导入多个
from foo import x, get, change
# 推荐如下所示导入多个
from foo import x
from foo import get
from foo import change
"""
# 5. 导入模块中的所有名字: *
"""
from foo import *
print(x)
print(get)
print(change)
"""
# 6. 使用__all__控制使用*导入名字的个数
"""
__all__默认就把所有的名字以列表的形式存放于__all__, 我们可以通过修改, 其里面的每个列表元素的值, 来达到*号控制读取名字的个数。
注意: __all__须放在所有定义的名字之前, 且该列表中所有元素必须是字符串类型, 每个字符串表示当前文件中定义的名字.
示例:
__all__ = ['x', 'get',] # 使用*导入这个模块以后, 只能访问和使用到名字x, get. (注意: __all__只控制*号方式的访问, 不控制指明道姓式的访问)
"""
# 7. 取别名: as
"""
# 注意: 一般用于名字过长的情况下
from qwertyuiopasdfghjklzxcvbnm as super_man
# 使用不推荐一行导入多个功能的方式的as示例(了解)
from aaa.foo import x as ok, get as ok1, change as ok2
print(ok)
print(ok1)
print(ok2)
"""
4.搜索模块的路径与优先级
# 无论是import 还是 from import,在导入模块时都涉及到模块查找问题。
# 模块查找优先级👇:
"""
1. 内存(内置模块)
2. 硬盘: 按照sys.path中存放的文件的顺序依次查找要导入的模块.
如果你这个sys.path所有的文件夹下没有你当前导入模块的文件,那么你可以往sys.path中添加你要导入的模块对于的文件所在的文件夹路径。(注意: 是文件夹)
添加方式: sys.path.append(r'路径')
如果路径是在windows中一定要在路径前加一个小r. 这里之所以用append,不用insert。因为默认的路径是要到你当前执行文件的文件夹下查找而不是应该用你去指定文件夹下去查找, 我们要准守这种规范。
sys.path介绍:
存放的是包含文件夹路径的列表, 列表中每个元素都包含了一个文件夹的路径地址。(补充: .zip压缩包也是一个文件夹)
其中列表的第一个元素。包含的路径是当前执行文件的文件夹的路径。(重要)
其中列表的第二个元素。包含的路径是项目文件夹的路径。(在终端中没有这个值, 在pycharm运行的时候会帮你加这个路径。所以我们当它不存在。)
注意: 模块的导入查找方式是以当前执行文件的文件夹为基准. sys.path包含的路径文件夹列表中, 第一个元素就是当前执行文件的文件夹.
"""
# 1. 验证: 优先查找的是内存
"""
执行步骤: 把被导入模块对应的文件删除, 这个时候硬盘中已经没有对应的文件, 使用time.sleep休息10秒, 内存中还存在, 再次import导入, 发现仍然能调用执行
"""
import foo
print(foo.x)
import time
time.sleep(10) # 在sleep时, 删除foo模块所对应的文件, 发现下面代码任然能执行
import foo
print(foo.x)
# 2. sys.modules查看已经加载到内存中的模块
import sys
import foo
# 注意: 我们所知,引用关系归零以后,这个名称就会被GC回收,但是对于模块级别的名称空间。python的底层做了优化机制。模块级别的名称空间的申请需要消耗的内存是比较大的,所以我们使用del和函数局部的调用结束并不会删除对foo模块的引用关系。所以通过sys.modules访问时,任然能访问到foo这个名称空间。
del foo
print(sys.modules)
print("".center(200, '#'))
def func():
import foo
func()
print("".center(200, '#'))
print(sys.modules)
import foo
print("".center(200, '#'))
print(sys.modules)
5.循环导入问题
"""
# 什么是循环导入问题?
循环导入问题指的是在一个模块加载/导入的过程中导入另外一个模块(m1.py),而在另外一个模块(m2.py)中又返回来导入第一个模块(m1.py)中的名字,由于第一个模块(m1.py)尚未加载完毕,所以引用失败、从而抛出循环导入的异常.
# 循环导入问题的真正原因:
究其根源就是在python中,同一个模块只会在第一次导入时执行其内部代码,再次导入该模块时,即便是该模块尚未完全加载完毕也不会去重复执行内部代码
"""
- 以下面m1.py, m2.py, run.py文件为例, 来详细分析循环/嵌套导入出现异常的原因以及解决的方案
# m1.py文件中内容
"""
print('正在导入m1')
from m2 import y
x = 'm1'
"""
# m2.py文件中内容
"""
print('正在导入m2')
from m1 import x
y = 'm2'
"""
# run.py文件中内容
"""
import m1
"""
# 1、执行run.py会抛出异常
正在导入m1
正在导入m2
Traceback (most recent call last):
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/aa.py", line 1, in <module>
import m1
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in <module>
from m2 import y
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m2.py", line 2, in <module>
from m1 import x
ImportError: cannot import name 'x'
# 2、分析
"""
先执行run.py--->执行import m1,开始导入m1并运行其内部代码--->打印内容"正在导入m1"
--->执行from m2 import y 开始导入m2并运行其内部代码--->打印内容“正在导入m2”--->执行from m1 import x,由于m1已经被导入过了,所以不会重新导入,所以直接去m1中拿x,然而x此时并没有存在于m1中,所以报错
"""
# 1、执行文件不等于导入文件,比如执行m1.py不等于导入了m1
直接执行m1.py抛出异常
正在导入m1
正在导入m2
正在导入m1
Traceback (most recent call last):
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in <module>
from m2 import y
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m2.py", line 2, in <module>
from m1 import x
File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in <module>
from m2 import y
ImportError: cannot import name 'y'
# 2、分析
"""
执行m1.py,打印“正在导入m1”,执行from m2 import y ,导入m2进而执行m2.py内部代码--->打印"正在导入m2",执行from m1 import x,此时m1是第一次被导入,执行m1.py并不等于导入了m1,于是开始导入m1并执行其内部代码--->打印"正在导入m1",执行from m1 import y,由于m1已经被导入过了,所以无需继续导入而直接问m2要y,然而y此时并没有存在于m2中所以报错
"""
# 方案一:导入语句放到最后,保证在导入时,所有名字都已经加载过
# 文件:m1.py
print('正在导入m1')
x='m1'
from m2 import y
# 文件:m2.py
print('正在导入m2')
y='m2'
from m1 import x
# 文件:run.py内容如下,执行该文件,可以正常使用
import m1
print(m1.x)
print(m1.y)
# 方案二:导入语句放到函数中,只有在调用函数时才会执行其内部代码
# 文件:m1.py
print('正在导入m1')
def f1():
from m2 import y
print(x,y)
x = 'm1'
# 文件:m2.py
print('正在导入m2')
def f2():
from m1 import x
print(x,y)
y = 'm2'
# 文件:run.py内容如下,执行该文件,可以正常使用
import m1
m1.f1()
# 注意: 循环导入问题大多数情况是因为程序设计失误导致,上述解决方案也只是在烂设计之上的无奈之举,在我们的程序中应该尽量避免出现循环/嵌套导入,如果多个模块确实都需要共享某些数据,可以将共享的数据集中存放到某一个地方,然后进行导入
6.编写一个规范的模块
"""
我们在编写py文件时,需要时刻提醒自己,该文件既是给自己用的,也有可能会被其他人使用,因而代码的可读性与易维护性显得十分重要,为此我们在编写一个模块时最好按照统一的规范去编写,如下
"""
# !/usr/bin/env python # 通常只在类unix环境有效,作用是可以使用脚本名来执行,而无需直接调用解释器。
# -*- coding: 与存入硬盘字符编码一致 -*-
"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() #在被当做脚本执行时,执行此处的代码