一日一库—importlib

#秉着python一切皆为对象的原则,我来看看python库的类型
import os
print(type(os))# <class 'module'> 结果为一个名为'module'的类型

#1、什么样的文件类型叫做模块
    #以.py文件结尾的都可以被python认为是模块
#2、package的概念
    #为了帮助组织模块并提供名称层次结构,Python 还引入了包的概念
    #通常以一个包含 __init__.py 文件的目录形式实现。 当一个正规包被导入时,
    #这个 __init__.py 文件会隐式地被执行,它所定义的对象会被绑定到该包命名空间中的名称。__init__.py 
    #文件可以包含与任何其他模块中所包含的 Python 代码相似的代码,Python 将在模块被导入时为其添加额外的属性。
    #值得注意的是,当模块被正确加载的时候模块会被解析成module对象,所谓的添加额外属性也就是在类型的属性命名空间
    #__dict__添加,python中很多对象属性的管理都是动态添加的
    
    #要注意的一个重点概念是所有包都是模块,但并非所有模块都是包。或者换句话说,包只是一种特殊的模块。
    #特别地,任何具有 __path__ 属性的模块都会被当作是包。
    
import logging  #我们拿logging来举例,准确来讲logging是一个包,它的目录下包含了多个模块,按照上述,包可以看做一种特殊的模块

print(logging.__loader__.is_package('logging')) #通过包加载器来判断是否为一个包,当然我们可以通过模块的特殊属性来判断
print(logging.__name__) #限定名称如果为顶层包,则直接反馈包名,否则将以'.'的形式呈现路径结构
from logging import config
print(config.__name__)  #logging.config
print(logging.__package__)#logging
print(logging.__file__)#D:python3liblogging\__init__.py 如果为包则反馈包下__init__.py文件的路径,此文件会在包被导入时执行
print(config.__file__) #模块的文件路径
print(config.__path__) #报错了,模块没有__path__属性,而包则必须包含此属性,因此我们可以通过判断此属性来却别模块与包 '__path__' in config.__dict__ ?
print(logging.__path__)
#3、导入相关的模块属性
    #__name__¶
        #__name__ 属性必须被设为模块的完整限定名称。 此名称被用来在导入系统中唯一地标识模块。
        #fullname 限定名称的定义
            #参照官方文档的定义: 一个以点号分隔的名称,显示从模块的全局作用域到该模块中定义的某个类、函数或方法的“路径”
    
    #__loader__
        #__loader__ 属性必须被设为导入系统在加载模块时使用的加载器对象
    #__package__
        #模块的 __package__ 属性必须设定。 其取值必须为一个字符串,但可以与 __name__ 取相同的值。当模块是包时,其__package__ 值应该设为其 __name__ 值。 
        #当模块不是包时,对于最高层级模块 __package__ 应该设为空字符串,对于子模块则应该设为其父包名
    #__spec__
        #__spec__ 属性必须设为在导入模块时要使用的模块规格说明。 对 __spec__ 的正确设定将同时作用于 解释器启动期间初始化的模块
    #__path__
        #如果模块为包(不论是正规包还是命名空间包),则必须设置模块对象的 __path__ 属性
        #不是包的模块不应该具有 __path__ 属性。
    #__file__
        #包或模块的文件路径,当为包是路径为__init__.py文件的路径,当为模块时,则为模块的路径
        
        
#4、模块加载过程
    #官方提供的加载过程实现源码
    '''
module = None
if spec.loader is not None and hasattr(spec.loader, 'create_module'):
    # It is assumed 'exec_module' will also be defined on the loader.
    module = spec.loader.create_module(spec)
if module is None:
    module = ModuleType(spec.name)
    #这里我可以通过types模块来引入ModuleType来创建模块,但是通常不这样做
    #通过会根据spec来创建模块,因为这样可以在模块上设置更多的特有属性
    #可以通过importlib.util.module_from_spec() 来实现
# The import-related module attributes get set here:
_init_module_attrs(spec, module)

if spec.loader is None:
    if spec.submodule_search_locations is not None:
        # namespace package
        sys.modules[spec.name] = module
    else:
        # unsupported
        raise ImportError
elif not hasattr(spec.loader, 'exec_module'):
    module = spec.loader.load_module(spec.name)
    # Set __loader__ and __package__ if missing.
else:
    sys.modules[spec.name] = module
    try:
        spec.loader.exec_module(module)
    except BaseException:
        try:
            del sys.modules[spec.name]
        except KeyError:
            pass
        raise
return sys.modules[spec.name]
    '''
#过程说明
'''
    1、如果在 sys.modules 中存在指定名称的模块对象,导入操作会已经将其返回。
    2、加载器执行模块代码之前,该模块将存在于 sys.modules 中。 这一点很关键,因为该模块代码可能(直接或间接地)导入其自身;
        预先将其添加到 sys.modules 可防止在最坏情况下的无限递归和最好情况下的多次加载。
    3、如果加载失败,则该模块 -- 只限加载失败的模块 -- 将从 sys.modules 中移除。 任何已存在于 sys.modules 缓存的模块,
        以及任何作为附带影响被成功加载的模块仍会保留在缓存中。 这与重新加载不同,后者会把即使加载失败的模块也保留在 sys.modules 中。
    4、在模块创建完成但还未执行之前,导入机制会设置导入相关模块属性(在上面的示例伪代码中为 “_init_module_attrs”),详情参见 后续部分。
    5、模块执行是加载的关键时刻,在此期间将填充模块的命名空间(也就是module.__dict__)。 执行会完全委托给加载器,由加载器决定要填充的内容和方式。
    6、在加载过程中创建并传递给 exec_module() 的模块并不一定就是在导入结束时返回的模块 [2]。    
'''
原文地址:https://www.cnblogs.com/alplf123/p/10691426.html