python模块加载顺序_【Python技术进阶-2】最全的Python模块和包详细指南

刚开始入门时,代码都是几行,十几行,在Python自带的解释器中就可以完成。逐渐的,代码会到几十行、上百行,就会用PyCharm/VSCode之类的Python IDE,通常是新建一个py文件,代码都写在这个py文件。

小伙伴们学习都很努力,经过一段时间Python基础知识的学习,已经迫不及待的想要一展身手。有一位小伙伴叫小帅,要完成这样的功能:“从一批存储不同业务数据的Excel文件中读取数据,然后对读取到的数据进行简单的转换处理,最后再写入到数据库。”需求挺简单的,小伙伴就开始写代码,写着写着,发现好几个函数好长呀,都是好几百行的代码,整个py文件有1千多行的代码。代码终于写完了,开始要用真实数据进行调试,这时候发现调试很不熟手。“感觉好乱呀,一会儿要用鼠标拖动滚动条到文件最上面位置,查看读取Excel数据的代码逻辑,一会儿又要拖动到文件中间位置查看数据转换后的格式为什么不对。”

小帅就来问飞哥,“一个文件里面的代码太多了,函数也很长,看代码以及调试代码的效率好低,应该怎么优化?”

飞哥说,“你这问题,很多Python新手小伙伴都会遇到,包括飞哥自己以前也是这么干的。要解决这问题也很简单,学习了今天介绍的模块和包的Python知识后,你就知道怎么做了。”

接下来,飞哥就给小伙伴详细聊聊Python的模块和包,它是一个很重要的知识点,能够帮助小伙伴有效的提升模块化编程思维,提高代码的可复用性。

01 模块

在Python中,一个.py文件就是一个模块,模块名就是.py文件的文件名称。通过导入模块的方式,模块里面的函数以及变量等对象就可以被其它py文件使用。

正是由于模块可导入的特性,可以把一个几千行的Py文件,通过重构的方式,将不同功能的代码分离,分别放在不同的Py文件里,就可以把一个大文件分割成几个小的只有几百行的Py文件。这样,保持了每个模块文件的代码功能的单一性,从而提高代码的可维护性以及可复用性。通过不同功能模块的组合,就可以完成一个功能复杂的需求,这就像乐高积木一样。

现在我们就来现学现用,上面小帅同学的问题,就有了优化方案:

1)将文件中的代码功能进行剥离,分成Excel读取功能、数据处理功能、数据入库功能,分别放入下图右边所示的3个模块文件中。

2)在主文件中,分别导入上述3个模块文件,调用相关模块的接口函数,完成整个需求的功能。

02 模块的使用

模块是一个功能独立的代码文件,要使用模块,必须先导入模块。每个模块被导入后,都会创建一个它自己的唯一的名称空间,通常模块名就代表了这个名称空间。因此,访问模块的变量、函数等属性时,必须使用模块名+点号+标识符的方式,基本语法如下所示:

2.1、导入模块的方法

首先,飞哥先定义一个很简单的测试模块,然后分别说明以下各种不同的导入模块的方法。

自定义模块hello.py的代码如下:

#!/usr/bin/python3

# -*- coding: utf-8 -*-

name = "hello.py"

print("各位请注意,我来啦")

def test1():

print("哈喽, 我是测试1")

def test2():

print("哈喽, 我是测试2")

​1)import 模块名

示例1,导入内置模块

>>> import math # 导入math模块

>>> math.pow(2,3) # 调用math模块的pow函数

8.0

>>>

示例2,导入自定义模块

在main.py中使用时,按如下方式调用自定义模块:

# 导入模块

import hello

# 调用模块中的变量

print(hello.name)

# 调用模块中的函数

hello.test1()

hello.test2()

示例3,可以一次导入多个模块,但代码规范中不提倡这种方式,应该使用一行导入一个模块的方式:

# 不推荐的一行导入多个模块

>>> import os, sys, math

>>>

# 推荐的是一行导入一个模块

import math

import os

import sys

2)import 模块名 as 别名

有时候,一个模块的名称很长,在使用的时候就很不方便,这时候就可以给它起一个别名(起一个简短的名称),简化模块名称。其实,有时候模块名称也不长,但为了书写方便或是惯例,导入模块的时候也会起一个很短的别名。

# 使用别名导入模块

import numpy as np

# 通过别名引用模块中的属性

img = cv2.imread("./cat.jpg")

emptyImage = np.zeros(img.shape, np.uint8)

另外一种情况,避免命名冲突。比如,在当前文件中有一个标识符(函数或是变量或是第三方模块)与我们自定义的模块名重复了,可以在导入模块时给它起一个别名,来解决名称冲突。

例如,在main.py中,定义一个hello函数,就会出现冲突:

import hello

def hello():

print("哈喽,main文件")

# 这时候出现名称冲突,这个全局的hello函数的名称

# 会覆盖hello模块的名称,导致下面的模块调用失败

hello.test1()

上面的代码执行后,会出现下面的异常:

上面这种情况,其实模块已经导入,只是模块名称hello和模块本身失去了联系(失联状态),就如同擦肩而过的2个人,那个TA其实一直都在,只是你无法看到而已。

通过别名的方式,就可以解决上面的问题:

import hello as hi

def hello():

print("哈喽,main文件")

# 通过别名解决命名冲突

hi.test1()

3)from 模块名 import 标识符

很多时候,我们为了完成一个功能,要使用另一个模块中的2个函数而已,而且功能也比较简单。就可以直接导入模块中的属性,在使用的时候,就如同本地定义的变量一样直接使用,不需要以模块名+点号的方式来访问。注意,这种方式,由于没有命名空间的限制,属性被导入到当前上下文的命名空间里,可能出现名称覆盖或命名冲突的情况。

# 可以一次导入1个也可以导入多个

# 导入多个属性时,属性之间用逗号分隔

from hello import test1, test2

# 直接访问模块中的属性

test1()

test2()

4)from 模块名 import *

将模块中所有可导出的属性都导入到当前上下文的命名空间。在使用的时候,就如同本地定义的变量一样直接使用,不需要以模块名+点号的方式来访问。但是,由于被导入的属性很多,尤其是使用第三方模块时,你自己可能都不知道有哪些属性,因此,非常容易出现名称覆盖或命名冲突的情况。在代码规范中严禁使用这种导入方式。平时,在一些临时性的测试中,可以偶尔使用。

from hello import *

# 直接访问模块中的属性

test1()

test2()

5)from 模块名 import 标识符 as 别名

它的作用与2)中的作用类似。一是解决命名冲突或覆盖,二是简化属性的名称或是简化包中子模块的名称。

示例1,导入模块的属性名称时使用别名:

from hello import test1 as hi1, test2 as hi2

# 直接访问属性的别名

hi1()

hi2()

示例2,导入包的子模块时使用别名:

import numpy as np

import cv2 as cv

from matplotlib import pyplot as plt #简化包中子模块的导入名称

img = cv.imread('die.png')

# 通过模块别名访问函数

plt.imshow(img)

2.2、导入和加载

一个模块可以被导入(import)多次,但只会被加载一次。当模块被加载时,被导入模块的顶层代码将直接被执行,比如模块中定义的全局变量、类和函数的定义、全局的函数调用等等。

示例1,测试模块hello.py代码如下:

#!/usr/bin/python3

# -*- coding: utf-8 -*-

name = "hello.py"

print("__name__的值:", __name__)

print("各位请注意,我来啦")

def test1():

print("哈喽, 我是测试1")

def test2():

print("哈喽, 我是测试2")

# 全局的函数调用

test1()

在PyCharm中,当模块hello.py被直接运行时,输出结果如下:

在PyCharm中,当模块hello.py被导入多次,代码如下:

import hello

import hello

import hello

print(hello.name)

然后,运行这段代码,结果如下:

在Python自带的IDLE中也试试,进行多次导入:

通过3次不同的实验,说明以下结论:

1)当模块第1次被导入时,才被加载并执行模块内的语句。

2)以后再次导入模块时,Python解释器会判断模块是否被加载,如果已经被加载,则不会再执行加载过程,而是通过模块名直接引用已经加载的模块。

3)模块直接运行时的__name__的值是字符串“__main__”,模块被导入时的__name__的值是模块的名称(如果模块在是某个包中,__name__模块的名字有什么不同?小伙伴可以自己试试)。

因此,在模块文件中,不要出现全局的函数调用,因为全局的函数调用在加载时被直接执行。有小伙伴说,“写完一个模块,需要进行一个简单的测试,需要调用这些函数,怎么办?”这时候,根据上面的结论3),可以在下面的if语句中调用。

if __name__ == "__main__":

# 在这里写相关的函数调用

pass

修改后的模块hello.py的代码如下:

#!/usr/bin/python3

# -*- coding: utf-8 -*-

name = "hello.py"

def test1():

print("哈喽, 我是测试1")

def test2():

print("哈喽, 我是测试2")

if __name__ == '__main__':

'''测试相关的代码写在这里,不要写在顶层的全局代码区'''

print("各位请注意,我来啦")

test1()

2.3、 模块的导入顺序

推荐所有的模块在Python模块的开头部分导入,一行导入一个模块。并且按照以下的导入顺序:

1)Python内置模块;

2)Python第三方模块;

3)自定义模块;

​03 模块的搜索路径

一个模块就是一个.py文件,当我们进行模块导入时,Python解释器是从哪里查找这个模块文件呢?

平时,我们安装一些开发软件时,会遇到添加到Path环境变量的选项,如果我们勾选后,当运行一些小工具时,直接在cmd窗口,输入工具的文件名称,回车就可以打开这个工具,比如我们的安装Python时,就有这个选项。如果没有勾选添加到环境变量,就要在工具所在的文件夹目录打开命令行窗口,输入工具的文件名称,回车才可以打开工具。

因此,Python查找模块文件时,很有可能也会在Path环境变量或是当前文件所在的文件夹目录中查找。事实上,Python确实会从这些位置查找模块文件,但是还有其他的一些目录,并遵循一定的搜索顺序。具体如下:

1)内存中查找是否存在要导入的模块,也就是查看模块是否已经加载过。

2)如果没加载过,在Python内置模块中查找;

3)如果没找到,当前执行文件所在的目录查找;

4)如果没找到,在sys.path所代表的环境变量指定的一系列目录中查找;

5)如果还没有找到,则Python抛出异常。

下面以hello模块为例,演示了这一查找过程。在实际开发过程中,如果我们的模块文件在其他文件夹,就需要使用sys.path.append()的方式添加,否则会弹出模块找不到的异常。

04 包

包,是一个包含其它模块或子包的目录。在Python3.3之前,目录下必须要有一个__init__.py文件,这个目录才被Python视为包。通常这个__init__.py文件是一个空文件。但在Python3.3及以后的版本,任何一个目录都可以被视为包,目录里可以有__init__.py文件,也可以没有。

一个典型的包的结构如下,这个包的名称是pkg1,包含3个子模块:mod1, mod2, mod3:

包有助于模块的结构化管理,在开发过程中,一个项目可能会有成百上千个Python文件,如果全放在一个文件夹里面,既不方便查找,也不方便管理。这时,包就派上用场了,可以建立层次化的包(目录)结构,把这些模块进行分类,功能相同或相关的模块放在一个目录(包)。

此外,一个软件项目,是由很多开发者共同开发,开发者A开发功能点1,开发者B开发功能点2,这2个开发者可以各自建立一个包,并把开发完成的模块放在包里面,即使2个开发者的模块名称相同,由于包名不同,访问时是由包名+模块名的方式访问,这也能解决模块名称冲突的问题。因此,使用包可以方便开发者进行协作开发。

4.1、 包的使用

包也是一种特殊的模块,和其它模块一样,包也使用包名+点号+标识符的方式访问属性。使用import或者from-import语句导入包中的模块。

对于上面的包pkg1中的模块1,定义了如下的函数:

# -*- coding: utf-8 -*-

def mod1_test():

print("我是模块1的函数")

导入和访问包中的模块,有如下方式:

1)import语句导入模块

# 导入模块

import pkg1.mod1

# 使用模块

pkg1.mod1.mod1_test()

这种方式,调用模块的属性时,有很长的名称限制,比较繁琐,这时就可以使用from-import语句

2)From Import语句导入模块

​# 导入模块

from pkg1 import mod1

# 使用模块

mod1.mod1_test()

3)From Import语句导入模块中的具体属性

# 直接导入模块中的函数

from pkg1.mod1 import mod1_test

# 使用模块中的函数

mod1_test()

注意:除了使用上面的标准方式访问模块或属性,还可以使用as来指定别名,小伙伴可以自己试试。

05 写在最后

能够应用到实际工作中的知识才是有价值的知识。前面出现问题的小帅同学,飞哥特别叮嘱他消化一下模块和包的知识,并把前面的那张模块结构划分的图发给他,让他进行重构。过了1天,小帅发给飞哥修改后的代码,“优化掉20%的代码量,1个主文件完成模块的导入和功能的调用,3个模块文件分别完成文件读取数据、数据处理以及数据库操作。现在的代码结构很清晰,每个模块的功能独立。”

1. 如果觉得有用,请给我个赞,鼓励我写出更多优质文章

2. 关注我,能第一时间收到最新文章

3. 欢迎私信交流学习!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值