待手绾青丝

待手绾青丝

待手绾青丝

庭中三千梨花树,再无一朵入我心。 心中只你一朵,似在心中,不在心中,可望可念可想不可及。

109 文章数
2 评论数
来首音乐
光阴似箭
今日已经过去小时
这周已经过去
本月已经过去
今年已经过去个月

01_模块

待手绾青丝
2024-10-23 / 0 评论 / 49 阅读 / 0 点赞

模块

一、介绍

1、什么是模块?

模块就是一系列功能的集合体,分为三大类:

​ 1.内置模块

​ 使用C编写并连接到python解释器的内置模块

​ 2.第三方的模块

​ 已被编译为共享库或DLL的C或C++的扩展

​ 3.自定义的模块

​ 一个python文件本身就是一个模块,文件名m.py,模块名叫m

​ 把一系列模块组织到一起的文件夹(注:文件夹下有一个__init__.py文件,该文件称之为包)

2、为什么要用模块

1.内置和第三方模块可以拿来就用,无需定义,这种拿来主义,可以极大地提升自己的开发效率

2.自定义模块:可以将程序的各部分功能提取出来放到一模块中为大家共享使用

​ 好处:减少了代码冗余,程序组织结构更加清晰

二、import语句

1、写一个自定义模块

foo.py

x = 1
def get():
    print(x)
    
def change():
    global x
    x=0

2、首次导入模块运行的三件事

  1. 执行源文件代码

  2. 产生一个新的名称空间用于存放源文件执行过程中产生的名字

  3. 在当前执行文件所在的名称空间中得到一个名字foo,该名字指向新创建的模块名称空间,若要引用模块名称空间中的名字,需要加上该前缀,如下所示:

import foo  # 导入模块foo
a = foo.x  # 引用模块foo中变量x的值赋值给当前名称空间中的名字a
foo.get()  # 调用模块foo的get函数
foo.change()  # 调用模块foo中的change函数

​ 加上foo.作为前缀就相当于指名道姓地说明要引用foo名称空间中的名字,所以肯定不会与当前执行文件所在名称空间中的名字相冲突,并且若当前执行文件的名称空间中存在x,执行foo.get()或foo.change()操作的都是源文件中的全局变量x。

​ 需要强调一点是,第一次导入模块已经将其加载到内存空间了,之后的重复导入会直接引用内存中已存在的模块,不会重复执行文件,通过 import sys,打印sys.modules的值可以看到内存中已经加载的模块名。

3、模块也属于第一类对象

在Python中模块也属于第一类对象,可以进行赋值,以数据形式传递以及作为容器类型的元素等操作

4、模块的命名规范

模块名应该遵循小写形式,标准库从Python 2过渡到Python3做出了很多这类调整,比如ConfigParser、Queue、SocketServer全更新为纯小写形式。

5、导入多个模块方式

用import语句导入多个模块,可以写多行import语句

import module1
import module2
    ...
import moduleN

还可以在一行导入,用逗号分隔开不同的模块

import module1, module2, ..., moduleN

​ 但其实第一种形式更为规范,可读性更强,也是官方推荐使用的方式,而且我们导入的模块中可能包含有Python内置的模块、第三方的模块、自定义的模块,为了便于明显地区分它们,我们通常在文件的开头导入模块,并且分类导入,一类模块的导入与另外一类的导入用空行隔开,不同类别的导入顺序如下:

# 1、Python内置模块
# 2、第三方模块
# 3、自定义模块

当然,我们也可以在函数内导入模块,对比在文件开头导入模块属于全局作用域,在函数内导入的模块则属于局部的作用域。

三、from ··· import ··· 语句

from … import … 与 import 语句基本一致,唯一不同的是:使用 import foo 导入模块后,引用模块中的名字都需要加上foo.作为前缀,而使用from foo import x,get,change,Foo则可以在当前执行文件中直接引用模块foo中的名字,如下

from foo import x,get,change  # 将模块foo中的x、get、change导入到当前名称空间
a=x  # 直接使用模块foo中的x赋值给a
get()  # 直接执行foo中的get函数
change()  # 即便是当前有重名的x,修改的仍然是源文件中的x

1、优缺点

好处:使得我们的代码更加简洁,

坏处:容易与当前名称空间中的名字冲突,如果当前名称空间存在相同的名字,则后定义的名字会覆盖之前定义的名字。

2、导入模块中的所有名字

from foo import *
a=x
get()
change()
obj=Foo()

​ 如果我们需要引用模块中的名字过多的话,可以采用上述的导入形式来达到节省代码量的效果,但是需要强调的一点是:只能在模块最顶层使用的方式导入,在函数内则非法,并且的方式会带来一种副作用,即我们无法搞清楚究竟从源文件中导入了哪些名字到当前位置,这极有可能与当前位置的名字产生冲突。模块的编写者可以在自己的文件中定义*all变量用来控制代表的意思。

foo.py

__all__ = ['x', 'get']  # 该列表中所有的元素必须是字符串类型,每个元素对应foo.py中的一个名字
x = 1
def get():
    print(x)
    
def change():
    global x
    x=0
    
class Foo:
    def func(self):
       print('from the func')

这样我们在另外一个文件中使用导入时,就只能导入__all__定义的名字了

from foo import *  # 此时的*只代表x和get

x  # 可用
get()  # 可用
change()  # 不可用
Foo()  # 不可用

四、其他导入语法(as)

我们还可以在当前位置为导入的模块起一个别名

import foo as f # 为导入的模块foo在当前位置起别名f,以后再使用时就用这个别名f
f.x
f.get()

# 还可以为导入的一个名字起别名
from foo import get as get_x
get_x()

​ 通常在被导入的名字过长时采用起别名的方式来精简代码,另外为被导入的名字起别名可以很好地避免与当前名字发生冲突,还有很重要的一点就是:可以保持调用方式的一致性,例如我们有两个模块json和pickle同时实现了load方法,作用是从一个打开的文件中解析出结构化的数据,但解析的格式不同,可以用下述代码有选择性地加载不同的模块。

if data_format == 'json':
    import json as serialize  # 如果数据格式是json,那么导入json模块并命名为serialize
elif data_format == 'pickle':
    import pickle as serialize  # 如果数据格式是pickle,那么导入pickle模块并命名为serialize

data=serialize.load(fn)  # 最终调用的方式是一致的

五、循环导入问题

​ 循环导入问题指的是在一个模块加载/导入的过程中导入另外一个模块,而在另外一个模块中又返回来导入第一个模块中的名字,由于第一个模块尚未加载完毕,所以引用失败、抛出异常,究其根源就是在Python中,****同一个模块只会在第一次导入时执行其内部代码****,再次导入该模块时,即便是该模块尚未完全加载完毕也不会去重复执行内部代码。

1、情景再现

1.准备文件

m1.py

print('正在导入m1')
from m2 import y

x='m1'

m2.py

print('正在导入m2')
from m1 import x

y='m2'

run.py

import m1

2、执行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'

分析

先执行run.py*--->执行import m1,开始导入m1并运行其内部代码--->打印内容"正在导入m1"*

*--->执行from m2 import y 开始导入m2并运行其内部代码--->打印内容“正在导入m2”--->*执行from m1 import x,由于m1已经被导入过了,所以不会重新导入,所以直接去m1中拿x,然而x此时并没有存在于m1中,所以报错

3、执行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'

分析

执行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中所以报错

4、解决方案

1.导入语句放到最后

保证在导入时,所有名字都已经加载过

# 文件: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)

2.导入语句放到函数中

导入语句放到函数中,只有在调用函数时才会执行其内部代码

# 文件: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()

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

六、搜索模块的路径与优先级

1、模块的四大类别

  1. 使用纯Python代码编写的py文件

  2. 包含一系列模块的包

  3. 使用C编写并链接到Python解释器中的内置模块

  4. 使用C或C++编译的扩展模块

2、优先级

  1. 内存
  2. 硬盘:按照sys.path中存放的文件夹的顺序依次查找要导入的模块
import sys
print(sys.path)

# 结果
['C:\\Users\\xiaowu\\Desktop\\pythnon-blog\\python', 'C:\\Users\\xiaowu\\Desktop\\pythnon-blog', 'E:\\Pycharm\\PyCharm 2022.1.3\\plugins\\python\\helpers\\pycharm_display', 'C:\\Users\\xiaowu\\AppData\\Local\\Programs\\11_开发学习\\Python310\\python310.zip', 'C:\\Users\\xiaowu\\AppData\\Local\\Programs\\11_开发学习\\Python310\\DLLs', 'C:\\Users\\xiaowu\\AppData\\Local\\Programs\\11_开发学习\\Python310\\lib', 'C:\\Users\\xiaowu\\AppData\\Local\\Programs\\11_开发学习\\Python310', 'C:\\Users\\xiaowu\\AppData\\Local\\Programs\\11_开发学习\\Python310\\lib\\site-packages', 'E:\\Pycharm\\PyCharm 2022.1.3\\plugins\\python\\helpers\\pycharm_matplotlib_backend']

​ 列表中的每个元素其实都可以当作一个目录来看:在列表中会发现有.zip或.egg结尾的文件,二者是不同形式的压缩文件,事实上Python确实支持从一个压缩文件中导入模块,我们也只需要把它们都当成目录去看即可。

sys.path中的第一个路径通常为空或者当前所在路径,代表执行文件所在的路径,所以在被导入模块与执行文件在同一目录下时肯定是可以正常导入的,而针对被导入的模块与执行文件在不同路径下的情况,为了确保模块对应的源文件仍可以被找到,需要将源文件foo.py所在的路径添加到sys.path中,假设foo.py所在的路径为*/pythoner/projects/*

3、导入其他路径模块

import sys
sys.path.append(r'/pythoner/projects/')  # 也可以使用sys.path.insert(……)

import foo  # 无论foo.py在何处,我们都可以导入它了

七、py文件的两种用途

​ 一个Python文件有两种用途,一种被当主程序/脚本执行,另一种被当模块导入,为了区别同一个文件的不同用途,每个py文件都内置了__name__变量,该变量在py文件被当做脚本执行时赋值为__main__,在py文件被当做模块导入时赋值为模块名。

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

# foo.py
...
if __name__ == '__main__':
    foo.py  # 被当做脚本执行时运行的代码
else:
    pass  # 被当做模块导入时运行的代码

通常我们会在if的子代码块中编写针对模块功能的测试代码,这样foo.py在被当做脚本运行时,就会执行测试代码,而被当做模块导入时则不用执行测试代码。

八、编写一个规范的模块

我们在编写py文件时,需要时刻提醒自己,该文件既是给自己用的,也有可能会被其他人使用,因而代码的可读性与易维护性显得十分重要,为此我们在编写一个模块时最好按照统一的规范去编写,如下

#!/usr/bin/env python3  # 通常只在类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()  # 在被当做脚本执行时,执行此处的代码
文章不错,扫码支持一下吧~
上一篇 下一篇
评论
最新回复
文章目录
每日一句