Python中模块化与包

Python模块化与包详解
本文详细介绍了Python中的模块化与包的概念,包括模块的定义、包的定义、导入语句(import和from...import)、模块的搜索顺序、重复导入、属性、子模块、绝对导入与相对导入、访问控制等内容。强调了模块和包的组织方式,以及如何通过sys模块管理模块。

模块化与包

  1. 模块的定义:所有以.py结尾的Python源代码文件都是一个模块
    • 模块名也是个标识符。可以由字母、下划线和数字组成(注意:不能以数字开头,不能与关键字重名)
    • 模块就像工具包,要想使用这个工具包中的工具,就需要使用import导入这个模块
    • 在模块中定义的全局变量、函数都是模块能够提供给外界直接使用的工具
  2. 包的定义:在一个文件夹下面存在一个__init__.py的文件,那么该文件夹就是一个包。
    • __init__.py文件为包上定义的内容。在包加载时会同步被加载。
    • 包文件夹下面可以存在多个子包模块
    • 目录可以作为模块,这就是包,不过代码需要写在该目录下 __init__.py
  • 一般来说,编程语言中,库、包、模块是同一种概念,是代码组织方式。
  • Python中只有一种模块对象类型,但是为了模块化组织模块的便利,提供了"包"的概念。
  • 模块module,指的是Python的源代码文件。
  • 包package,指的是模块组织在一起的和包名同名的目录及其相关文件。

导入语句

语句含义
import 模块1[,模块2…]完全导入
import … as …模块别名
  • import 语句

    1. 找到指定的模块,加载和初始化它,生成模块对象。找不到,抛出异常
    2. 在import所在的作用域的局部命名空间中,增加名称和上一步创建的对象关联
  • 简单示例

  • import functools 表示将functools导入当前作用域中

import functools #导入模块functools
print(dir()) #查看当前作用域的命名名空间
print(functools) #<module 'functools' from 'D:\\Application\\python\\ping\\lib\\functools.py'>
print(functools.wraps) #<function wraps at 0x000002969469EE18>

modules_001

  • import os.path #导入os.path,在导入的同时,会将os和os.path加入当前命名空间
import os.path #导入os.path,os加入当前名词空间
import sys
print(dir()) #[...,'os']
print(os)
print(os.path)
print(sys.modules.keys()) #查看系统已经加载的模块

modules_002

  • import os.path as osp #导入os.path并赋给osp
import os.path as osp # 导入os.path并赋给osp  
print(dir()) #[..., 'osp']  
print(osp) # <module 'ntpath' from 'path/to/path.py'>
  • 导入的访问域控制
def textimport():
    import os.path #局部,只能在局部访问
    print(dir())

textimport()
print(globals().keys())
#import os.stat #不可以,应为stat不是模块

modules_003

  • 总结
    1. 导入顶级模块,其名称会加入到本地名词空间中,并绑定到其模块对象。
    2. 导入非顶级模块,只将其顶级模块名称加入到本地名词空间中。导入的模块必须使用完全限定名称来访问。
    3. 如果使用了as,as后的名称直接绑定到导入的模块对象,并将该名称加入到本地名词空间中。

from语句

语句含义
from…import…部分导入
from…import…as…别名
  • 找到from子句中指定的模块,加载并初始化它(注意不是导入)

  • from后面只能是模块名称,或包名称

  • from … import后面可以跟随有效的名称。

    1. 如果from后面是包,先加载包文件,在从包模块内容(__init__.py)文件中查找与import后面对应的名称。找到对应的变量名就导入到当前作用域。
      • 如果没找到,就查找包下面与其名称对应的模块,找到就加载该模块,并将该模块作为变量名引入当前作用域。
      • 如果都没找到就抛出ImportError异常
    2. 如果from后面是模块,先加载from后面的模块,再从模块中找到import后面对应的变量名,找到就导入到当前作用域。找不到就抛出ImportError异常
  • 使用import或者from…import导入的模块或函数,在内存中只存在一份,不会多次导入

from os.path import exists #加载、初始化os、os.path模块,exists加入本地名词空间并绑定

print(exists)

import os.path

print(os.path.exists)
print(os.path.__dict__["exists"])
print(getattr(os.path,"exists"))

modules_004

  • 别名
from functools import wraps as wr,partial #别名wr
print(dir()) #[... 'wr',‘partial’]
  • 当前名词空间中导入该模块指定的成员
from pathlib import Path,PosixPath #在当前名词控件导入该模块指定的成员
print(dir()) #[...,"Path","PosixPath"]
  • 在当前名词控件导入该模块所有公共成员(非下划线开头成员)或指定成员
from pathlib import * #在当前名词控件导入该模块所有公共成员(非下划线开头成员)或指定成员
print(dir())  # [..., 'Path', 'PosixPath', 'PurePath', 'PurePosixPath', 'PureWindowsPath', 'WindowsPath']

自定义模块

自定义模块:.py文件就是一个模块

自定义模块命名规范

  1. 模块名就是文件名
  2. 模块名必须符合标识符的要求,是非数字开头的字母、数字和下划线的组合。
    • 例如:test-module.py这样的文件名不能作为模块名。也不要使用中文。
  3. 不要使用系统模块名,避免冲突。除非你明确知道这个模块名的用途
  4. 通常模块名为全小写,下划线来分割

模块的搜索顺序

使用sys.path查看搜索顺序

import sys

# print(*sys.path,sep='\n')
for p in sys.path:
    print(p)

modules_005

  • sys.path是一个列表,记录了模块的默认搜索顺序,当加载一个模块的时候,需要从这些搜索路径从前到后依次查找,并不搜索这些目录的子目录。搜索到模块就加载,搜索不到就抛出异常。
    1. 路径也可以为字典、zip文件、egg文件
      • .egg文件,由setuptools库创建的包,第三方库常用的格式。添加了元数据(版本号、依赖项等)信息的zip文件,实质上就是个zip文件
    2. 路径顺序为:
      • 程序主目录,程序运行的主程序脚本所在的目录
      • PYTHONPATH目录,环境变量PYTHONPATH设置的目录也是搜索模块的路径
      • 标准库目录,Python自带的库模块所在目录
  • sys.path可以被修改,增加新的目录

模块的重复导入

  • 模块不会产生重复导入的现象,所有加载的模块都会记录在sys.modules中,sys.modules是存储已经加载过的所有模块的字典。(注意:打印sys.modules可以看到os、os.path都已经加载了。)
# test1.py文件如下
print("this is test1 module")

class A:
    def showmodule(self):
        print(1,self.__module__,self)
        print(2,self.__dict__)
        print(3,self.__class__.__dict__)
        print(4,self.__class__.__name__)

a = A()
a.showmodule()

# test2.py文件如下(运行test2.py)
import test1  #导入当前文件
print("local module")
import test1
import test1

modules_0056

  • 从执行结果来看,不会产生重复导入的现象。所有加载模块都会记录在sys.modules中。
  • sys.modules是存储已经加载过的所有模块的字典。(打印sys.modules可以看到os,os.path都已经加载了)

模块的属性

属性含义
__file__字符串,源文件路径
__cached__字符串,编译后的字节码文件路径
__spec__显示模块的规范
__name__模块名
__package__当模块是包,同__name__;否则,可以设置为顶级模块的字符串

模块运行

  1. __name__,没个模块都会定义一个__name__特殊变量来存储当前模块的名称,如果不指定,则默认为源代码文件名,如果是包则有限定名。

  2. 解释器初始化的时候,会初始化sys.modules字典(保存已加载的模块),加载builtins(全局函数、常量)模块、__main__模块、sys模块,以及初始化模块搜索路径sys.path

  3. Python是脚本语言,任何一个脚本都可以直接执行,也可以作为模块被导入。

  4. 当从表中输入(命令行方式敲代码)、脚本($ python test.py)或交互式读取的时候,会将模块的__name__设置为__main__,模块的顶层代码就在__main__这个作用域中执行。

    • 顶层代码:模块中缩进最外层的代码。
  5. 如果是import导入的,其__name__默认就是模块名

  • if __name__ == '__main__'用途:
    1. 本模块的功能测试,对于非主模块,测试本模块内的函数、类
    2. 避免主模块变更的副作用。假如:顶层代码,没有封装,主模块使用时没有问题。但是,一旦有了新的主模块,老的主模块成了被导入模块,由于原来代码没有封装,会一并执行。
# test1.py文件
import test2

#test2.py文件
# 判断模式是否以主模块方式运行 $python test2.py
if __name__ == "__main__":
    print("in __main__") #主模块方式运行
else:
    print("in imported module") #模块导入的方式运行的代码

子模块

包目录下的py文件、子目录都是其子模块

模块和包的总结

  • 包能够更好的组织模块,尤其是大的模块,其代码行数很多,可以把它拆分成很多子模块,便于使用某些功能就加 载相应的子模块。
  • 包目录中 __init__.py 是在包第一次导入的时候就会执行,内容可以为空,也可以是用于该包初始化工作的代码, 最好不要删除它(低版本不可删除 __init__.py 文件)
  • 导入子模块一定会加载父模块,但是导入父模块一定不会导入子模块
  • 包目录之间只能使用.点号作为间隔符,表示模块及其子模块的层级关系
  • 模块也是封装,如同类、函数,不过它能够封装变量、类、函数。
  • 模块就是命名空间,其内部的顶层标识符,都是它的属性,可以通过 __dict__ 或dir(module)查看。
  • 包也是模块,但模块不一定是包,包是特殊的模块,是一种组织方式,它包含 __path__ 属性

绝对导入,相对导入

绝对导入

  • 在import语句或者from导入模块,模块名称最前面不是以点(.)开头的
  • 绝对导入总是去模块搜索路径中找,当然会查看一下该模块是否已经加载

相对导入

  • 只在包内使用,且只能用在from语句中
    1. 使用点(.)号表示当前目录内
    2. 使用(…)表示上一级目录
    3. 使用(…)表示上上级目录。依次内推
  • 不要再顶层模块中使用相对导入
  • 一旦一个模块中使用了相对导入,就不可用作为主模块运行

简单示例:

  • 目录结构如下:

modules_007

# m文件夹下面的__init__.py文件内容
from . import mm
print(__name__)

# m文件夹下面的mm.py文件内容
from . import m1
print(__name__)

# m1文件夹下面的__init__.py文件内容
print(__name__)

# test1.py文件内容 (运行test1.py)
import m
  • 运行test1.py输出结果如下:

modules_008

访问控制

  • 模块中以下划线(_或者__)开头的模块都可以导入,只要是合法的字符,就可以作为模块名。
  • 普通变量、保护变量、私有变量、特殊变量,都没有被隐藏,也就是说模块内没有私有的变量,在模块中定义不做 特殊处理。依然可以使用from语句,访问所有变量。

from ... import *__all__

  • __all__是一个列表,元素是字符串,每一个元素都是一个模块内的变量名

一、使用from xyz import * 导入

  1. 如果模块没有__all__from xyz import *只导入非下划线开头的该模块的变量。如果是包,子模块也不会导入,除非在__all__中设置,或__init__.py中导入他们。
  2. 如果模块有 __all__from xyz import * 只导入 __all__ 列表中指定的名称,哪怕这个名词是下划线开头 的,或者是子模块
  3. from xyz import * 方式导入,使用简单,但是其副作用是导入大量不需要使用的变量,甚至有可能造成名 称的冲突。而 __all__ 可以控制被导入模块在这种导入方式下能够提供的变量名称,就是为了阻止from xyz import *导入过多的模块变量,从而避免冲突。因此,编写模块时,应该尽量加入 __all__

二、from module import name1,name2导入

  • 这种方式的导入是明确的,哪怕是导入子模块,或者导入下划线开头的名称
  • 程序员可以有控制的导入名称和其对应的对象

附加sys中的特殊属性

特殊属性意义
sys.modules当前系统已经加载过的所有模块的字典
sys.path初始化模块搜索路径。(导入模块时会从这些搜索路径中找)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值