zoukankan      html  css  js  c++  java
  • Python 动态从文件中导入类或函数的方法

    假设模块文件名是data_used_to_test.py,放在tests文件夹下

    文件夹结构如下:

    project
        |-tests
            |-data_used_to_test.py
    

    文件内包含一个test_class类:

    class test_class():
        def test_func(arg):
            return "hello {}".format(arg)
    

    代码全部基于 Python3.6.4

    1. 使用imp

      1. 用imp.find_module查找模块

        In [1]:file, pathname, description = imp.find_module('data_used_to_test', path=['tests/'])
        
        In [2]: file
        Out[2]: <_io.TextIOWrapper name='tests/data_used_to_test.py' mode='r' encoding='utf-8'>
        
        In [3]: pathname
        Out[3]: 'tests/data_used_to_test.py'
        
        In [4]: description
        Out[4]: ('.py', 'r', 1)
        
      2. 用imp.load_module将找到的模块加载到sys.modules中

        In [5]: mod = imp.load_module('test_load', file, pathname, description)
        In [6]: mod
        Out[6]: <module 'test_load' from 'tests/data_used_to_test.py'>
        

        这时候sys.modules里会多一条'test_load'的记录,值就是mod的值。

      3. 这时候就可以直接通过mod访问包内的对象了

        In [7]: mod.test_class().test_func('x')
        Out[7]: 'hello x'
        

        这个方法的优点是

        1. 简单,容易实现。
        2. 不用对pyc文件做特殊处理

        缺点是可定制性太低。不适合框架使用

        1. 无法动态修改模块源代码, 开放的api必须要很稳定,不会经常变动。
        2. 可以访问的对象一开始就要制定成固定名称。无法动态注册访问。
      4. 上面的问题2可以用getattr解决,让我们更进一步。

        In [8]: tmp = getattr(mod, 'test_class')
        
        In [9]: tmp
        Out[9]: test_load.test_class
        In [10]: tmp().test_func('l')
        Out[10]: 'hello l'
        

        这样就可以通过事先在外部模块中调用准备好的注册函数,

        把外部模块中的类或函数注册到一个全局的单例变量中,

        实现动态的模块加载和对象访问。

        但仍然无法解决问题 1.

        所以就需要另一种模块加载方法。

    2. 显式的定义一个加载函数

      import imp
      import sys
          
      def load_module(module_name, module_content):
          if fullname in sys.modules:
              return sys.modules[fullname]
              
          mod = sys.modules.setdefault(url, imp.new_module(module_name))
          mod.__file__ = module_name
          mod.__package__ = ''
              
          # if *.py
          code = compile(module_content, url, 'exec')
          # if *.pyc 有问题,我运行一直报错
          # code = marshal.loads(module_content[8:])
              
          exec(code, mod.__dict__)
          # 2的写法是 exec code in mod.__dict__
          # 其实就是让code在环境里运行一下,所以这里可能会有注入漏洞
          return mod
      

      这个函数接受模块的名字和源码内容,并使用 compile() 将其编译到一个代码对象中, 然后在一个新创建的模块对象的字典中来执行它。

      下面是这个函数的使用方式:

      In[1]: module_content = open('tests/data_used_to_test.py').read()
          
      In[2]: mod = load_module('test_import', module_content)
      

      后面就和 1.4 一样了。

      这样可以同时解决 1.3 中提出的两个问题。

      因为你是先将源码作为普通文件读进来的,也就可以做各种修改后再注册到sys.modules中。

      Pocsuite使用的就是这种方法。虽然我觉得他们当时可能也没太弄明白。

      当然这还是有不足的。

      1. 上面的两种方法都只支持简单的模块。并没有嵌入到通常的import语句中。并不支持更高级的结构比如包。
      2. 不够酷
    3. 自定义导入器

      PythonCookbook上给了自定义导入器的两种方式

      1. 创建一个元路径导入器
      2. 编写一个钩子直接嵌入到 sys.path 变量中去

      还没看明白,PEP302的文档太长了,大概知道是继承importlib.abc来做的。

  • 相关阅读:
    WeakReference(弱引用)
    男人怎么挑选女人
    同步方法和异步方法
    常指针与指针常量的区别(转帖)
    关于WebService的一些注意事项
    ASP.Net缓存技术
    关于GridView手动绑定的一段代码,一切尽在不言中
    基本三层架构的一些代码
    写给自己看的关于DataList的和RePeater
    ASP.Net绑定数据源
  • 原文地址:https://www.cnblogs.com/leisurelylicht/p/dong-tai-cong-wen-jian-zhong-dao-ru-lei-huo-han-sh.html
Copyright © 2011-2022 走看看