21----模块的使用

一 模块介绍

1 什么是模块

模块就是一系列功能的集合体
  Ⅰ 内置的模块
  Ⅱ 第三方的模块
  Ⅲ 自定义的模块
  一个python文件本身就是一个模块,文件名m.py,模块名叫m

2 为什么要用模块

Ⅰ 内置与第三方的模块拿来就用,无需定义,这种拿来主义,可以极大的提升开发效率
Ⅱ 自定义的模块:可以将程序中的各部分功能提前出来放到一个模块中为大家共享使用,好处是减少了代码冗余,程序组织结构更加清晰

二 import 导入自定义模块

import foo

# 如何引用模块名称空间中的名字?---需要在模块名称空间中的名字加上前缀,即模块的名字+.
a = foo.x
print(a)
print(foo.get)
foo.change()
print(foo.x)
首次导入模块发生的事情
1 执行模块文件中的源代码
2 产生新的名称空间用于存放模块文件执行过程中产生的名字
3 在执行文件所在的名称空间中得到模块文件的名字,该名字指向模块文件的名称空间
注意:只有第一次导入模块才会执行模块代码

三 from...import...导入自定义模块

# 从foo中导入需要使用的名字,但是此时从模块中导入的名字已经是执行文件中的全局名称,所以可能会出现名字混淆的状况
from foo import x
from foo import get
from foo import change

print(x)  # 此时输出的是foo文件中x对应的内存地址111的值
change()  # 执行change函数,将全局变量x与111的内存地址解绑,然后与值0的内存地址绑定
print(x)  # 此时执行文件中全局变量x指向的内存地址还是111的内存地址


from foo import x   # 重新导入模块,全局变量的x指向的内存地址已经是0的内存地址
print(x)  # 此时执行文件中全局变量x指向的内存地址也是0的内存地址
from ... import导入也发现三件事
1 产生模块的内存空间
2 运行模块文件将运行过程中产生的名字都丢到模块的名称空间去
3 在当前名称空间拿到一个名字,该名字指向模块名称空间中的某一个内存地址

四 两种导入方式对比

import 导入模块在使用时必须加前缀'模块.'
优点:肯定不会与当前名称空间的名字冲突
缺点:加前缀显得麻烦
from...import...
优点:可以不用加前缀,代码更精简
缺点:容易与当前名称空间的名字混淆

五 循环导入

循环导入问题指的是在一个模块加载/导入的过程中导入另外一个模块,而在另外一个模块中又返回来导入第一个模块中的名字,由于第一个模块尚未加载完毕,所以引用失败、抛出异常

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()

注意:循环导入问题大多数情况是因为程序设计失误导致,上述解决方案也只是在烂设计之上的无奈之举,在我们的程序中应该尽量避免出现循环/嵌套导入,如果多个模块确实都需要共享某些数据,可以将共享的数据集中存放到某一个地方,然后进行导入

六 一个py文件的两种用途

一个python文件有两种用途
    1 被当作程序/脚本运行
    2 被当作模块导入

为了区别同一个文件的不同用途,每个py文件都内置了__name__变量,该变量在py文件被当做脚本执行时赋值为“__main__”,在py文件被当做模块导入时赋值为模块名

作为模块foo.py的开发者,可以在文件末尾基于__name__在不同应用场景下值的不同来控制文件执行不同的逻辑

def func():
    print('我是func')

if __name__ == '__main__':
    func()

七 模块查找优先级

无论是import 还是from...import...导入模块都涉及到查找模块路径的问题

模块查找的优先级
1 内存
2 硬盘:按照sys.path存放的文件夹的顺序依次查找
    Ⅰ第一个文件夹就是执行文件所在的文件夹
    Ⅱsys.path存放的文件夹的顺序以解释器为准,不要以pycharm为准
    
sys.path中的第一个路径通常为空,代表执行文件所在的路径,所以在被导入模块与执行文件在同一目录下时肯定是可以正常导入的,而针对被导入的模块与执行文件在不同路径下的情况,为了确保模块对应的源文件仍可以被找到,需要将源文件foo.py所在的路径添加到sys.path中,假设foo.py所在的路径为/pythoner/projects/
import sys
sys.path.append(r'/pythoner/projects/') #也可以使用sys.path.insert(……)

import foo #无论foo.py在何处,我们都可以导入它了
posted @ 2020-03-29 13:48  微信搜索-程序媛小庄  阅读(156)  评论(0编辑  收藏  举报