模块详解

模块详解

一、什么是模块

模块其实就是一系列功能的集合体。

一个简单的Py文件是一个模块,一系列py文件组成的文件夹也是一个模块(也成为包)。模块即可以执行,也可以被导入。模块分为以下几种:

  1. 内置模块。当解释器启动的时候就会加载的一些供我们使用的内置功能。
  2. 第三方模块。别人已经制作好上传的可供我们使用的模块。
  3. 自定义模块。自己制作好的模块。

Python本身是一个开源的语言,拥有极其丰富的第三方库,这也是Python语言发展越来越好的一个重要原因,我们在做开发的时候,很多功能都不需要我们自己制作,而是直接导入相关模块就可以轻而易举的达到要求,极大的提高了开发效率。

二、模块的导入

2.1 import

模块的使用就是在开头位置利用import关键字来导入模块,

# 以导入时间模块为例。
import time

在导入模块的时候,会发生三件事情。

  1. 被导入模块会执行。
  2. 会向内存申请名称空间,凡是运行该模块产生的变量名等等都会存储在里面。
  3. 在当前名称空间中会产生一个time的变量名,指向被导入模块的名称空间。

所以,即使被导入模块和当前模块有同样变量名的存在,他们之间也不会有任何联系,因为这两者就不是处于同一块内存空间。

我们使用被导入模块的方法或者变量名的时候都是使用模块名.变量名的方式。

import time

# 模块名.方法名()。变量名也是这样的调用方式。
time.time()


在导入模块的时候,一般我们都是按照:内置模块、第三方模块、自定义模块的顺序导入的,当然你也可以不按照这个顺序,但是按规矩来会提高代码的可阅读性。同时虽然你也可以在一行通过用逗号导入多个模块,但是也不推荐这么做。

我们还可以使用as给模块起一个别名。

# 起别名:建议别名应该采用纯小写+下划线风格。
import time as t

2.2 from..import

我们在使用import的时候,在调用时要通过模块名.名字的方式调用的,有时候会觉得比较麻烦,这个时候可以使用from..import的方法导入模块,这样在全局中使用被导入模块就可以不使用前缀而直接调用了。

# from 模块名 import 名称

因为调用方式的不同,我们就可以猜出来它发生的情况应该也是不太一样的。from..import会发生这样的事情。

  1. 产生一个模块的名称空间
  2. 运行模块,并将产生的名称都放在名称空间中。
  3. 在当前模块创造与模块空间内部名称相同的名字,且都指向同一个内存地址。

也就是说前两步都相同,主要是第三步不一样,import语法是通过当前模块的一个名称去寻找其内部名称空间的名称继而找到值得内存地址。而from..import则是将当前模块的变量名直接指向被导入模块内存空间变量名的值的内存地址,因此,可以通过调用当前模块的名称而直接使用被导入模块内部的值。

当然他的优点缺点也都很明显。

  1. 优点:代码简洁,方便调用,不用加前缀。
  2. 缺点:容易与当前模块内部名称混淆。
# from..import可以导入单个变量或语法。

from foo import x  # 导入单个变量
from foo import login  # 导入单个方法
from foo import x,login  # 不推荐
from foo import *  # 全部导入

趁热打铁,看看是否真正明白了from..import的运行。

# 假设下方是m1的内容。
from m2 import *
x = "m1"
get()

# 下方是m2的内容。
x = "m2"

def get():
    print(x)

def change():
    global x
    x= "m2***2"
    

这个时候我们执行m1的内容,你觉得get()打印的什么呢?运行结果是"m2"。因为get是当前模块的get,指向的是m2中get()内部函数体的内存地址,因此调用的时候就会根据内存地址找m2中的get方法,之前我们学过名称的查找顺序是在定义的时候就确定好的,因此,找到的结果是m2。

# 下方是m1的内容
from m2 import *
get()  		# m2  如上图所示找到m2
change()    # 改变了m2中的x值
get()  		# m2***2  同第一个get,因为chang(),所以找到的结果是m2
print(x)  	# m2  下方介绍

前三步分析过了,按理说得到的结果应该是m2***2才对,因为在change那一步已经改成这个值了,但是还记得上方说过的from..import的运行方式吗?

也就是说其实当前打印的x是当前模块的变量名,当初导入的时候创造的新的x,其指向的是m2中变量名x 对应的值的内存地址.

也就是实际上他们只是同一个值上的不同标签,当我们改变了m2中x的值,并不影响m1模块中x与"m2"的绑定关系。因此,打印的还是“m2”.

总而言之,记住一点:当前模块的变量名直接指向被导入模块内存空间变量名的值的内存地址。名字一样,指的也都是内存地址。

2.3 模块的搜索路径

上述两种方法都是导入模块的,采取哪种看情况分析,但是他们都涉及到在执行该导入文件的时候,是怎么查找到该模块的。这就要讲一下查找顺序优先级。

  1. 内存(内置模块)。只要在内存中已经加载过该模块,那么就会直接使用该模块。
  2. 硬盘。按照sys.path中存放的文件路径依次查找,直到找到为止。找不到就报错。

sys.path的第一个值就是当前执行文件的路径,因此,我们平常调用的时候因为是在同一个平级目录,所以能够找到。

我们可以使用sys.modules()来查看在内存中运行的模块,这里要讲一个冷知识。

import sys
import foo # foo=模块的内存地址
del foo  # 理论上将foo这个变量名删除,然后foo的内存空间会被释放。
print('foo' in sys.modules)  # 结果是True   ?
print(sys.modules)  # foo存在    ?

我们之前说过,模块在导入的时候会申请名称空间,然后执行,如果在当前模块没有任何变量去引用这个模块的时候,该名称空间被引用次数就为0,按理说这时候应该是根据垃圾回收机制对其进行清除回收,但是实际上情况并不是这样,Python内部的机制做了部分优化,也就是说Python认为你导入模块不是用完就删除的,就为其做了保留。当然,这肯定是利大于弊的。

2.4 模块的相互导入

据egon老师说,如果发生这样的事情,说明这首先就是极度的不合理,是一坨**。但是有时候可能写着写着就碰到这种情况了,因此,也就讲了讲,首先我们逐步分析。

# 这是m1模块的代码。
1. print("这是m1")
2. from m2 import y

3. x = "m1"
# 这是m2模块的代码。
1. print("这是m2")
2. from m1 import x

3. y= "m2"

假设这两个代码位于同一层目录,如果此时我们运行m1的话,会发生什么状况呢?

# 运行结果
1. 这是m1
2. 这是m2
3. 这是m1
ImportError: cannot import name 'y' from 'm2'

运行结果报错,错误说是导入错误,在m2里没找到y。这里你要知道import导入模块会发生什么,还有就是模块首次被导入会执行,

我们进行分析流程

  1. 首先运行m1,从上往下运行打印第一行,输出屏上显示“这是m1”
  2. 第二句开始导入m2模块,申请名称空间,执行模块,打印m2的第一行“这是m2”
  3. 程序运行到m2的第二行,开始导入m1模块,我们一开始执行m1模块并不是等于它就被导入了,所以它会申请名称空间然后运行等等。
  4. 执行m1,打印第一行的print“这是m1”
  5. 这时候又运行到了导入m2中的y,但是此时m2已经被导入执行过了,于是在其名称空间找y,但是还没定义,于是就报错了。

老师说这种程序本身就很辣鸡,但是有时候会碰到不得不用的时候,因此提供了两种”便上雕花“的方式

  1. 将导入语句放在最后,这样保证所有的名称空间都加载进去。
  2. 将导入语句放到函数内部,只有在函数运行的时候才执行语句。

2.5 区分文件的用途

我们知道,py文件既可以当做模块导入,也可以执行,如何针对不同的用途执行不同的代码呢?这里用到了一个神奇的内置方法。

# __name__的用法

if __name__ = "__main__":
	print("当执行而非导入的时候会执行的代码。")
else:
    print("当文件被导入的时候执行的代码")

当运行这个文件的时候,内置的name就会默认等于“_main_”,如果是被导入的话,则是等于1。

这是一个装逼利器,简单方便又美观大方,推荐指数:满星。

原文地址:https://www.cnblogs.com/liqianxin/p/12584399.html