• 如何通过字符串形式导包(importlib模块的使用)


    1 模块简介

    Python提供了importlib包作为标准库的一部分。目的就是提供Python中import语句的实现(以及__import__函数)。另外,importlib允许程序员创建他们自定义的对象,可用于引入过程(也称为importer)。

    什么是imp?
    另外有一个叫做imp的模块,它提供给Python import语句机制的接口。这个模块在Python 3.4中被否决,目的就是为了只使用importlib。

    这个模块有些复杂,因此我们在这篇博文中主要讨论以下几个主题:

    • 动态引入
    • 检查模块是否可以被引入
    • 引入源文件自身
    • 第三方模块 import_from_github_com

    2 模块使用

    2.1 动态引入

    importlib模块支持传入字符串来引入一个模块。我们创建两个简单的模块来验证这个功能。我们将会给予两个模块相同的接口,让它们打印名字以便我们能够区分它们。创建两个模块,分别为foo.py和bar.py,代码如下所示,

    def main():
        print(__name__)

    现在我们使用importlib来引入它们。让我们看看这段代码如何去做的。确保你已经把这段代码放在与上面创建的两个模块相同的目录下。

    #importer.py
    import importlib
    
    def dynamic_import(module):
        return importlib.import_module(module)
    
    if __name__ == "__main__":
        module = dynamic_import('foo')
        module.main()
    
        module_two = dynamic_import('bar')
        module_two()

    在这段代码中,我们手动引入importlib模块,并创建一个简单的函数dynamic_import。这个函数所做的就是调用importlib模块中的import_module函数,入参就是我们传入的字符串,然后返回调用结果。在代码段的下面,我们调用每个模块的main方法,将会打印出每个模块的名称。

    在你的代码中,你可能不会大量这样做。当你只有一个字符串时,如果你想引入这个模块,importlib就允许你可以这么做。

    2.2 模块引入检查

    Python有一个编码规范就是EAPP:Easier to ask for forgiveness than permision。意思就是经常假设一些事情是存在的(例如,key在词典中),如果出错了,那么就捕获异常。你可以看 Python标准模块--import 文章中我们尝试引入模块,当它不存在时,我们就会捕获到ImportError。如果我们想检查并观察一个模块是否可以引入而不是仅仅是猜测,该如何去做?你可以使用importlib。代码如下:

    #coding:utf-8
    
    import importlib.util
    import importlib
    
    def check_module(module_name):
        module_spec = importlib.util.find_spec(module_name)
        if module_spec is None:
            print("Module :{} not found".format(module_name))
            return None
        else:
            print("Module:{} can be imported!".format(module_name))
            return module_spec
        
    def import_module_from_spec(module_spec):
        module = importlib.util.module_from_spec(module_spec)
        module_spec.loader.exec_module(module)
        return module
    
    if __name__ == "__main__":
        module_spec = check_module("fake_module")
        module_spec = check_module("collections")
        if(module_spec):
            module = import_module_from_spec(module_spec)
            print(dir(module))

    这里我们引入importlib模块的子模块util。在check_module函数中,我们调用find_spec函数来检查传入的字符串作为模块是否存在。首先,我们传入一个假的名称,然后我们传入一个Python模块的真实名称。如果你运行这段代码,你将会看到你传入一个没有安装的模块的名称,find_spec函数将会返回None,我们的代码将会打印出这个模块没有找到。如果找到了,我们就会返回模块的说明。

    我们可以获取到模块的说明,然后使用它来真正的引入模块。或者你可以将字符串传入到import_module函数中,正如我们在2.1节中所学习到的一样。但是我们已经学习到如何使用模块的说明。让我们看一下上述代码中的import_module_from_spec函数。它接受由check_module函数返回的模块说明。我们将其传入到module_from_spec函数,它将会返回引入的模块。Python的官方文档推荐,在引入模块后执行它,所以我们下一步做的就是调用exec_module函数。最后我们返回这个模块,并且运行Python的dir函数来确认这个我们就是我们所期望的。

    2.3 从源文件中引入

    在这一节中,我想说明importlib的子模块util还有另外一个技巧。你可以使用util通过模块名和文件路径来引入一个模块。示例如下所示,

    #coding:utf-8
    
    import importlib.util
    
    def import_source(module_name):
        module_file_path = module_name.__file__
        module_name = module_name.__name__
        
        module_spec = importlib.util.spec_from_file_location(module_name,module_file_path)
        module = importlib.util.module_from_spec(module_spec)
        module_spec.loader.exec_module(module)
        print(dir(module))
    
        msg = "The {module_name} module has the following methods:{methods}"
        print(msg.format(module_name = module_name,methods = dir(module)))
        
    if __name__ == "__main__":
        import logging
        import_source(logging)

    上述代码中,我们实际引入了logging模块,并将它传入到import_source函数。在这个函数中,我们首先获取到模块的实际路径和名称。然后我们将这些信息传入到util的spec_from_file_location函数中,这个将会返回模块的说明。一旦我们获取到模块的说明,我们就可以使用与2.2节相同的importlib机制来实际引入模块。

    现在让我们来看一个精巧的第三方库,Python的__import__()函数直接引入github中的包。

    2.4 import_from_github_com

    这个精巧的包叫做import_from_github_com,它可以用于发现和下载github上的包。为了安装他,你需要做的就是按照如下命令使用pip,

    pip install import_from_github_com

    这个包使用了PEP 302中新的引入钩子,允许你可以从github上引入包。这个包实际做的就是安装这个包并将它添加到本地。你需要Python 3.2或者更高的版本,git和pip才能使用这个包。

    一旦这些已经安装,你可以在Python shell中输入如下命令,

    >>> from github_com.zzzeek import sqlalchemy
    Collecting git+https://github.com/zzzeek/sqlalchemy
    Cloning https://github.com/zzzeek/sqlalchemy to /tmp/pip-acfv7t06-build
    Installing collected packages: SQLAlchemy
    Running setup.py install for SQLAlchemy ... done
    Successfully installed SQLAlchemy-1.1.0b1.dev0
    >>> locals()
    {'__builtins__': <module 'builtins' (built-in)>, '__spec__': None,
    '__package__': None, '__doc__': None, '__name__': '__main__',
    'sqlalchemy': <module 'sqlalchemy' from '/usr/local/lib/python3.5/site-packages/
    sqlalchemy/__init__.py'>,
    '__loader__': <class '_frozen_importlib.BuiltinImporter'>}

    你如果看了import_from_github_com的源码,你将会注意到它并没有使用importlib。实际上,它使用了pip来安装那些没有安装的包,然后使用Python的__import__()函数来引入新安装的模块。这段代码非常值得学习。

    2.5 总结

    到这里,你已经了解到在你的代码中如何使用importlib和引入钩子。当然还有很多超出本文所覆盖的知识,如果你需要写一个自定义的引入器或者下载器,你需要花费很多时间来阅读官方文档和源码。

    importlib包的目的是双重的。一个是在Python源代码中提供import语句(以及扩展名为__import__()函数)的实现。这提供了可以移植到任何Python解释器的import的实现。这也提供了比在除了Python之外的编程语言中实现的实现更容易理解的实现。

    第二个目的是,实现import的组件在此包中公开,使用户更容易创建自己的自定义对象(通常称为importer)以参与导入处理。

    importlib.__import__(name, globals=None, locals=None, fromlist=(), level=0)

    内建__import__()函数的实现。

    注意

    程序化导入模块应使用import_module()而不是此函数。

    importlib.import_module(name, package=None)

    导入模块。name参数指定要以绝对或相对术语导入的模块。pkg.mod..mod)。如果名称是以相对术语指定的,则参数必须设置为包的名称,该名称将作为解析包名称的锚点。import_module('.. mod', 'pkg.subpkg')将导入pkg.mod)。

    import_module()函数用作importlib.__import__()的简化包装。这意味着函数的所有语义都派生自importlib.__import__()这两个函数最重要的区别是import_module()返回指定的包或模块(例如,pkg.mod),而__import__()返回顶级包或模块pkg)。

    如果您动态导入自解析器开始执行后创建的模块(例如,创建了一个Python源文件),则可能需要调用invalidate_caches()才能注意到新模块由进口系统。

    在版本3.3中更改:父包会自动导入。

    importlib.find_loader(name, path=None)

    找到模块的加载器,可选择在指定的路径中。如果模块在sys.modules中,则返回sys.modules[name].__loader__(除非加载器None未设置,在这种情况下会引发ValueError)。否则,使用sys.meta_path的搜索完成。如果未找到加载程序,则返回None

    点名称没有父级的隐式导入,因为需要加载它们,这可能不是所希望的。要正确导入子模块,您需要导入子模块的所有父包,并使用正确的参数path

    版本3.3中的新功能。

    在版本3.4中更改:如果__loader__未设置,则引发ValueError,就像属性设置为None

    自版本3.4后弃用:改用importlib.util.find_spec()

    importlib.invalidate_caches()

    使存储在sys.meta_path的finder的内部缓存无效。如果finder实现invalidate_caches(),那么它将被调用以执行无效。如果在程序运行时创建/安装了任何模块,则应调用此函数,以确保所有查找程序都注意到新模块的存在。

    版本3.3中的新功能。

    importlib.reload(module)

    重新载入先前导入的模块参数必须是一个模块对象,因此必须先被成功导入。如果您已使用外部编辑器编辑了模块源文件,并希望在不离开Python解释器的情况下尝试新版本,这将非常有用。返回值是模块对象(如果重新导入会导致将不同的对象放置在sys.modules中,则可能不同)。

    当执行reload()时:

    • Python模块的代码被重新编译并重新执行模块级代码,通过重用最初加载模块的loader来定义一组新的对象,这些对象被绑定到模块字典中的名称。扩展模块的init功能不再第二次调用。
    • 与Python中的所有其他对象一样,旧对象只有在引用计数下降到零后才被回收。
    • 模块命名空间中的名称将更新为指向任何新的或已更改的对象。
    • 对旧对象的其他引用(例如模块外部的名称)不会重新引用来引用新对象,如果需要,必须在每个命名空间中对其进行更新。

    还有一些其他警告:

    当模块被重新加载时,它的字典(包含模块的全局变量)被保留。名称的重定义将覆盖旧的定义,因此这通常不是问题。如果模块的新版本未定义由旧版本定义的名称,则旧定义将保留。如果它维护一个全局表或对象缓存,使用try语句可以测试表的存在并跳过其初始化,如果需要,这个特性可以用于模块的优势:

    try:
        cache
    except NameError:
        cache = {}
    

    重新加载内建或动态加载的模块通常不是很有用。不建议重新加载sys__main__builtins和其他关键模块。在许多情况下,扩展模块不会被设计为初始化多次,并且可能在重新加载时以任意方式失败。

    如果模块使用from ... import ...从另一个模块导入对象,则对其他模块调用reload()其中一个方法是重新执行from语句,另一个方法是使用import和限定名称(module.name t15>)。

    如果一个模块实例化一个类的实例,重新加载定义该类的模块不会影响实例的方法定义 - 它们继续使用旧的类定义。对于派生类同样如此。

    版本3.4中的新功能。

    详情 http://python.usyiyi.cn/translate/python_352/library/importlib.html#module-importlib

  • 相关阅读:
    LTE-TDD随机接入过程(3)-RAR(MSG2)以及MSG1的重传
    《javascript设计模式》读书笔记一(接口)
    数据结构——算法之(033)(两个有序单链表合并为一个有序的单链表)
    利用redis和php-resque实现后台任务
    最能毁掉程序猿健康的几件事
    springMVC4(11)使用注解完毕数据格式化
    Linux
    Linux
    Linux
    Fluent_Python_Part4面向对象,11-iface-abc,协议(接口),抽象基类
  • 原文地址:https://www.cnblogs.com/zhaopanpan/p/9085344.html
Copyright © 2020-2023  润新知