DoubleLi

qq: 517712484 wx: ldbgliet

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  4737 随笔 :: 2 文章 :: 542 评论 :: 1615万 阅读
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

模块

简介

模块是包含python的定义及语句的文件,其文件名就是模块名加后缀名.py ;说白了也就是一个.py文件;每个模块都有自己独立的符号表(命名空间);该命名空间被模块中定义的所有函数用作全局符号表使用。所以可以放心大胆的在模块内部使用这些全局变量,而不用担心和其他模块的全局变量搞混;

模块也可以导入到其他模块,以使用该模块的函数等功能;

如:创建一个test.py文件

 
 
def t(n):
 
print(n)
 
 

然后,就可以在其他模块中引入 或 进入python解释器

 
 
>>> import test
 
>>> test.__name__
 
'test'
 
>>> test.t(10)
 
10
 
 

模块的特点

  • 可维护性:分成多个文件,修改时只需要修改相关文件
  • 可重用性:单个模块中定义的功能可以被应用程序中其他部分重用
  • 作用域:模块通常会有一个单独的命名空间,可以有效的避免程序名称发生冲突

模块的分类

模块一共可以分为三类:

  • 内置模块
  • 第三方模块:`pip install`安装的
  • 自定义的模块

就是一个包含.py文件的目录;一种管理python模块命名空间的形式;只把包含__init__.py的文件目录当成包;

导入机制

一个模块内的python代码通过导入操作就能够访问另一个模块内的代码。import是最常用的导入机制,但不是唯一的方式。还可以通过 importlib.import_module() 以及内置的 __import__() 函数用来发起调用导入机制;

import导入操作有两个:

  1. 搜索指定名称的模块;如果有必要还会加载并初始化模块
  2. 将搜索结果绑定到当前作用域中;

在第一步中,import 语句的搜索操作被定义为对 __import__() 函数的调用并带有适当的参数;__import__()的返回值会被用于执行 import 语句的名称绑定操作;当然直接调用 __import__() 函数进行模块搜索以及找到模块时的创建操作;只有 import 语句执行名称绑定操作;其它导入机制(importlib.import_module())可能会绕过 __import__() 函数并使用自定义解决方案实现导入机制;

搜索路径

sys.modules:是一个全局字典,python启动后就加载到内存中,同时会预导入一些内置和标准模块;当有新模块导入时,sys.modules会将模块名称与模块对象进行映射;主要用来映射所有的模块名称和模块对象;在模块搜索时起到缓存作用,避免重复搜索;可修改,如果删除键,则会导致缓存条目无效,导致python在下次导入时重新搜索模块;也可以直接对键赋值为None,下一次导入时将会引发ModuleNotFoundError异常;

搜索路径:

  1. 搜索sys.modules。缓存之前所有导入的模块;如果存在直接返回该对象
  2. 如果没有找到,则搜索sys.meta_path(元路径查找器对象列表);sys.meta_path默认有三个:内置、frozen、sys.path
    1. 则在变量 sys.path 中进行搜索;sys.path主要来自以下几个方面:
      1. 包的默认安装路径;如:site-packeages
      2. PYTHONPATH环境变量
      3. 脚本所在目录 或 python所在工作目录

在导入搜索期间,首先会搜索sys.modules,sys.module 模块的导入缓存,所缓存的模块包括中间路径;如果存在该模块的关联的值,则导入过程完成;如果值为None,则引发ModuleNotFoundError。

如果没有找到指定模块的名称,则调用python的导入系统以查找和加载该模块;python会搜索sys.meta_path,sys.meta_path包含元路径查找器对象列表。这些查找器按顺序被查询,如果查找到指定名称的模块,它将返回一个对象;如果未找到,则返回None。如果sys.meta_path查找到列表末尾,仍未找到该名称的模块,则将引发ModuleNotFundError。

查找器和加载器

如果在sys.modules缓存中查找不到特定名称的模块,则python根据导入协议去查找和加载该模块;导入协议有两部分构成:查找器和加载器;查找器(finder)主要任务为是否能够根据现有策略找到特定名称的模块;同时实现这两种接口的对象被称为导入器;

导入机制可扩展,可以加入新的查找器以扩展模块搜索的范围和作用域;

  • 查找器:(finder)
  • 加载器:(loader)

查找器

是否能够根据现有策略找到特定名称的模块;并没有真正加载模块。而是返回一个模块规格说明,实际上是模块导入相关信息的封装;以供后续导入机制用于加载模块时使用;

元路径

元路径查找器当特定名称的模块未在sys.module中找到时,python会搜索sys.meta_path;sys.meta_path元路径查找器对象列表;列表中的查找器会按照顺序依次被执行,是否找到特定名称的模块;如果找到特定名称的模块,将返回一个对象;如果不能处理将返回None;如果sys.meta_path处理过程到达列表末尾仍未返回说明对象,则引发ModuleNotFoundError。

模块说明对象:

 
 
>>> import test
 
>>> test.__spec__
 
ModuleSpec(name='test', loader=<_frozen_importlib_external.SourceFileLoader object at 0x7f8411c8de50>, origin='/usr/local/opt/miniconda3/lib/python3.8/test/__init__.py', submodule_search_locations=['/usr/local/opt/miniconda3/lib/python3.8/test'])
 
 

sys.meta_path默认有三个元路径查询器:

  • 内置模块查询器
  • 冻结模块查询器(frozen)
  • sys.path模块
 
 
>>> sys.meta_path
 
[
 
<class '_frozen_importlib.BuiltinImporter'>,
 
<class '_frozen_importlib.FrozenImporter'>,
 
<class '_frozen_importlib_external.PathFinder'>
 
]
 
 

 所有的元路径查找器都必须实现 find_spec()方法,该方法接受三个参数:

find_spec(filename, path, target=None)
 
  • filename:模块的完全限定名称;如:test.file.read
  • path:供模块搜索使用的路径;对于顶层,则为None;但对于子模块或包,path就是父级包的__path__属性值。如果__path__无法访问,则引发ModuleNotFoundError;如果找不到则返回None;
  • target:目标模块;可选;

如:

 
 
# 如导入test.file.read
 
 
 
# 首先执行顶级导入
 
mpf.find_spec('test', None, None)
 
 
 
# 导入test之后,遍历元路径导入
 
mpf.find_spec('test.file', test.__path__, None)
 
 
 
# 导入test.file后,最后一次遍历
 
mpf.find_spec('test.file.read', test.file.__path__, None)
 
 

如果元路径查找器

加载

当一个模块说明被找到时,导入机制将在加载该模块时使用它;

加载模块细节:

  • 如果sys.module中存在特定模块对象,导入操作将其返回
  • 在加载执行模块代码之前,该模块预先被添加到sys.modules中。因为该模块代码中可能直接或间接导入其自身,可以防治无限递归和多次加载情况
  • 如果加载失败,则模块从sys.modules中移除;仅限加载失败的模块;任何已经存在于sys.modules的模块,以及任何作为附带被加载的模块仍会保留
  • 在模块创建完成但还未执行之前,导入机制会设置导入相关属性;如:__name__、__loader__、__package__、__spec__、__path__、__file__、__cache__
  • 模块执行是加载的关键时刻,在此期间将填充模块的命名空间。执行会完全委托给加载器,由加载器决定要填充的内容和方式
  • 在加载过程中创建并传递给exec_module()的模块并不一定就是导入结束时返回的模块;

加载器

调用 importlib.abc.Loader.exec_module() 来执行模块;从exec_module()返回的任何值都将被忽略;

加载器必须满足的条件:

  • 如果是一个python模块,则加载器必须在模块的全局命名空间(module.__dict__)中执行模块的代码
  • 如果加载器无法执行执行模块,引发ImportError;但是在exec_module()期间引发的任何其他异常也会被传播;

当查找器返回一个模块说明时,导入机制将

编译python文件

为了快速加载模块,python把模块的编译缓存到__pycache__目录中,文件名为 module.version.pyc,version 对编译文件格式进行编码,一般是python的版本号。如:CPython 的3.8发行版本中,编译的版本缓存为 __pycache__/test.cpython-38/pyc 。使用这种命名惯例,可以让不同 python 发型版本及不同版本的已编译模块共存。

python对比编译版本与源码修改日期,查看是否已过期,是否需要重新编译,此过程完全自动化。此外,编译模块与平台无关,因此可在不同架构系统之间共享相同的支持库;

python有两种情况不检查缓存:

  • 从命令行直接载入模块,只重新编译,不存储结果;
  • 没有源模块,就不会检查缓存。

 

当通过python导入模块的完整限定名称(以点分割的整个路径test.file.read)时,此名称会在导入搜索的各个阶段被使用,也可以指向一个子模块的带点路径,如:限定名称:test.file.read 会尝试导入test,然后是test.file,最后是test.file.read。如果任何一个失败,都会引发ModuleNotFoundError异常;

如:

 
 
>>> import sys
 
>>> import test1
 
>>> sys.modules
 
{...... 'test1': <module 'test1' from '/Users/mac/Desktop/PROJECT/test1.py'>}
 
 

导入模块后

当语句包含多个子句时,这两个步骤将对子句分别执行,如同这些子句被分成独立的import语句一样;如果请求成功,可以通过以下三种方式之一在局部命名空间中使用它们:

  • 模块名后使用 as 时,直接把 as 后的名称与导入模块绑定
  • 如果没有指定其他名称,且被导入模块为最高层级模块,则模块的名称被绑定到局部命名空间作为对所有模块的引入

控制模块导入内容

当使用 from module import *时,希望对从模块或包 导出的符号进行精确控制;可以通过 __all__ 来明确的列出需要导出的内容

模块

模块:test.py

 
 
def test():
 
pass
 
 
 
def add():
 
pass
 
 
 
def spam():
 
pass
 
 
 
__all__ = ['test', 'add']
 
 

目录结构:

 
 
test
 
    __init__.py
 
    file/
 
        __init__.py
 
        read.py
 
        write.py
 
    effects/
 
        __init__.py
 
        echo.py
 
        reverse.py
 
 

如果需要 from test.file. import * 精确控制,可以在包的__init__.py中定义__all__,用于控制导入模块:

 
 
# test/__init__.py
 
__all__ = ['test', 'effects']
 
 
 
posted on   DoubleLi  阅读(112)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
历史上的今天:
2022-01-11 图解正向代理、反向代理、透明代理
2022-01-11 如何利用MobaX同时处理多台虚拟机输入相同命令
2022-01-11 linux ssh执行命令_在Linux上通过SSH在多个节点上并行执行命令的三种方法
2022-01-11 Window、Linux查看本机外网ip
2021-01-11 mysql 5.7 安装 (压缩包方式 .tar.gz)
2019-01-11 C/C++程序CPU问题分析
2012-01-11 List<T>.Contains(T item)判断是否包含的根据是什么
点击右上角即可分享
微信分享提示