模块和包-python

1、模块

1.1、概念

在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护。

为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在python中,一个.py文件就称之为一个模块(Module)

模块是代码的一种组织形式,是功能相关或者相似代码的组合。

你也许还想到,如果不同的人编写的模块名相同怎么办?为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package)

python中的模块一般有如下3种:

  1. python内置模块
  2. 第三方模块
  3. 自定义模块

1.2、模块的导入

1.2.1、import 导入
import module1[, module2, ..., moduleN]
# 或者
import moudule1
import module2
...
import moduleN
  • 多个模块导入时的顺序:

    • 规范建议:模块应该一个一个的导入,先后顺序为:内置模块---->扩展(第三方)模块------>自定义模块;
  • 顺序说明:我们知道导入模块其实就是在执行这个模块,我们不确定扩展模块或自定义模块里有没有某个功能调用了内置模块,所以,我们在导入模块时,应先导入解释器内置的模块,然后在导入扩展模块,最后导入自定义模块。

1.2.2、导入过程
  • 找到这个需导入的模块;

  • 判断这个模块是否被导入过;

  • 如果没有被导入过:创建一个属于这个模块的命名空间;如果用户没有定义变量来引用这个模块的内存地址的话,那么就使用模块的名称来引用这个模块的内存地址;如果用户使用as来指定变量接受这个内存地址的话,那么就将内存地址赋值给这个变量;且下文在调用时只能使用这个变量进行调用不能再使用模块名进行调用了。然后执行这个模块中的代码;

  • 如果该模块已经被导入过:那么解释器不会重新执行模块内的语句,后续的import语句仅仅是对已经加载到内存中的模块的对象增加一次引用;

  • 示例:

    # 测试模块
    print("repeat import...")
    
    
    def fun():
        pass
    
    
    class A:
        pass
        
    # 测试多次导入
    import repeat_import
    import repeat_import
    import repeat_import
    
    # 测试结果
    repeat import...
    
1.2.3、关于导入的模块与当前空间的关系

带入的模块会重新开辟一块独立的名称空间,定义在这个模块中的函数把这个模块的命名空间当做全局命名空间,这样的话当前的空间就和模块运行的空间分隔了,谁也不影响谁;

1.2.4、为模块起别名

模块在导入的时候开辟了新空间内存,默认是使用模块的名称来引用这个内存地址的,有时候模块的名称很长再加上执行调用里面的功能的时候,就显的很不方便,为了更好的使用模块,我们可以给模块起别名;

也就是在导入模块的时候我们不让它使用默认的名字来引用内存地址,而是由我们自己定义的变量来引用这个模块的内存地址;

import module as alias
1.2.5、from…import

我们在使用一个模块时可能只想使用其中的某些功能,比如只想使用sys模块中的modules时,那么import就不好用了,这时我们可以使用from…import这样的格式来进行模块的某个功能的导入。

注意:这时模块是已经全部加载了,但只是为这个功能进行了引用,其他的功能并没有引用,所以在使用模块的时候,就只能使用这个模块的这个功能,其他的功能不能使用,如果想再使用这个模块的另一个功能时,可以在进行一次模块的要使用的功能的导入,这时模块不会再进行加载了,而是只对该功能进行引用,这样我们就可以使用这个功能了。

**from…import 也支持as模式,**这个我们在上个例子里也说过了,这里就不再重复了;

from…import 也支持导入某个模块的多个功能

示例:

# import sys.modules
from sys import modules

print(modules)

from sys import modules as m

print(m)
1.2.6、from…import *

* 表示导入模块中所有的不是以下划线(_)开头的名字都导入到当前位置,大部分情况下我们的python程序不应该使用这种导入方式,因为*你不知道你导入什么名字,很有可能会覆盖掉你之前已经定义的名字。而且可读性极其的差,在交互式环境中导入时没有问题。

还有一点要说的是,如果使用* 的方式进行了导入,这时只想使用里面的某个或某些功能时,可以使用__all__来进行约束;

注意:__all__只是用来约束*方式的,其他方式导入的话,不会生效;具体使用见例子:

# import_all 模块
__all__ = ["func3"]

print("hello world")
name = "zjk"


def func1():
    print("zjk1")


def func2():
    print("zjk2")
    func1()


def func3():
    print("zjk3")
    func2()

# 测试导入模块
from import_all  import *
func3()
print(name)
func1()
func2()

# 测试结果
hello world
zjk3
NameError: name 'name' is not defined
1.2.7、_name_

有时候我们某个模块中的可执行部分只是为了测试或者当前模块使用,我们不希望该模块被导入时执行,此时需要用到_name_,是一个内置变量。这个变量记录了当前文件(使用 name 变量的文件)是作为模块运行还是主执行文件

示例:

# import_name.py
def func1():
    print('func1')


print(__name__)
if __name__ == '__main__':
    func1()
# 当前模块执行结果
__main__
func1
# 导入测试
import import_name
# 测试结果
import_name

由此可知,_name__当前模块为_main;非当前模块默认为模块名(文件名)。

2、包

2.1、概念

简单而言,Python中的包(Package)就是一个目录,里面存放了 .py文件,外加一个 init.py。通过目录的方式来组织众多的模块,包就是用来管理和分类模块的

2.2、包的导入

导入包的语句如下:

import package 

# 或引入包下的某一个模块
from package import module

import package 或者 from package import module 都会执行package 包下的_init_ .py文件

现在有如下目录结构:

├─ROOT                               
│  ├─pk_1
│  │  ├─__init__.py 
│  │  ├─m1.py    
│  ├─pk_2  
│  │  ├─__init__.py 
│  │  └─m2.py
│  ├─__init__.py
│  ├─test.py

pk_1 和 pk_2 包中的 init.py 文件都为空,ROOT包下的 test.py 想要使用 pk_1 包下 m1模块中的方法,可以使用如下语句:

from pk_1 import m1
m1.fun_1()        # fun_1() 为m1模块中的方法

但是使用如下语句,就会抛出异常:

from pk_1 import *
m1.fun_1()

# 异常信息:
NameError: name 'm1' is not defined
##############################
import pk_1
pk_1.m1.fun_1()

# 异常信息:
AttributeError: module 'pk_1' has no attribute 'm1'

这时候可以在 pk_1 包中的__init__.py 中 进行 包提升(在包中提升导入权限),pk_1 包的 init.py 文件内容如下:

from pk_1.m1 import fun_1

然后在 test.py 文件中可以直接通过包名引入方法:

# 1)
from pk_1 import fun_1 # 或 from pk_1 import *
fun_1()

# 2)
import pk_1
pk_1.fun_1()

这个就是 包中 init.py 文件存在的意义,可以将相关的导入语句 或 提升导入权限的语句 写在 init.py文件中,这样使用者就不需要了解包中的内部结构,可以直接通过包名 调用该包(package)中某个模块的方法~

还可以在 包中 init.py 文件中使用 all 列出需要导入的模块,例如在 pk_1 包中的 init.py文件中添加 all 变量:

__all__ = ['m2']

然后在 test.py 文件中就可以使用 from pk_2 import * 一次性导入 __all__变量中列出的模块:

from pk_2 import *
m2.fun_2()

若是 pk_2 包的 init.py 文件已经对 fun_2 方法做了提升:

# pk_2 包的 \_\_init\_\_.py 内容
from pk_2.m2 import fun_2

这样在 test.py 中 import * 后可直接使用该方法:

from pk_2 import *
fun_2()

注意:当 init.py 中定义了 all 变量时,import * 只会导入 __all__中列出的模块

1.3、包中的模块调用

现在有如下目录结构:

├─log                           
│  ├─util
│  │  ├─__init__.py 
│  │  ├─a.py    
│  │  ├─b.py    
│  ├─__init__.py
│  ├─test.py

在test中引入 a模块:

from util import a

在 a 模块中又引入了 b 模块:

import b

这样的话在执行 test 文件时就会报错,ModuleNotFoundError: No module named ‘b’,说无法找到b模块。

这是因为 在执行 test 时,sys.path 中的路径不包含 util 下的路径,所以在 a.py 文件中 import b 模块时就会报错(若是直接执行的是 a.py 文件就不会有问题)。在 a模块 中引入 b 模块的正确的写法是:

from util import b

当然这个时候 a.py 文件就不能再单独运行了,运行时就会报错

**Tip:**在 a.py 文件中 使用 “from util import b” 导入模块 b,这个时候若是直接执行 a.py 文件就会报错,因为 a.py 文件本身就位于util路径下,sys.path(执行 a.py 时的sys.path)中有util路径,但是 ‘from util’ 是找不到util的,util 位于 sys.path 的某个路径下时,‘from util’ 才能找到util ~

若是现在 主执行文件 本身就位于项目目录下的某个包中,要引入其他包中的模块,就需要通过在 os.path 中添加路径来实现:

现在执行文件是 bin 目录下的 bin.py,在 bin.py 中要导入 util包 中的 a模块 和 b模块,为了保证通用性,可以使用如下方式获取 log 路径,并且添加到os.path中:

import sys, os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from util import a

3、作用

  • 提供代码的复用性、可维护性。
  • 可以避免模块、函数名和变量名冲突

参考文章:

参考视频:https://www.bilibili.com/video/BV1R7411F7JV 249~256

源代码仓库:https://gitee.com/gaogzhen/python-study.git

交流QQ群;433529853

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gaog2zh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值