首页 > 编程语言 >第十弹 python 的 模块 和 导入机制

第十弹 python 的 模块 和 导入机制

时间:2023-01-11 12:44:39浏览次数:56  
标签:__ 第十 python sys 导入 模块 test

模块

简介

模块是包含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']
 
  ref:(183条消息) 第十弹 python 的 模块 和 导入机制_python模块导入机制_长策的博客-CSDN博客  

标签:__,第十,python,sys,导入,模块,test
From: https://www.cnblogs.com/lidabo/p/17043382.html

相关文章